summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFatih Acet <acetfatih@gmail.com>2016-07-22 17:01:57 +0300
committerFatih Acet <acetfatih@gmail.com>2016-07-22 17:07:29 +0300
commitbc3f33f92a16d3afdb8a472f60940e7c46a31564 (patch)
treec6e568504b7888591ad15586bb1ace99db8941ca
parent033e5423a2594e08a7ebcd2379bd2331f4c39032 (diff)
downloadgitlab-ce-jsify.tar.gz
JSify all the things!jsify
-rw-r--r--app/assets/javascripts/LabelManager.js107
-rw-r--r--app/assets/javascripts/LabelManager.js.coffee92
-rw-r--r--app/assets/javascripts/activities.js37
-rw-r--r--app/assets/javascripts/activities.js.coffee24
-rw-r--r--app/assets/javascripts/admin.js61
-rw-r--r--app/assets/javascripts/admin.js.coffee51
-rw-r--r--app/assets/javascripts/api.js133
-rw-r--r--app/assets/javascripts/api.js.coffee122
-rw-r--r--app/assets/javascripts/application.js317
-rw-r--r--app/assets/javascripts/application.js.coffee310
-rw-r--r--app/assets/javascripts/aside.js23
-rw-r--r--app/assets/javascripts/aside.js.coffee16
-rw-r--r--app/assets/javascripts/autosave.js60
-rw-r--r--app/assets/javascripts/autosave.js.coffee39
-rw-r--r--app/assets/javascripts/awards_handler.coffee372
-rw-r--r--app/assets/javascripts/awards_handler.js377
-rw-r--r--app/assets/javascripts/behaviors/autosize.js25
-rw-r--r--app/assets/javascripts/behaviors/autosize.js.coffee22
-rw-r--r--app/assets/javascripts/behaviors/details_behavior.coffee15
-rw-r--r--app/assets/javascripts/behaviors/details_behavior.js12
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js54
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js.coffee56
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js41
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js.coffee52
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.coffee14
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.js7
-rw-r--r--app/assets/javascripts/blob/blob_ci_yaml.js42
-rw-r--r--app/assets/javascripts/blob/blob_ci_yaml.js.coffee23
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js59
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js.coffee57
-rw-r--r--app/assets/javascripts/blob/blob_gitignore_selector.js19
-rw-r--r--app/assets/javascripts/blob/blob_gitignore_selector.js.coffee5
-rw-r--r--app/assets/javascripts/blob/blob_gitignore_selectors.js22
-rw-r--r--app/assets/javascripts/blob/blob_gitignore_selectors.js.coffee17
-rw-r--r--app/assets/javascripts/blob/blob_license_selector.js24
-rw-r--r--app/assets/javascripts/blob/blob_license_selector.js.coffee9
-rw-r--r--app/assets/javascripts/blob/blob_license_selectors.js22
-rw-r--r--app/assets/javascripts/blob/blob_license_selectors.js.coffee17
-rw-r--r--app/assets/javascripts/blob/edit_blob.js63
-rw-r--r--app/assets/javascripts/blob/edit_blob.js.coffee42
-rw-r--r--app/assets/javascripts/blob/template_selector.js71
-rw-r--r--app/assets/javascripts/blob/template_selector.js.coffee60
-rw-r--r--app/assets/javascripts/breakpoints.coffee37
-rw-r--r--app/assets/javascripts/breakpoints.js65
-rw-r--r--app/assets/javascripts/broadcast_message.js31
-rw-r--r--app/assets/javascripts/broadcast_message.js.coffee22
-rw-r--r--app/assets/javascripts/build.coffee114
-rw-r--r--app/assets/javascripts/build.js136
-rw-r--r--app/assets/javascripts/build_artifacts.js24
-rw-r--r--app/assets/javascripts/build_artifacts.js.coffee14
-rw-r--r--app/assets/javascripts/commit.js10
-rw-r--r--app/assets/javascripts/commit.js.coffee4
-rw-r--r--app/assets/javascripts/commit/file.js10
-rw-r--r--app/assets/javascripts/commit/file.js.coffee5
-rw-r--r--app/assets/javascripts/commit/image-file.js172
-rw-r--r--app/assets/javascripts/commit/image-file.js.coffee127
-rw-r--r--app/assets/javascripts/commits.js55
-rw-r--r--app/assets/javascripts/commits.js.coffee39
-rw-r--r--app/assets/javascripts/compare.js88
-rw-r--r--app/assets/javascripts/compare.js.coffee67
-rw-r--r--app/assets/javascripts/compare_autocomplete.js48
-rw-r--r--app/assets/javascripts/compare_autocomplete.js.coffee41
-rw-r--r--app/assets/javascripts/confirm_danger_modal.js29
-rw-r--r--app/assets/javascripts/confirm_danger_modal.js.coffee20
-rw-r--r--app/assets/javascripts/copy_to_clipboard.js38
-rw-r--r--app/assets/javascripts/copy_to_clipboard.js.coffee37
-rw-r--r--app/assets/javascripts/diff.js74
-rw-r--r--app/assets/javascripts/diff.js.coffee51
-rw-r--r--app/assets/javascripts/dispatcher.js252
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee171
-rw-r--r--app/assets/javascripts/dropzone_input.js215
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee201
-rw-r--r--app/assets/javascripts/due_date_select.js101
-rw-r--r--app/assets/javascripts/due_date_select.js.coffee99
-rw-r--r--app/assets/javascripts/extensions/jquery.js11
-rw-r--r--app/assets/javascripts/extensions/jquery.js.coffee11
-rw-r--r--app/assets/javascripts/files_comment_button.js138
-rw-r--r--app/assets/javascripts/files_comment_button.js.coffee98
-rw-r--r--app/assets/javascripts/flash.js40
-rw-r--r--app/assets/javascripts/flash.js.coffee28
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js269
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee228
-rw-r--r--app/assets/javascripts/gl_dropdown.js702
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee638
-rw-r--r--app/assets/javascripts/gl_form.js50
-rw-r--r--app/assets/javascripts/gl_form.js.coffee54
-rw-r--r--app/assets/javascripts/graphs/graphs_bundle.js3
-rw-r--r--app/assets/javascripts/graphs/graphs_bundle.js.coffee7
-rw-r--r--app/assets/javascripts/graphs/stat_graph.js16
-rw-r--r--app/assets/javascripts/graphs/stat_graph.js.coffee6
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors.js108
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors.js.coffee71
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_graph.js275
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee173
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_util.js132
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_util.js.coffee98
-rw-r--r--app/assets/javascripts/group_avatar.js18
-rw-r--r--app/assets/javascripts/group_avatar.js.coffee9
-rw-r--r--app/assets/javascripts/groups.js10
-rw-r--r--app/assets/javascripts/groups.js.coffee4
-rw-r--r--app/assets/javascripts/groups_select.js64
-rw-r--r--app/assets/javascripts/groups_select.js.coffee41
-rw-r--r--app/assets/javascripts/importer_status.js66
-rw-r--r--app/assets/javascripts/importer_status.js.coffee53
-rw-r--r--app/assets/javascripts/issuable.js86
-rw-r--r--app/assets/javascripts/issuable.js.coffee93
-rw-r--r--app/assets/javascripts/issuable_context.js66
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee60
-rw-r--r--app/assets/javascripts/issuable_form.js133
-rw-r--r--app/assets/javascripts/issuable_form.js.coffee112
-rw-r--r--app/assets/javascripts/issue.js148
-rw-r--r--app/assets/javascripts/issue.js.coffee117
-rw-r--r--app/assets/javascripts/issue_status_select.js32
-rw-r--r--app/assets/javascripts/issue_status_select.js.coffee18
-rw-r--r--app/assets/javascripts/issues-bulk-assignment.js158
-rw-r--r--app/assets/javascripts/issues-bulk-assignment.js.coffee128
-rw-r--r--app/assets/javascripts/labels.js41
-rw-r--r--app/assets/javascripts/labels.js.coffee28
-rw-r--r--app/assets/javascripts/labels_select.js374
-rw-r--r--app/assets/javascripts/labels_select.js.coffee386
-rw-r--r--app/assets/javascripts/layout_nav.js24
-rw-r--r--app/assets/javascripts/layout_nav.js.coffee24
-rw-r--r--app/assets/javascripts/lib/chart.js3
-rw-r--r--app/assets/javascripts/lib/chart.js.coffee1
-rw-r--r--app/assets/javascripts/lib/cropper.js3
-rw-r--r--app/assets/javascripts/lib/cropper.js.coffee1
-rw-r--r--app/assets/javascripts/lib/d3.js3
-rw-r--r--app/assets/javascripts/lib/d3.js.coffee1
-rw-r--r--app/assets/javascripts/lib/raphael.js7
-rw-r--r--app/assets/javascripts/lib/raphael.js.coffee3
-rw-r--r--app/assets/javascripts/lib/utils/animate.js46
-rw-r--r--app/assets/javascripts/lib/utils/animate.js.coffee39
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js57
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js.coffee68
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js33
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js.coffee28
-rw-r--r--app/assets/javascripts/lib/utils/notify.js38
-rw-r--r--app/assets/javascripts/lib/utils/notify.js.coffee35
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js109
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js.coffee105
-rw-r--r--app/assets/javascripts/lib/utils/type_utility.js12
-rw-r--r--app/assets/javascripts/lib/utils/type_utility.js.coffee9
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js61
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js.coffee52
-rw-r--r--app/assets/javascripts/line_highlighter.js111
-rw-r--r--app/assets/javascripts/line_highlighter.js.coffee148
-rw-r--r--app/assets/javascripts/logo.js51
-rw-r--r--app/assets/javascripts/logo.js.coffee44
-rw-r--r--app/assets/javascripts/markdown_preview.js147
-rw-r--r--app/assets/javascripts/markdown_preview.js.coffee119
-rw-r--r--app/assets/javascripts/merge_request.js99
-rw-r--r--app/assets/javascripts/merge_request.js.coffee82
-rw-r--r--app/assets/javascripts/merge_request_tabs.js235
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee252
-rw-r--r--app/assets/javascripts/merge_request_widget.js182
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee143
-rw-r--r--app/assets/javascripts/merged_buttons.js42
-rw-r--r--app/assets/javascripts/merged_buttons.js.coffee30
-rw-r--r--app/assets/javascripts/milestone.js192
-rw-r--r--app/assets/javascripts/milestone.js.coffee146
-rw-r--r--app/assets/javascripts/milestone_select.js148
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee137
-rw-r--r--app/assets/javascripts/namespace_select.js83
-rw-r--r--app/assets/javascripts/namespace_select.js.coffee56
-rw-r--r--app/assets/javascripts/network/branch-graph.js401
-rw-r--r--app/assets/javascripts/network/branch-graph.js.coffee340
-rw-r--r--app/assets/javascripts/network/network.js16
-rw-r--r--app/assets/javascripts/network/network.js.coffee9
-rw-r--r--app/assets/javascripts/network/network_bundle.js12
-rw-r--r--app/assets/javascripts/network/network_bundle.js.coffee17
-rw-r--r--app/assets/javascripts/new_branch_form.js101
-rw-r--r--app/assets/javascripts/new_branch_form.js.coffee78
-rw-r--r--app/assets/javascripts/new_commit_form.js31
-rw-r--r--app/assets/javascripts/new_commit_form.js.coffee21
-rw-r--r--app/assets/javascripts/notes.js722
-rw-r--r--app/assets/javascripts/notes.js.coffee694
-rw-r--r--app/assets/javascripts/notifications_dropdown.js27
-rw-r--r--app/assets/javascripts/notifications_dropdown.js.coffee25
-rw-r--r--app/assets/javascripts/notifications_form.js55
-rw-r--r--app/assets/javascripts/notifications_form.js.coffee49
-rw-r--r--app/assets/javascripts/pager.js60
-rw-r--r--app/assets/javascripts/pager.js.coffee44
-rw-r--r--app/assets/javascripts/profile/gl_crop.js166
-rw-r--r--app/assets/javascripts/profile/gl_crop.js.coffee152
-rw-r--r--app/assets/javascripts/profile/profile.js99
-rw-r--r--app/assets/javascripts/profile/profile.js.coffee83
-rw-r--r--app/assets/javascripts/profile/profile_bundle.js3
-rw-r--r--app/assets/javascripts/profile/profile_bundle.js.coffee2
-rw-r--r--app/assets/javascripts/project.js100
-rw-r--r--app/assets/javascripts/project.js.coffee91
-rw-r--r--app/assets/javascripts/project_avatar.js18
-rw-r--r--app/assets/javascripts/project_avatar.js.coffee9
-rw-r--r--app/assets/javascripts/project_find_file.js167
-rw-r--r--app/assets/javascripts/project_find_file.js.coffee125
-rw-r--r--app/assets/javascripts/project_fork.js11
-rw-r--r--app/assets/javascripts/project_fork.js.coffee5
-rw-r--r--app/assets/javascripts/project_import.js10
-rw-r--r--app/assets/javascripts/project_import.js.coffee5
-rw-r--r--app/assets/javascripts/project_members.js10
-rw-r--r--app/assets/javascripts/project_members.js.coffee4
-rw-r--r--app/assets/javascripts/project_new.js37
-rw-r--r--app/assets/javascripts/project_new.js.coffee23
-rw-r--r--app/assets/javascripts/project_select.js99
-rw-r--r--app/assets/javascripts/project_select.js.coffee72
-rw-r--r--app/assets/javascripts/project_show.js6
-rw-r--r--app/assets/javascripts/project_show.js.coffee3
-rw-r--r--app/assets/javascripts/projects_list.js45
-rw-r--r--app/assets/javascripts/projects_list.js.coffee36
-rw-r--r--app/assets/javascripts/protected_branch_select.js69
-rw-r--r--app/assets/javascripts/protected_branch_select.js.coffee40
-rw-r--r--app/assets/javascripts/protected_branches.js32
-rw-r--r--app/assets/javascripts/protected_branches.js.coffee22
-rw-r--r--app/assets/javascripts/right_sidebar.js198
-rw-r--r--app/assets/javascripts/right_sidebar.js.coffee175
-rw-r--r--app/assets/javascripts/search.js90
-rw-r--r--app/assets/javascripts/search.js.coffee75
-rw-r--r--app/assets/javascripts/search_autocomplete.js357
-rw-r--r--app/assets/javascripts/search_autocomplete.js.coffee334
-rw-r--r--app/assets/javascripts/shortcuts.js94
-rw-r--r--app/assets/javascripts/shortcuts.js.coffee60
-rw-r--r--app/assets/javascripts/shortcuts_blob.coffee10
-rw-r--r--app/assets/javascripts/shortcuts_blob.js24
-rw-r--r--app/assets/javascripts/shortcuts_dashboard_navigation.js35
-rw-r--r--app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee14
-rw-r--r--app/assets/javascripts/shortcuts_find_file.js31
-rw-r--r--app/assets/javascripts/shortcuts_find_file.js.coffee19
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee53
-rw-r--r--app/assets/javascripts/shortcuts_issuable.js70
-rw-r--r--app/assets/javascripts/shortcuts_navigation.coffee23
-rw-r--r--app/assets/javascripts/shortcuts_navigation.js60
-rw-r--r--app/assets/javascripts/shortcuts_network.js23
-rw-r--r--app/assets/javascripts/shortcuts_network.js.coffee12
-rw-r--r--app/assets/javascripts/sidebar.js38
-rw-r--r--app/assets/javascripts/sidebar.js.coffee37
-rw-r--r--app/assets/javascripts/single_file_diff.js74
-rw-r--r--app/assets/javascripts/single_file_diff.js.coffee54
-rw-r--r--app/assets/javascripts/star.js28
-rw-r--r--app/assets/javascripts/star.js.coffee24
-rw-r--r--app/assets/javascripts/subscription.js38
-rw-r--r--app/assets/javascripts/subscription.js.coffee26
-rw-r--r--app/assets/javascripts/subscription_select.js32
-rw-r--r--app/assets/javascripts/subscription_select.js.coffee18
-rw-r--r--app/assets/javascripts/syntax_highlight.coffee20
-rw-r--r--app/assets/javascripts/syntax_highlight.js15
-rw-r--r--app/assets/javascripts/todos.js141
-rw-r--r--app/assets/javascripts/todos.js.coffee110
-rw-r--r--app/assets/javascripts/tree.js62
-rw-r--r--app/assets/javascripts/tree.js.coffee50
-rw-r--r--app/assets/javascripts/u2f/authenticate.js86
-rw-r--r--app/assets/javascripts/u2f/authenticate.js.coffee75
-rw-r--r--app/assets/javascripts/u2f/error.js24
-rw-r--r--app/assets/javascripts/u2f/error.js.coffee13
-rw-r--r--app/assets/javascripts/u2f/register.js84
-rw-r--r--app/assets/javascripts/u2f/register.js.coffee63
-rw-r--r--app/assets/javascripts/u2f/util.js10
-rw-r--r--app/assets/javascripts/u2f/util.js.coffee3
-rw-r--r--app/assets/javascripts/user.js28
-rw-r--r--app/assets/javascripts/user.js.coffee17
-rw-r--r--app/assets/javascripts/user_tabs.js116
-rw-r--r--app/assets/javascripts/user_tabs.js.coffee156
-rw-r--r--app/assets/javascripts/users/calendar.js189
-rw-r--r--app/assets/javascripts/users/calendar.js.coffee194
-rw-r--r--app/assets/javascripts/users/users_bundle.js3
-rw-r--r--app/assets/javascripts/users/users_bundle.js.coffee2
-rw-r--r--app/assets/javascripts/users_select.js339
-rw-r--r--app/assets/javascripts/users_select.js.coffee330
-rw-r--r--app/assets/javascripts/wikis.js33
-rw-r--r--app/assets/javascripts/wikis.js.coffee19
-rw-r--r--app/assets/javascripts/zen_mode.js71
-rw-r--r--app/assets/javascripts/zen_mode.js.coffee80
-rw-r--r--coffee-asset-pipeline-comments.js37
-rw-r--r--node_modules/line-reader/.eslintrc14
-rw-r--r--node_modules/line-reader/.npmignore35
-rw-r--r--node_modules/line-reader/LICENSE20
-rw-r--r--node_modules/line-reader/README.md166
-rw-r--r--node_modules/line-reader/lib/line_reader.js306
-rw-r--r--node_modules/line-reader/package.json61
-rw-r--r--node_modules/line-reader/test.js89
-rw-r--r--node_modules/line-reader/test/data/empty_file.txt0
-rw-r--r--node_modules/line-reader/test/data/mac_os_9_file.txt1
-rw-r--r--node_modules/line-reader/test/data/multi_separator_file.txt2
-rw-r--r--node_modules/line-reader/test/data/multibyte_file.txt2
-rw-r--r--node_modules/line-reader/test/data/normal_file.txt6
-rw-r--r--node_modules/line-reader/test/data/one_line_file.txt1
-rw-r--r--node_modules/line-reader/test/data/one_line_file_no_endline.txt1
-rw-r--r--node_modules/line-reader/test/data/separator_file.txt2
-rw-r--r--node_modules/line-reader/test/data/three_line_file.txt3
-rw-r--r--node_modules/line-reader/test/data/unix_file.txt6
-rw-r--r--node_modules/line-reader/test/data/windows_buffer_overlap_file.txt2
-rw-r--r--node_modules/line-reader/test/data/windows_file.txt6
-rw-r--r--node_modules/line-reader/test/line_reader.js412
-rw-r--r--spec/javascripts/application_spec.js28
-rw-r--r--spec/javascripts/application_spec.js.coffee30
-rw-r--r--spec/javascripts/awards_handler_spec.js180
-rw-r--r--spec/javascripts/awards_handler_spec.js.coffee200
-rw-r--r--spec/javascripts/behaviors/autosize_spec.js17
-rw-r--r--spec/javascripts/behaviors/autosize_spec.js.coffee11
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js89
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js.coffee70
-rw-r--r--spec/javascripts/behaviors/requires_input_spec.js40
-rw-r--r--spec/javascripts/behaviors/requires_input_spec.js.coffee49
-rw-r--r--spec/javascripts/extensions/array_spec.js18
-rw-r--r--spec/javascripts/extensions/array_spec.js.coffee12
-rw-r--r--spec/javascripts/extensions/jquery_spec.js38
-rw-r--r--spec/javascripts/extensions/jquery_spec.js.coffee34
-rw-r--r--spec/javascripts/fixtures/emoji_menu.coffee957
-rw-r--r--spec/javascripts/fixtures/emoji_menu.js1
-rw-r--r--spec/javascripts/issue_spec.js116
-rw-r--r--spec/javascripts/issue_spec.js.coffee109
-rw-r--r--spec/javascripts/line_highlighter_spec.js225
-rw-r--r--spec/javascripts/line_highlighter_spec.js.coffee158
-rw-r--r--spec/javascripts/merge_request_spec.js24
-rw-r--r--spec/javascripts/merge_request_spec.js.coffee23
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js102
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js.coffee88
-rw-r--r--spec/javascripts/merge_request_widget_spec.js70
-rw-r--r--spec/javascripts/merge_request_widget_spec.js.coffee55
-rw-r--r--spec/javascripts/new_branch_spec.js165
-rw-r--r--spec/javascripts/new_branch_spec.js.coffee160
-rw-r--r--spec/javascripts/notes_spec.js36
-rw-r--r--spec/javascripts/notes_spec.js.coffee26
-rw-r--r--spec/javascripts/project_title_spec.js50
-rw-r--r--spec/javascripts/project_title_spec.js.coffee37
-rw-r--r--spec/javascripts/right_sidebar_spec.js64
-rw-r--r--spec/javascripts/right_sidebar_spec.js.coffee69
-rw-r--r--spec/javascripts/search_autocomplete_spec.js150
-rw-r--r--spec/javascripts/search_autocomplete_spec.js.coffee149
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js70
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js.coffee82
-rw-r--r--spec/javascripts/spec_helper.coffee47
-rw-r--r--spec/javascripts/spec_helper.js13
-rw-r--r--spec/javascripts/syntax_highlight_spec.js40
-rw-r--r--spec/javascripts/syntax_highlight_spec.js.coffee42
-rw-r--r--spec/javascripts/u2f/authenticate_spec.coffee51
-rw-r--r--spec/javascripts/u2f/authenticate_spec.js67
-rw-r--r--spec/javascripts/u2f/mock_u2f_device.js30
-rw-r--r--spec/javascripts/u2f/mock_u2f_device.js.coffee15
-rw-r--r--spec/javascripts/u2f/register_spec.js73
-rw-r--r--spec/javascripts/u2f/register_spec.js.coffee56
-rw-r--r--spec/javascripts/zen_mode_spec.js69
-rw-r--r--spec/javascripts/zen_mode_spec.js.coffee51
-rw-r--r--vendor/assets/javascripts/task_list.js112
-rw-r--r--vendor/assets/javascripts/task_list.js.coffee258
343 files changed, 15299 insertions, 13302 deletions
diff --git a/app/assets/javascripts/LabelManager.js b/app/assets/javascripts/LabelManager.js
new file mode 100644
index 00000000000..8aa97a4d1d1
--- /dev/null
+++ b/app/assets/javascripts/LabelManager.js
@@ -0,0 +1,107 @@
+this.LabelManager = (function() {
+ LabelManager.prototype.errorMessage = 'Unable to update label prioritization at this time';
+
+ function LabelManager(opts) {
+ var ref, ref1, ref2;
+ if (opts == null) {
+ opts = {};
+ }
+ this.togglePriorityButton = (ref = opts.togglePriorityButton) != null ? ref : $('.js-toggle-priority'), this.prioritizedLabels = (ref1 = opts.prioritizedLabels) != null ? ref1 : $('.js-prioritized-labels'), this.otherLabels = (ref2 = opts.otherLabels) != null ? ref2 : $('.js-other-labels');
+ this.prioritizedLabels.sortable({
+ items: 'li',
+ placeholder: 'list-placeholder',
+ axis: 'y',
+ update: this.onPrioritySortUpdate.bind(this)
+ });
+ this.bindEvents();
+ }
+
+ LabelManager.prototype.bindEvents = function() {
+ return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick);
+ };
+
+ LabelManager.prototype.onTogglePriorityClick = function(e) {
+ var $btn, $label, $tooltip, _this, action;
+ e.preventDefault();
+ _this = e.data;
+ $btn = $(e.currentTarget);
+ $label = $("#" + ($btn.data('domId')));
+ action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
+ $tooltip = $("#" + ($btn.find('.has-tooltip:visible').attr('aria-describedby')));
+ $tooltip.tooltip('destroy');
+ return _this.toggleLabelPriority($label, action);
+ };
+
+ LabelManager.prototype.toggleLabelPriority = function($label, action, persistState) {
+ var $from, $target, _this, url, xhr;
+ if (persistState == null) {
+ persistState = true;
+ }
+ _this = this;
+ url = $label.find('.js-toggle-priority').data('url');
+ $target = this.prioritizedLabels;
+ $from = this.otherLabels;
+ if (action === 'remove') {
+ $target = this.otherLabels;
+ $from = this.prioritizedLabels;
+ }
+ if ($from.find('li').length === 1) {
+ $from.find('.empty-message').removeClass('hidden');
+ }
+ if (!$target.find('li').length) {
+ $target.find('.empty-message').addClass('hidden');
+ }
+ $label.detach().appendTo($target);
+ if (!persistState) {
+ return;
+ }
+ if (action === 'remove') {
+ xhr = $.ajax({
+ url: url,
+ type: 'DELETE'
+ });
+ if (!$from.find('li').length) {
+ $from.find('.empty-message').removeClass('hidden');
+ }
+ } else {
+ xhr = this.savePrioritySort($label, action);
+ }
+ return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action));
+ };
+
+ LabelManager.prototype.onPrioritySortUpdate = function() {
+ var xhr;
+ xhr = this.savePrioritySort();
+ return xhr.fail(function() {
+ return new Flash(this.errorMessage, 'alert');
+ });
+ };
+
+ LabelManager.prototype.savePrioritySort = function() {
+ return $.post({
+ url: this.prioritizedLabels.data('url'),
+ data: {
+ label_ids: this.getSortedLabelsIds()
+ }
+ });
+ };
+
+ LabelManager.prototype.rollbackLabelPosition = function($label, originalAction) {
+ var action;
+ action = originalAction === 'remove' ? 'add' : 'remove';
+ this.toggleLabelPriority($label, action, false);
+ return new Flash(this.errorMessage, 'alert');
+ };
+
+ LabelManager.prototype.getSortedLabelsIds = function() {
+ var sortedIds;
+ sortedIds = [];
+ this.prioritizedLabels.find('li').each(function() {
+ return sortedIds.push($(this).data('id'));
+ });
+ return sortedIds;
+ };
+
+ return LabelManager;
+
+})();
diff --git a/app/assets/javascripts/LabelManager.js.coffee b/app/assets/javascripts/LabelManager.js.coffee
deleted file mode 100644
index 6d8faba40d7..00000000000
--- a/app/assets/javascripts/LabelManager.js.coffee
+++ /dev/null
@@ -1,92 +0,0 @@
-class @LabelManager
- errorMessage: 'Unable to update label prioritization at this time'
-
- constructor: (opts = {}) ->
- # Defaults
- {
- @togglePriorityButton = $('.js-toggle-priority')
- @prioritizedLabels = $('.js-prioritized-labels')
- @otherLabels = $('.js-other-labels')
- } = opts
-
- @prioritizedLabels.sortable(
- items: 'li'
- placeholder: 'list-placeholder'
- axis: 'y'
- update: @onPrioritySortUpdate.bind(@)
- )
-
- @bindEvents()
-
- bindEvents: ->
- @togglePriorityButton.on 'click', @, @onTogglePriorityClick
-
- onTogglePriorityClick: (e) ->
- e.preventDefault()
- _this = e.data
- $btn = $(e.currentTarget)
- $label = $("##{$btn.data('domId')}")
- action = if $btn.parents('.js-prioritized-labels').length then 'remove' else 'add'
-
- # Make sure tooltip will hide
- $tooltip = $ "##{$btn.find('.has-tooltip:visible').attr('aria-describedby')}"
- $tooltip.tooltip 'destroy'
-
- _this.toggleLabelPriority($label, action)
-
- toggleLabelPriority: ($label, action, persistState = true) ->
- _this = @
- url = $label.find('.js-toggle-priority').data 'url'
-
- $target = @prioritizedLabels
- $from = @otherLabels
-
- # Optimistic update
- if action is 'remove'
- $target = @otherLabels
- $from = @prioritizedLabels
-
- if $from.find('li').length is 1
- $from.find('.empty-message').removeClass('hidden')
-
- if not $target.find('li').length
- $target.find('.empty-message').addClass('hidden')
-
- $label.detach().appendTo($target)
-
- # Return if we are not persisting state
- return unless persistState
-
- if action is 'remove'
- xhr = $.ajax url: url, type: 'DELETE'
-
- # Restore empty message
- $from.find('.empty-message').removeClass('hidden') unless $from.find('li').length
- else
- xhr = @savePrioritySort($label, action)
-
- xhr.fail @rollbackLabelPosition.bind(@, $label, action)
-
- onPrioritySortUpdate: ->
- xhr = @savePrioritySort()
-
- xhr.fail ->
- new Flash(@errorMessage, 'alert')
-
- savePrioritySort: () ->
- $.post
- url: @prioritizedLabels.data('url')
- data:
- label_ids: @getSortedLabelsIds()
-
- rollbackLabelPosition: ($label, originalAction)->
- action = if originalAction is 'remove' then 'add' else 'remove'
- @toggleLabelPriority($label, action, false)
-
- new Flash(@errorMessage, 'alert')
-
- getSortedLabelsIds: ->
- sortedIds = []
- @prioritizedLabels.find('li').each ->
- sortedIds.push $(@).data 'id'
- sortedIds
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
new file mode 100644
index 00000000000..b4190d6eaf1
--- /dev/null
+++ b/app/assets/javascripts/activities.js
@@ -0,0 +1,37 @@
+this.Activities = (function() {
+ function Activities() {
+ Pager.init(20, true, false, this.updateTooltips);
+ $(".event-filter-link").on("click", (function(_this) {
+ return function(event) {
+ event.preventDefault();
+ _this.toggleFilter($(event.currentTarget));
+ return _this.reloadActivities();
+ };
+ })(this));
+ }
+
+ Activities.prototype.updateTooltips = function() {
+ return gl.utils.localTimeAgo($('.js-timeago', '#activity'));
+ };
+
+ Activities.prototype.reloadActivities = function() {
+ $(".content_list").html('');
+ return Pager.init(20, true);
+ };
+
+ Activities.prototype.toggleFilter = function(sender) {
+ var event_filters, filter;
+ $('.event-filter .active').removeClass("active");
+ event_filters = $.cookie("event_filter");
+ filter = sender.attr("id").split("_")[0];
+ $.cookie("event_filter", (event_filters !== filter ? filter : ""), {
+ path: '/'
+ });
+ if (event_filters !== filter) {
+ return sender.closest('li').toggleClass("active");
+ }
+ };
+
+ return Activities;
+
+})();
diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee
deleted file mode 100644
index ed5a5d0260c..00000000000
--- a/app/assets/javascripts/activities.js.coffee
+++ /dev/null
@@ -1,24 +0,0 @@
-class @Activities
- constructor: ->
- Pager.init 20, true, false, @updateTooltips
- $(".event-filter-link").on "click", (event) =>
- event.preventDefault()
- @toggleFilter($(event.currentTarget))
- @reloadActivities()
-
- updateTooltips: ->
- gl.utils.localTimeAgo($('.js-timeago', '#activity'))
-
- reloadActivities: ->
- $(".content_list").html ''
- Pager.init 20, true
-
-
- toggleFilter: (sender) ->
- $('.event-filter .active').removeClass "active"
- event_filters = $.cookie("event_filter")
- filter = sender.attr("id").split("_")[0]
- $.cookie "event_filter", (if event_filters isnt filter then filter else ""), { path: '/' }
-
- if event_filters isnt filter
- sender.closest('li').toggleClass "active"
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js
new file mode 100644
index 00000000000..23c7f65aeb1
--- /dev/null
+++ b/app/assets/javascripts/admin.js
@@ -0,0 +1,61 @@
+this.Admin = (function() {
+ function Admin() {
+ var modal, showBlacklistType;
+ $('input#user_force_random_password').on('change', function(elem) {
+ var elems;
+ elems = $('#user_password, #user_password_confirmation');
+ if ($(this).attr('checked')) {
+ return elems.val('').attr('disabled', true);
+ } else {
+ return elems.removeAttr('disabled');
+ }
+ });
+ $('body').on('click', '.js-toggle-colors-link', function(e) {
+ e.preventDefault();
+ return $('.js-toggle-colors-container').toggle();
+ });
+ $('.log-tabs a').click(function(e) {
+ e.preventDefault();
+ return $(this).tab('show');
+ });
+ $('.log-bottom').click(function(e) {
+ var visible_log;
+ e.preventDefault();
+ visible_log = $(".file-content:visible");
+ return visible_log.animate({
+ scrollTop: visible_log.find('ol').height()
+ }, "fast");
+ });
+ modal = $('.change-owner-holder');
+ $('.change-owner-link').bind("click", function(e) {
+ e.preventDefault();
+ $(this).hide();
+ return modal.show();
+ });
+ $('.change-owner-cancel-link').bind("click", function(e) {
+ e.preventDefault();
+ modal.hide();
+ return $('.change-owner-link').show();
+ });
+ $('li.project_member').bind('ajax:success', function() {
+ return Turbolinks.visit(location.href);
+ });
+ $('li.group_member').bind('ajax:success', function() {
+ return Turbolinks.visit(location.href);
+ });
+ showBlacklistType = function() {
+ if ($("input[name='blacklist_type']:checked").val() === 'file') {
+ $('.blacklist-file').show();
+ return $('.blacklist-raw').hide();
+ } else {
+ $('.blacklist-file').hide();
+ return $('.blacklist-raw').show();
+ }
+ };
+ $("input[name='blacklist_type']").click(showBlacklistType);
+ showBlacklistType();
+ }
+
+ return Admin;
+
+})();
diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee
deleted file mode 100644
index 90c09619f8c..00000000000
--- a/app/assets/javascripts/admin.js.coffee
+++ /dev/null
@@ -1,51 +0,0 @@
-class @Admin
- constructor: ->
- $('input#user_force_random_password').on 'change', (elem) ->
- elems = $('#user_password, #user_password_confirmation')
-
- if $(@).attr 'checked'
- elems.val('').attr 'disabled', true
- else
- elems.removeAttr 'disabled'
-
- $('body').on 'click', '.js-toggle-colors-link', (e) ->
- e.preventDefault()
- $('.js-toggle-colors-container').toggle()
-
- $('.log-tabs a').click (e) ->
- e.preventDefault()
- $(this).tab('show')
-
- $('.log-bottom').click (e) ->
- e.preventDefault()
- visible_log = $(".file-content:visible")
- visible_log.animate({ scrollTop: visible_log.find('ol').height() }, "fast")
-
- modal = $('.change-owner-holder')
-
- $('.change-owner-link').bind "click", (e) ->
- e.preventDefault()
- $(this).hide()
- modal.show()
-
- $('.change-owner-cancel-link').bind "click", (e) ->
- e.preventDefault()
- modal.hide()
- $('.change-owner-link').show()
-
- $('li.project_member').bind 'ajax:success', ->
- Turbolinks.visit(location.href)
-
- $('li.group_member').bind 'ajax:success', ->
- Turbolinks.visit(location.href)
-
- showBlacklistType = ->
- if $("input[name='blacklist_type']:checked").val() == 'file'
- $('.blacklist-file').show()
- $('.blacklist-raw').hide()
- else
- $('.blacklist-file').hide()
- $('.blacklist-raw').show()
-
- $("input[name='blacklist_type']").click showBlacklistType
- showBlacklistType()
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
new file mode 100644
index 00000000000..a70cc87232d
--- /dev/null
+++ b/app/assets/javascripts/api.js
@@ -0,0 +1,133 @@
+this.Api = {
+ groupsPath: "/api/:version/groups.json",
+ groupPath: "/api/:version/groups/:id.json",
+ namespacesPath: "/api/:version/namespaces.json",
+ groupProjectsPath: "/api/:version/groups/:id/projects.json",
+ projectsPath: "/api/:version/projects.json?simple=true",
+ labelsPath: "/api/:version/projects/:id/labels",
+ licensePath: "/api/:version/licenses/:key",
+ gitignorePath: "/api/:version/gitignores/:key",
+ gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key",
+ group: function(group_id, callback) {
+ var url;
+ url = Api.buildUrl(Api.groupPath);
+ url = url.replace(':id', group_id);
+ return $.ajax({
+ url: url,
+ data: {
+ private_token: gon.api_token
+ },
+ dataType: "json"
+ }).done(function(group) {
+ return callback(group);
+ });
+ },
+ groups: function(query, skip_ldap, callback) {
+ var url;
+ url = Api.buildUrl(Api.groupsPath);
+ return $.ajax({
+ url: url,
+ data: {
+ private_token: gon.api_token,
+ search: query,
+ per_page: 20
+ },
+ dataType: "json"
+ }).done(function(groups) {
+ return callback(groups);
+ });
+ },
+ namespaces: function(query, callback) {
+ var url;
+ url = Api.buildUrl(Api.namespacesPath);
+ return $.ajax({
+ url: url,
+ data: {
+ private_token: gon.api_token,
+ search: query,
+ per_page: 20
+ },
+ dataType: "json"
+ }).done(function(namespaces) {
+ return callback(namespaces);
+ });
+ },
+ projects: function(query, order, callback) {
+ var url;
+ url = Api.buildUrl(Api.projectsPath);
+ return $.ajax({
+ url: url,
+ data: {
+ private_token: gon.api_token,
+ search: query,
+ order_by: order,
+ per_page: 20
+ },
+ dataType: "json"
+ }).done(function(projects) {
+ return callback(projects);
+ });
+ },
+ newLabel: function(project_id, data, callback) {
+ var url;
+ url = Api.buildUrl(Api.labelsPath);
+ url = url.replace(':id', project_id);
+ data.private_token = gon.api_token;
+ return $.ajax({
+ url: url,
+ type: "POST",
+ data: data,
+ dataType: "json"
+ }).done(function(label) {
+ return callback(label);
+ }).error(function(message) {
+ return callback(message.responseJSON);
+ });
+ },
+ groupProjects: function(group_id, query, callback) {
+ var url;
+ url = Api.buildUrl(Api.groupProjectsPath);
+ url = url.replace(':id', group_id);
+ return $.ajax({
+ url: url,
+ data: {
+ private_token: gon.api_token,
+ search: query,
+ per_page: 20
+ },
+ dataType: "json"
+ }).done(function(projects) {
+ return callback(projects);
+ });
+ },
+ licenseText: function(key, data, callback) {
+ var url;
+ url = Api.buildUrl(Api.licensePath).replace(':key', key);
+ return $.ajax({
+ url: url,
+ data: data
+ }).done(function(license) {
+ return callback(license);
+ });
+ },
+ gitignoreText: function(key, callback) {
+ var url;
+ url = Api.buildUrl(Api.gitignorePath).replace(':key', key);
+ return $.get(url, function(gitignore) {
+ return callback(gitignore);
+ });
+ },
+ gitlabCiYml: function(key, callback) {
+ var url;
+ url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key);
+ return $.get(url, function(file) {
+ return callback(file);
+ });
+ },
+ buildUrl: function(url) {
+ if (gon.relative_url_root != null) {
+ url = gon.relative_url_root + url;
+ }
+ return url.replace(':version', gon.api_version);
+ }
+};
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
deleted file mode 100644
index 89b0ac697ed..00000000000
--- a/app/assets/javascripts/api.js.coffee
+++ /dev/null
@@ -1,122 +0,0 @@
-@Api =
- groupsPath: "/api/:version/groups.json"
- groupPath: "/api/:version/groups/:id.json"
- namespacesPath: "/api/:version/namespaces.json"
- groupProjectsPath: "/api/:version/groups/:id/projects.json"
- projectsPath: "/api/:version/projects.json?simple=true"
- labelsPath: "/api/:version/projects/:id/labels"
- licensePath: "/api/:version/licenses/:key"
- gitignorePath: "/api/:version/gitignores/:key"
- gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key"
-
- group: (group_id, callback) ->
- url = Api.buildUrl(Api.groupPath)
- url = url.replace(':id', group_id)
-
- $.ajax(
- url: url
- data:
- private_token: gon.api_token
- dataType: "json"
- ).done (group) ->
- callback(group)
-
- # Return groups list. Filtered by query
- # Only active groups retrieved
- groups: (query, skip_ldap, callback) ->
- url = Api.buildUrl(Api.groupsPath)
-
- $.ajax(
- url: url
- data:
- private_token: gon.api_token
- search: query
- per_page: 20
- dataType: "json"
- ).done (groups) ->
- callback(groups)
-
- # Return namespaces list. Filtered by query
- namespaces: (query, callback) ->
- url = Api.buildUrl(Api.namespacesPath)
-
- $.ajax(
- url: url
- data:
- private_token: gon.api_token
- search: query
- per_page: 20
- dataType: "json"
- ).done (namespaces) ->
- callback(namespaces)
-
- # Return projects list. Filtered by query
- projects: (query, order, callback) ->
- url = Api.buildUrl(Api.projectsPath)
-
- $.ajax(
- url: url
- data:
- private_token: gon.api_token
- search: query
- order_by: order
- per_page: 20
- dataType: "json"
- ).done (projects) ->
- callback(projects)
-
- newLabel: (project_id, data, callback) ->
- url = Api.buildUrl(Api.labelsPath)
- url = url.replace(':id', project_id)
-
- data.private_token = gon.api_token
- $.ajax(
- url: url
- type: "POST"
- data: data
- dataType: "json"
- ).done (label) ->
- callback(label)
- .error (message) ->
- callback(message.responseJSON)
-
- # Return group projects list. Filtered by query
- groupProjects: (group_id, query, callback) ->
- url = Api.buildUrl(Api.groupProjectsPath)
- url = url.replace(':id', group_id)
-
- $.ajax(
- url: url
- data:
- private_token: gon.api_token
- search: query
- per_page: 20
- dataType: "json"
- ).done (projects) ->
- callback(projects)
-
- # Return text for a specific license
- licenseText: (key, data, callback) ->
- url = Api.buildUrl(Api.licensePath).replace(':key', key)
-
- $.ajax(
- url: url
- data: data
- ).done (license) ->
- callback(license)
-
- gitignoreText: (key, callback) ->
- url = Api.buildUrl(Api.gitignorePath).replace(':key', key)
-
- $.get url, (gitignore) ->
- callback(gitignore)
-
- gitlabCiYml: (key, callback) ->
- url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key)
-
- $.get url, (file) ->
- callback(file)
-
- buildUrl: (url) ->
- url = gon.relative_url_root + url if gon.relative_url_root?
- return url.replace(':version', gon.api_version)
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
new file mode 100644
index 00000000000..ce245493812
--- /dev/null
+++ b/app/assets/javascripts/application.js
@@ -0,0 +1,317 @@
+/*= require jquery2 */
+/*= require jquery-ui/autocomplete */
+/*= require jquery-ui/datepicker */
+/*= require jquery-ui/draggable */
+/*= require jquery-ui/effect-highlight */
+/*= require jquery-ui/sortable */
+/*= require jquery_ujs */
+/*= require jquery.cookie */
+/*= require jquery.endless-scroll */
+/*= require jquery.highlight */
+/*= require jquery.waitforimages */
+/*= require jquery.atwho */
+/*= require jquery.scrollTo */
+/*= require jquery.turbolinks */
+/*= require turbolinks */
+/*= require autosave */
+/*= require bootstrap/affix */
+/*= require bootstrap/alert */
+/*= require bootstrap/button */
+/*= require bootstrap/collapse */
+/*= require bootstrap/dropdown */
+/*= require bootstrap/modal */
+/*= require bootstrap/scrollspy */
+/*= require bootstrap/tab */
+/*= require bootstrap/transition */
+/*= require bootstrap/tooltip */
+/*= require bootstrap/popover */
+/*= require select2 */
+/*= require ace/ace */
+/*= require ace/ext-searchbox */
+/*= require underscore */
+/*= require dropzone */
+/*= require mousetrap */
+/*= require mousetrap/pause */
+/*= require shortcuts */
+/*= require shortcuts_navigation */
+/*= require shortcuts_dashboard_navigation */
+/*= require shortcuts_issuable */
+/*= require shortcuts_network */
+/*= require jquery.nicescroll */
+/*= require date.format */
+/*= require_directory ./behaviors */
+/*= require_directory ./blob */
+/*= require_directory ./commit */
+/*= require_directory ./extensions */
+/*= require_directory ./lib/utils */
+/*= require_directory ./u2f */
+/*= require_directory . */
+/*= require fuzzaldrin-plus */
+
+window.slugify = function(text) {
+ return text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase();
+};
+
+window.ajaxGet = function(url) {
+ return $.ajax({
+ type: "GET",
+ url: url,
+ dataType: "script"
+ });
+};
+
+window.split = function(val) {
+ return val.split(/,\s*/);
+};
+
+window.extractLast = function(term) {
+ return split(term).pop();
+};
+
+window.rstrip = function(val) {
+ if (val) {
+ return val.replace(/\s+$/, '');
+ } else {
+ return val;
+ }
+};
+
+window.disableButtonIfEmptyField = function(field_selector, button_selector) {
+ var closest_submit, field;
+ field = $(field_selector);
+ closest_submit = field.closest('form').find(button_selector);
+ if (rstrip(field.val()) === "") {
+ closest_submit.disable();
+ }
+ return field.on('input', function() {
+ if (rstrip($(this).val()) === "") {
+ return closest_submit.disable();
+ } else {
+ return closest_submit.enable();
+ }
+ });
+};
+
+window.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) {
+ var closest_submit, updateButtons;
+ closest_submit = form.find(button_selector);
+ updateButtons = function() {
+ var filled;
+ filled = true;
+ form.find('input').filter(form_selector).each(function() {
+ return filled = rstrip($(this).val()) !== "" || !$(this).attr('required');
+ });
+ if (filled) {
+ return closest_submit.enable();
+ } else {
+ return closest_submit.disable();
+ }
+ };
+ updateButtons();
+ return form.keyup(updateButtons);
+};
+
+window.sanitize = function(str) {
+ return str.replace(/<(?:.|\n)*?>/gm, '');
+};
+
+window.unbindEvents = function() {
+ return $(document).off('scroll');
+};
+
+window.shiftWindow = function() {
+ return scrollBy(0, -100);
+};
+
+document.addEventListener("page:fetch", unbindEvents);
+
+window.addEventListener("hashchange", shiftWindow);
+
+window.onload = function() {
+ if (location.hash) {
+ return setTimeout(shiftWindow, 100);
+ }
+};
+
+$(function() {
+ var $body, $document, $sidebarGutterToggle, $window, bootstrapBreakpoint, checkInitialSidebarSize, fitSidebarForSize, flash;
+ $document = $(document);
+ $window = $(window);
+ $body = $('body');
+ gl.utils.preventDisabledButtons();
+ bootstrapBreakpoint = bp.getBreakpointSize();
+ $(".nav-sidebar").niceScroll({
+ cursoropacitymax: '0.4',
+ cursorcolor: '#FFF',
+ cursorborder: "1px solid #FFF"
+ });
+ $(".js-select-on-focus").on("focusin", function() {
+ return $(this).select().one('mouseup', function(e) {
+ return e.preventDefault();
+ });
+ });
+ $('.remove-row').bind('ajax:success', function() {
+ return $(this).closest('li').fadeOut();
+ });
+ $('.js-remove-tr').bind('ajax:before', function() {
+ return $(this).hide();
+ });
+ $('.js-remove-tr').bind('ajax:success', function() {
+ return $(this).closest('tr').fadeOut();
+ });
+ $('select.select2').select2({
+ width: 'resolve',
+ dropdownAutoWidth: true
+ });
+ $('.js-select2').bind('select2-close', function() {
+ return setTimeout((function() {
+ $('.select2-container-active').removeClass('select2-container-active');
+ return $(':focus').blur();
+ }), 1);
+ });
+ $body.tooltip({
+ selector: '.has-tooltip, [data-toggle="tooltip"]',
+ placement: function(_, el) {
+ var $el;
+ $el = $(el);
+ return $el.data('placement') || 'bottom';
+ }
+ });
+ $('.trigger-submit').on('change', function() {
+ return $(this).parents('form').submit();
+ });
+ gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
+ if ((flash = $(".flash-container")).length > 0) {
+ flash.click(function() {
+ return $(this).fadeOut();
+ });
+ flash.show();
+ }
+ $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function(e) {
+ var buttons;
+ buttons = $('[type="submit"]', this);
+ switch (e.type) {
+ case 'ajax:beforeSend':
+ case 'submit':
+ return buttons.disable();
+ default:
+ return buttons.enable();
+ }
+ });
+ $(document).ajaxError(function(e, xhrObj, xhrSetting, xhrErrorText) {
+ var ref;
+ if (xhrObj.status === 401) {
+ return new Flash('You need to be logged in.', 'alert');
+ } else if ((ref = xhrObj.status) === 404 || ref === 500) {
+ return new Flash('Something went wrong on our end.', 'alert');
+ }
+ });
+ $('.account-box').hover(function() {
+ return $(this).toggleClass('hover');
+ });
+ $document.on('click', '.diff-content .js-show-suppressed-diff', function() {
+ var $container;
+ $container = $(this).parent();
+ $container.next('table').show();
+ return $container.remove();
+ });
+ $('.navbar-toggle').on('click', function() {
+ $('.header-content .title').toggle();
+ $('.header-content .header-logo').toggle();
+ $('.header-content .navbar-collapse').toggle();
+ return $('.navbar-toggle').toggleClass('active');
+ });
+ $body.on("click", ".js-toggle-diff-comments", function(e) {
+ $(this).toggleClass('active');
+ $(this).closest(".diff-file").find(".notes_holder").toggle();
+ return e.preventDefault();
+ });
+ $document.off("click", '.js-confirm-danger');
+ $document.on("click", '.js-confirm-danger', function(e) {
+ var btn, form, text;
+ e.preventDefault();
+ btn = $(e.target);
+ text = btn.data("confirm-danger-message");
+ form = btn.closest("form");
+ return new ConfirmDangerModal(form, text);
+ });
+ $document.on('click', 'button', function() {
+ return $(this).blur();
+ });
+ $('input[type="search"]').each(function() {
+ var $this;
+ $this = $(this);
+ $this.attr('value', $this.val());
+ });
+ $document.off('keyup', 'input[type="search"]').on('keyup', 'input[type="search"]', function(e) {
+ var $this;
+ $this = $(this);
+ return $this.attr('value', $this.val());
+ });
+ $sidebarGutterToggle = $('.js-sidebar-toggle');
+ $document.off('breakpoint:change').on('breakpoint:change', function(e, breakpoint) {
+ var $gutterIcon;
+ if (breakpoint === 'sm' || breakpoint === 'xs') {
+ $gutterIcon = $sidebarGutterToggle.find('i');
+ if ($gutterIcon.hasClass('fa-angle-double-right')) {
+ return $sidebarGutterToggle.trigger('click');
+ }
+ }
+ });
+ fitSidebarForSize = function() {
+ var oldBootstrapBreakpoint;
+ oldBootstrapBreakpoint = bootstrapBreakpoint;
+ bootstrapBreakpoint = bp.getBreakpointSize();
+ if (bootstrapBreakpoint !== oldBootstrapBreakpoint) {
+ return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
+ }
+ };
+ checkInitialSidebarSize = function() {
+ bootstrapBreakpoint = bp.getBreakpointSize();
+ if (bootstrapBreakpoint === "xs" || "sm") {
+ return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
+ }
+ };
+ $window.off("resize.app").on("resize.app", function(e) {
+ return fitSidebarForSize();
+ });
+ gl.awardsHandler = new AwardsHandler();
+ checkInitialSidebarSize();
+ new Aside();
+ if ($window.width() < 1024 && $.cookie('pin_nav') === 'true') {
+ $.cookie('pin_nav', 'false', {
+ path: '/',
+ expires: 365 * 10
+ });
+ $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned');
+ $('.navbar-fixed-top').removeClass('header-pinned-nav');
+ }
+ return $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) {
+ var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText;
+ e.preventDefault();
+ $pinBtn = $(e.currentTarget);
+ $page = $('.page-with-sidebar');
+ $topNav = $('.navbar-fixed-top');
+ $tooltip = $("#" + ($pinBtn.attr('aria-describedby')));
+ doPinNav = !$page.is('.page-sidebar-pinned');
+ tooltipText = 'Pin navigation';
+ $(this).toggleClass('is-active');
+ if (doPinNav) {
+ $page.addClass('page-sidebar-pinned');
+ $topNav.addClass('header-pinned-nav');
+ } else {
+ $tooltip.remove();
+ $page.removeClass('page-sidebar-pinned').toggleClass('page-sidebar-collapsed page-sidebar-expanded');
+ $topNav.removeClass('header-pinned-nav').toggleClass('header-collapsed header-expanded');
+ }
+ $.cookie('pin_nav', doPinNav, {
+ path: '/',
+ expires: 365 * 10
+ });
+ if ($.cookie('pin_nav') === 'true' || doPinNav) {
+ tooltipText = 'Unpin navigation';
+ }
+ $tooltip.find('.tooltip-inner').text(tooltipText);
+ return $pinBtn.attr('title', tooltipText).tooltip('fixTitle');
+ });
+});
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
deleted file mode 100644
index eceff6d91d5..00000000000
--- a/app/assets/javascripts/application.js.coffee
+++ /dev/null
@@ -1,310 +0,0 @@
-# This is a manifest file that'll be compiled into including all the files listed below.
-# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
-# be included in the compiled file accessible from http://example.com/assets/application.js
-# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-# the compiled file.
-#
-#= require jquery2
-#= require jquery-ui/autocomplete
-#= require jquery-ui/datepicker
-#= require jquery-ui/draggable
-#= require jquery-ui/effect-highlight
-#= require jquery-ui/sortable
-#= require jquery_ujs
-#= require jquery.cookie
-#= require jquery.endless-scroll
-#= require jquery.highlight
-#= require jquery.waitforimages
-#= require jquery.atwho
-#= require jquery.scrollTo
-#= require jquery.turbolinks
-#= require turbolinks
-#= require autosave
-#= require bootstrap/affix
-#= require bootstrap/alert
-#= require bootstrap/button
-#= require bootstrap/collapse
-#= require bootstrap/dropdown
-#= require bootstrap/modal
-#= require bootstrap/scrollspy
-#= require bootstrap/tab
-#= require bootstrap/transition
-#= require bootstrap/tooltip
-#= require bootstrap/popover
-#= require select2
-#= require ace/ace
-#= require ace/ext-searchbox
-#= require underscore
-#= require dropzone
-#= require mousetrap
-#= require mousetrap/pause
-#= require shortcuts
-#= require shortcuts_navigation
-#= require shortcuts_dashboard_navigation
-#= require shortcuts_issuable
-#= require shortcuts_network
-#= require jquery.nicescroll
-#= require date.format
-#= require_directory ./behaviors
-#= require_directory ./blob
-#= require_directory ./commit
-#= require_directory ./extensions
-#= require_directory ./lib/utils
-#= require_directory ./u2f
-#= require_directory .
-#= require fuzzaldrin-plus
-
-window.slugify = (text) ->
- text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
-
-window.ajaxGet = (url) ->
- $.ajax({type: "GET", url: url, dataType: "script"})
-
-window.split = (val) ->
- return val.split( /,\s*/ )
-
-window.extractLast = (term) ->
- return split( term ).pop()
-
-window.rstrip = (val) ->
- return if val then val.replace(/\s+$/, '') else val
-
-# Disable button if text field is empty
-window.disableButtonIfEmptyField = (field_selector, button_selector) ->
- field = $(field_selector)
- closest_submit = field.closest('form').find(button_selector)
-
- closest_submit.disable() if rstrip(field.val()) is ""
-
- field.on 'input', ->
- if rstrip($(@).val()) is ""
- closest_submit.disable()
- else
- closest_submit.enable()
-
-# Disable button if any input field with given selector is empty
-window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) ->
- closest_submit = form.find(button_selector)
- updateButtons = ->
- filled = true
- form.find('input').filter(form_selector).each ->
- filled = rstrip($(this).val()) != "" || !$(this).attr('required')
-
- if filled
- closest_submit.enable()
- else
- closest_submit.disable()
-
- updateButtons()
- form.keyup(updateButtons)
-
-window.sanitize = (str) ->
- return str.replace(/<(?:.|\n)*?>/gm, '')
-
-window.unbindEvents = ->
- $(document).off('scroll')
-
-window.shiftWindow = ->
- scrollBy 0, -100
-
-document.addEventListener("page:fetch", unbindEvents)
-
-window.addEventListener "hashchange", shiftWindow
-
-window.onload = ->
- # Scroll the window to avoid the topnav bar
- # https://github.com/twitter/bootstrap/issues/1768
- if location.hash
- setTimeout shiftWindow, 100
-
-$ ->
-
- $document = $(document)
- $window = $(window)
- $body = $('body')
-
- gl.utils.preventDisabledButtons()
- bootstrapBreakpoint = bp.getBreakpointSize()
-
- $(".nav-sidebar").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
-
- # Click a .js-select-on-focus field, select the contents
- $(".js-select-on-focus").on "focusin", ->
- # Prevent a mouseup event from deselecting the input
- $(this).select().one 'mouseup', (e) ->
- e.preventDefault()
-
- $('.remove-row').bind 'ajax:success', ->
- $(this).closest('li').fadeOut()
-
- $('.js-remove-tr').bind 'ajax:before', ->
- $(this).hide()
-
- $('.js-remove-tr').bind 'ajax:success', ->
- $(this).closest('tr').fadeOut()
-
- # Initialize select2 selects
- $('select.select2').select2(width: 'resolve', dropdownAutoWidth: true)
-
- # Close select2 on escape
- $('.js-select2').bind 'select2-close', ->
- setTimeout ( ->
- $('.select2-container-active').removeClass('select2-container-active')
- $(':focus').blur()
- ), 1
-
- # Initialize tooltips
- $body.tooltip(
- selector: '.has-tooltip, [data-toggle="tooltip"]'
- placement: (_, el) ->
- $el = $(el)
- $el.data('placement') || 'bottom'
- )
-
- # Form submitter
- $('.trigger-submit').on 'change', ->
- $(@).parents('form').submit()
-
- gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true)
-
- # Flash
- if (flash = $(".flash-container")).length > 0
- flash.click -> $(@).fadeOut()
- flash.show()
-
- # Disable form buttons while a form is submitting
- $body.on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
- buttons = $('[type="submit"]', @)
-
- switch e.type
- when 'ajax:beforeSend', 'submit'
- buttons.disable()
- else
- buttons.enable()
-
- $(document).ajaxError (e, xhrObj, xhrSetting, xhrErrorText) ->
-
- if xhrObj.status is 401
- new Flash 'You need to be logged in.', 'alert'
-
- else if xhrObj.status in [ 404, 500 ]
- new Flash 'Something went wrong on our end.', 'alert'
-
-
- # Show/Hide the profile menu when hovering the account box
- $('.account-box').hover -> $(@).toggleClass('hover')
-
- # Commit show suppressed diff
- $document.on 'click', '.diff-content .js-show-suppressed-diff', ->
- $container = $(@).parent()
- $container.next('table').show()
- $container.remove()
-
- $('.navbar-toggle').on 'click', ->
- $('.header-content .title').toggle()
- $('.header-content .header-logo').toggle()
- $('.header-content .navbar-collapse').toggle()
- $('.navbar-toggle').toggleClass('active')
-
- # Show/hide comments on diff
- $body.on "click", ".js-toggle-diff-comments", (e) ->
- $(@).toggleClass('active')
- $(@).closest(".diff-file").find(".notes_holder").toggle()
- e.preventDefault()
-
- $document.off "click", '.js-confirm-danger'
- $document.on "click", '.js-confirm-danger', (e) ->
- e.preventDefault()
- btn = $(e.target)
- text = btn.data("confirm-danger-message")
- form = btn.closest("form")
- new ConfirmDangerModal(form, text)
-
-
- $document.on 'click', 'button', ->
- $(this).blur()
-
- $('input[type="search"]').each ->
- $this = $(this)
- $this.attr 'value', $this.val()
- return
-
- $document
- .off 'keyup', 'input[type="search"]'
- .on 'keyup', 'input[type="search"]' , (e) ->
- $this = $(this)
- $this.attr 'value', $this.val()
-
- $sidebarGutterToggle = $('.js-sidebar-toggle')
-
- $document
- .off 'breakpoint:change'
- .on 'breakpoint:change', (e, breakpoint) ->
- if breakpoint is 'sm' or breakpoint is 'xs'
- $gutterIcon = $sidebarGutterToggle.find('i')
- if $gutterIcon.hasClass('fa-angle-double-right')
- $sidebarGutterToggle.trigger('click')
-
- fitSidebarForSize = ->
- oldBootstrapBreakpoint = bootstrapBreakpoint
- bootstrapBreakpoint = bp.getBreakpointSize()
- if bootstrapBreakpoint != oldBootstrapBreakpoint
- $document.trigger('breakpoint:change', [bootstrapBreakpoint])
-
- checkInitialSidebarSize = ->
- bootstrapBreakpoint = bp.getBreakpointSize()
- if bootstrapBreakpoint is "xs" or "sm"
- $document.trigger('breakpoint:change', [bootstrapBreakpoint])
-
- $window
- .off "resize.app"
- .on "resize.app", (e) ->
- fitSidebarForSize()
-
- gl.awardsHandler = new AwardsHandler()
- checkInitialSidebarSize()
- new Aside()
-
- # Sidenav pinning
- if $window.width() < 1024 and $.cookie('pin_nav') is 'true'
- $.cookie('pin_nav', 'false', { path: '/', expires: 365 * 10 })
- $('.page-with-sidebar')
- .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
- .removeClass('page-sidebar-pinned')
- $('.navbar-fixed-top').removeClass('header-pinned-nav')
-
- $document
- .off 'click', '.js-nav-pin'
- .on 'click', '.js-nav-pin', (e) ->
- e.preventDefault()
-
- $pinBtn = $(e.currentTarget)
- $page = $ '.page-with-sidebar'
- $topNav = $ '.navbar-fixed-top'
- $tooltip = $ "##{$pinBtn.attr('aria-describedby')}"
- doPinNav = not $page.is('.page-sidebar-pinned')
- tooltipText = 'Pin navigation'
-
- $(this).toggleClass 'is-active'
-
- if doPinNav
- $page.addClass('page-sidebar-pinned')
- $topNav.addClass('header-pinned-nav')
- else
- $tooltip.remove() # Remove it immediately when collapsing the sidebar
- $page.removeClass('page-sidebar-pinned')
- .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
- $topNav.removeClass('header-pinned-nav')
- .toggleClass('header-collapsed header-expanded')
-
- # Save settings
- $.cookie 'pin_nav', doPinNav, { path: '/', expires: 365 * 10 }
-
- if $.cookie('pin_nav') is 'true' or doPinNav
- tooltipText = 'Unpin navigation'
-
- # Update tooltip text immediately
- $tooltip.find('.tooltip-inner').text(tooltipText)
-
- # Persist tooltip title
- $pinBtn.attr('title', tooltipText).tooltip('fixTitle')
diff --git a/app/assets/javascripts/aside.js b/app/assets/javascripts/aside.js
new file mode 100644
index 00000000000..67148651916
--- /dev/null
+++ b/app/assets/javascripts/aside.js
@@ -0,0 +1,23 @@
+this.Aside = (function() {
+ function Aside() {
+ $(document).off("click", "a.show-aside");
+ $(document).on("click", 'a.show-aside', function(e) {
+ var btn, icon;
+ e.preventDefault();
+ btn = $(e.currentTarget);
+ icon = btn.find('i');
+ if (icon.hasClass('fa-angle-left')) {
+ btn.parent().find('section').hide();
+ btn.parent().find('aside').fadeIn();
+ return icon.removeClass('fa-angle-left').addClass('fa-angle-right');
+ } else {
+ btn.parent().find('aside').hide();
+ btn.parent().find('section').fadeIn();
+ return icon.removeClass('fa-angle-right').addClass('fa-angle-left');
+ }
+ });
+ }
+
+ return Aside;
+
+})();
diff --git a/app/assets/javascripts/aside.js.coffee b/app/assets/javascripts/aside.js.coffee
deleted file mode 100644
index 66ab5054326..00000000000
--- a/app/assets/javascripts/aside.js.coffee
+++ /dev/null
@@ -1,16 +0,0 @@
-class @Aside
- constructor: ->
- $(document).off "click", "a.show-aside"
- $(document).on "click", 'a.show-aside', (e) ->
- e.preventDefault()
- btn = $(e.currentTarget)
- icon = btn.find('i')
-
- if icon.hasClass('fa-angle-left')
- btn.parent().find('section').hide()
- btn.parent().find('aside').fadeIn()
- icon.removeClass('fa-angle-left').addClass('fa-angle-right')
- else
- btn.parent().find('aside').hide()
- btn.parent().find('section').fadeIn()
- icon.removeClass('fa-angle-right').addClass('fa-angle-left')
diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js
new file mode 100644
index 00000000000..cf000f1cef9
--- /dev/null
+++ b/app/assets/javascripts/autosave.js
@@ -0,0 +1,60 @@
+this.Autosave = (function() {
+ function Autosave(field, key) {
+ this.field = field;
+ if (key.join != null) {
+ key = key.join("/");
+ }
+ this.key = "autosave/" + key;
+ this.field.data("autosave", this);
+ this.restore();
+ this.field.on("input", (function(_this) {
+ return function() {
+ return _this.save();
+ };
+ })(this));
+ }
+
+ Autosave.prototype.restore = function() {
+ var e, error, text;
+ if (window.localStorage == null) {
+ return;
+ }
+ try {
+ text = window.localStorage.getItem(this.key);
+ } catch (error) {
+ e = error;
+ return;
+ }
+ if ((text != null ? text.length : void 0) > 0) {
+ this.field.val(text);
+ }
+ return this.field.trigger("input");
+ };
+
+ Autosave.prototype.save = function() {
+ var text;
+ if (window.localStorage == null) {
+ return;
+ }
+ text = this.field.val();
+ if ((text != null ? text.length : void 0) > 0) {
+ try {
+ return window.localStorage.setItem(this.key, text);
+ } catch (undefined) {}
+ } else {
+ return this.reset();
+ }
+ };
+
+ Autosave.prototype.reset = function() {
+ if (window.localStorage == null) {
+ return;
+ }
+ try {
+ return window.localStorage.removeItem(this.key);
+ } catch (undefined) {}
+ };
+
+ return Autosave;
+
+})();
diff --git a/app/assets/javascripts/autosave.js.coffee b/app/assets/javascripts/autosave.js.coffee
deleted file mode 100644
index 28f8e103664..00000000000
--- a/app/assets/javascripts/autosave.js.coffee
+++ /dev/null
@@ -1,39 +0,0 @@
-class @Autosave
- constructor: (field, key) ->
- @field = field
-
- key = key.join("/") if key.join?
- @key = "autosave/#{key}"
-
- @field.data "autosave", this
-
- @restore()
-
- @field.on "input", => @save()
-
- restore: ->
- return unless window.localStorage?
-
- try
- text = window.localStorage.getItem @key
- catch e
- return
-
- @field.val text if text?.length > 0
- @field.trigger "input"
-
- save: ->
- return unless window.localStorage?
-
- text = @field.val()
- if text?.length > 0
- try
- window.localStorage.setItem @key, text
- else
- @reset()
-
- reset: ->
- return unless window.localStorage?
-
- try
- window.localStorage.removeItem @key
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
deleted file mode 100644
index 37d0adaa625..00000000000
--- a/app/assets/javascripts/awards_handler.coffee
+++ /dev/null
@@ -1,372 +0,0 @@
-class @AwardsHandler
-
- constructor: ->
-
- @aliases = gl.emojiAliases()
-
- $(document)
- .off 'click', '.js-add-award'
- .on 'click', '.js-add-award', (e) =>
- e.stopPropagation()
- e.preventDefault()
-
- @showEmojiMenu $(e.currentTarget)
-
- $('html').on 'click', (e) ->
- $target = $ e.target
-
- unless $target.closest('.emoji-menu-content').length
- $('.js-awards-block.current').removeClass 'current'
-
- unless $target.closest('.emoji-menu').length
- if $('.emoji-menu').is(':visible')
- $('.js-add-award.is-active').removeClass 'is-active'
- $('.emoji-menu').removeClass 'is-visible'
-
- $(document)
- .off 'click', '.js-emoji-btn'
- .on 'click', '.js-emoji-btn', (e) =>
- e.preventDefault()
-
- $target = $ e.currentTarget
- emoji = $target.find('.icon').data 'emoji'
-
- $target.closest('.js-awards-block').addClass 'current'
- @addAward @getVotesBlock(), @getAwardUrl(), emoji
-
-
- showEmojiMenu: ($addBtn) ->
-
- $menu = $ '.emoji-menu'
-
- if $addBtn.hasClass 'js-note-emoji'
- $addBtn.closest('.note').find('.js-awards-block').addClass 'current'
- else
- $addBtn.closest('.js-awards-block').addClass 'current'
-
- if $menu.length
- $holder = $addBtn.closest('.js-award-holder')
-
- if $menu.is '.is-visible'
- $addBtn.removeClass 'is-active'
- $menu.removeClass 'is-visible'
- $('#emoji_search').blur()
- else
- $addBtn.addClass 'is-active'
- @positionMenu($menu, $addBtn)
-
- $menu.addClass 'is-visible'
- $('#emoji_search').focus()
- else
- $addBtn.addClass 'is-loading is-active'
- url = @getAwardMenuUrl()
-
- @createEmojiMenu url, =>
- $addBtn.removeClass 'is-loading'
- $menu = $('.emoji-menu')
- @positionMenu($menu, $addBtn)
- @renderFrequentlyUsedBlock() unless @frequentEmojiBlockRendered
-
- setTimeout =>
- $menu.addClass 'is-visible'
- $('#emoji_search').focus()
- @setupSearch()
- , 200
-
-
- createEmojiMenu: (awardMenuUrl, callback) ->
-
- $.get awardMenuUrl, (response) ->
- $('body').append response
- callback()
-
-
- positionMenu: ($menu, $addBtn) ->
-
- position = $addBtn.data('position')
-
- # The menu could potentially be off-screen or in a hidden overflow element
- # So we position the element absolute in the body
- css =
- top: "#{$addBtn.offset().top + $addBtn.outerHeight()}px"
-
- if position? and position is 'right'
- css.left = "#{($addBtn.offset().left - $menu.outerWidth()) + 20}px"
- $menu.addClass 'is-aligned-right'
- else
- css.left = "#{$addBtn.offset().left}px"
- $menu.removeClass 'is-aligned-right'
-
- $menu.css(css)
-
-
- addAward: (votesBlock, awardUrl, emoji, checkMutuality = true, callback) ->
-
- emoji = @normilizeEmojiName emoji
-
- @postEmoji awardUrl, emoji, =>
- @addAwardToEmojiBar votesBlock, emoji, checkMutuality
- callback?()
-
- $('.emoji-menu').removeClass 'is-visible'
-
-
- addAwardToEmojiBar: (votesBlock, emoji, checkForMutuality = true) ->
-
- @checkMutuality votesBlock, emoji if checkForMutuality
- @addEmojiToFrequentlyUsedList emoji
-
- emoji = @normilizeEmojiName emoji
- $emojiButton = @findEmojiIcon(votesBlock, emoji).parent()
-
- if $emojiButton.length > 0
- if @isActive $emojiButton
- @decrementCounter $emojiButton, emoji
- else
- counter = $emojiButton.find '.js-counter'
- counter.text parseInt(counter.text()) + 1
- $emojiButton.addClass 'active'
- @addMeToUserList votesBlock, emoji
- @animateEmoji $emojiButton
- else
- votesBlock.removeClass 'hidden'
- @createEmoji votesBlock, emoji
-
-
- getVotesBlock: ->
-
- currentBlock = $ '.js-awards-block.current'
- return if currentBlock.length then currentBlock else $('.js-awards-block').eq 0
-
-
- getAwardUrl: -> return @getVotesBlock().data 'award-url'
-
-
- checkMutuality: (votesBlock, emoji) ->
-
- awardUrl = @getAwardUrl()
-
- if emoji in [ 'thumbsup', 'thumbsdown' ]
- mutualVote = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup'
- $emojiButton = votesBlock.find("[data-emoji=#{mutualVote}]").parent()
- isAlreadyVoted = $emojiButton.hasClass 'active'
-
- if isAlreadyVoted
- @showEmojiLoader $emojiButton
- @addAward votesBlock, awardUrl, mutualVote, false, ->
- $emojiButton.removeClass 'is-loading'
-
-
- showEmojiLoader: ($emojiButton) ->
-
- $loader = $emojiButton.find '.fa-spinner'
-
- unless $loader.length
- $emojiButton.append '<i class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading"></i>'
-
- $emojiButton.addClass 'is-loading'
-
-
- isActive: ($emojiButton) -> $emojiButton.hasClass 'active'
-
-
- decrementCounter: ($emojiButton, emoji) ->
-
- counter = $ '.js-counter', $emojiButton
- counterNumber = parseInt counter.text(), 10
-
- if counterNumber > 1
- counter.text counterNumber - 1
- @removeMeFromUserList $emojiButton, emoji
- else if emoji is 'thumbsup' or emoji is 'thumbsdown'
- $emojiButton.tooltip 'destroy'
- counter.text '0'
- @removeMeFromUserList $emojiButton, emoji
- @removeEmoji $emojiButton if $emojiButton.parents('.note').length
- else
- @removeEmoji $emojiButton
-
- $emojiButton.removeClass 'active'
-
-
- removeEmoji: ($emojiButton) ->
-
- $emojiButton.tooltip('destroy')
- $emojiButton.remove()
-
- $votesBlock = @getVotesBlock()
-
- if $votesBlock.find('.js-emoji-btn').length is 0
- $votesBlock.addClass 'hidden'
-
-
- getAwardTooltip: ($awardBlock) ->
-
- return $awardBlock.attr('data-original-title') or $awardBlock.attr('data-title') or ''
-
-
- removeMeFromUserList: ($emojiButton, emoji) ->
-
- awardBlock = $emojiButton
- originalTitle = @getAwardTooltip awardBlock
-
- authors = originalTitle.split ', '
- authors.splice authors.indexOf('me'), 1
-
- newAuthors = authors.join ', '
-
- awardBlock
- .closest '.js-emoji-btn'
- .removeData 'original-title'
- .attr 'data-original-title', newAuthors
-
- @resetTooltip awardBlock
-
-
- addMeToUserList: (votesBlock, emoji) ->
-
- awardBlock = @findEmojiIcon(votesBlock, emoji).parent()
- origTitle = @getAwardTooltip awardBlock
- users = []
-
- if origTitle
- users = origTitle.trim().split ', '
-
- users.push 'me'
- awardBlock.attr 'title', users.join ', '
-
- @resetTooltip awardBlock
-
-
- resetTooltip: (award) ->
-
- award.tooltip 'destroy'
-
- # 'destroy' call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
- cb = -> award.tooltip()
- setTimeout cb, 200
-
-
- createEmoji_: (votesBlock, emoji) ->
-
- emojiCssClass = @resolveNameToCssClass emoji
- buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'>
- <div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>
- <span class='award-control-text js-counter'>1</span>
- </button>"
-
- $emojiButton = $ buttonHtml
- $emojiButton
- .insertBefore votesBlock.find '.js-award-holder'
- .find '.emoji-icon'
- .data 'emoji', emoji
-
- @animateEmoji $emojiButton
- $('.award-control').tooltip()
- votesBlock.removeClass 'current'
-
-
- animateEmoji: ($emoji) ->
-
- className = 'pulse animated'
-
- $emoji.addClass className
- setTimeout (-> $emoji.removeClass className), 321
-
-
- createEmoji: (votesBlock, emoji) ->
-
- if $('.emoji-menu').length
- return @createEmoji_ votesBlock, emoji
-
- @createEmojiMenu @getAwardMenuUrl(), => @createEmoji_ votesBlock, emoji
-
-
- getAwardMenuUrl: -> return gon.award_menu_url
-
-
- resolveNameToCssClass: (emoji) ->
-
- emojiIcon = $ ".emoji-menu-content [data-emoji='#{emoji}']"
-
- if emojiIcon.length > 0
- unicodeName = emojiIcon.data 'unicode-name'
- else
- # Find by alias
- unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data 'unicode-name'
-
- return "emoji-#{unicodeName}"
-
-
- postEmoji: (awardUrl, emoji, callback) ->
-
- $.post awardUrl, { name: emoji }, (data) ->
- callback() if data.ok
-
-
- findEmojiIcon: (votesBlock, emoji) ->
-
- return votesBlock.find ".js-emoji-btn [data-emoji='#{emoji}']"
-
-
- scrollToAwards: ->
-
- options = scrollTop: $('.awards').offset().top - 110
- $('body, html').animate options, 200
-
-
- normilizeEmojiName: (emoji) -> return @aliases[emoji] or emoji
-
-
- addEmojiToFrequentlyUsedList: (emoji) ->
-
- frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
- frequentlyUsedEmojis.push emoji
- $.cookie 'frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }
-
-
- getFrequentlyUsedEmojis: ->
-
- frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') or '').split(',')
- return _.compact _.uniq frequentlyUsedEmojis
-
-
- renderFrequentlyUsedBlock: ->
-
- if $.cookie 'frequently_used_emojis'
- frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
-
- ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>")
-
- for emoji in frequentlyUsedEmojis
- $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
-
- $('.emoji-menu-content')
- .prepend(ul)
- .prepend($('<h5>').text('Frequently used'))
-
- @frequentEmojiBlockRendered = true
-
-
- setupSearch: ->
-
- $('input.emoji-search').on 'keyup', (ev) =>
- term = $(ev.target).val()
-
- # Clean previous search results
- $('ul.emoji-menu-search, h5.emoji-search').remove()
-
- if term
- # Generate a search result block
- h5 = $('<h5>').text('Search results')
- found_emojis = @searchEmojis(term).show()
- ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis)
- $('.emoji-menu-content ul, .emoji-menu-content h5').hide()
- $('.emoji-menu-content').append(h5).append(ul)
- else
- $('.emoji-menu-content').children().show()
-
-
- searchEmojis: (term) ->
-
- $(".emoji-menu-list:not(.frequent-emojis) [data-emoji*='#{term}']").closest('li').clone()
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
new file mode 100644
index 00000000000..4a61556745e
--- /dev/null
+++ b/app/assets/javascripts/awards_handler.js
@@ -0,0 +1,377 @@
+this.AwardsHandler = (function() {
+ function AwardsHandler() {
+ this.aliases = gl.emojiAliases();
+ $(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) {
+ return function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ return _this.showEmojiMenu($(e.currentTarget));
+ };
+ })(this));
+ $('html').on('click', function(e) {
+ var $target;
+ $target = $(e.target);
+ if (!$target.closest('.emoji-menu-content').length) {
+ $('.js-awards-block.current').removeClass('current');
+ }
+ if (!$target.closest('.emoji-menu').length) {
+ if ($('.emoji-menu').is(':visible')) {
+ $('.js-add-award.is-active').removeClass('is-active');
+ return $('.emoji-menu').removeClass('is-visible');
+ }
+ }
+ });
+ $(document).off('click', '.js-emoji-btn').on('click', '.js-emoji-btn', (function(_this) {
+ return function(e) {
+ var $target, emoji;
+ e.preventDefault();
+ $target = $(e.currentTarget);
+ emoji = $target.find('.icon').data('emoji');
+ $target.closest('.js-awards-block').addClass('current');
+ return _this.addAward(_this.getVotesBlock(), _this.getAwardUrl(), emoji);
+ };
+ })(this));
+ }
+
+ AwardsHandler.prototype.showEmojiMenu = function($addBtn) {
+ var $holder, $menu, url;
+ $menu = $('.emoji-menu');
+ if ($addBtn.hasClass('js-note-emoji')) {
+ $addBtn.closest('.note').find('.js-awards-block').addClass('current');
+ } else {
+ $addBtn.closest('.js-awards-block').addClass('current');
+ }
+ if ($menu.length) {
+ $holder = $addBtn.closest('.js-award-holder');
+ if ($menu.is('.is-visible')) {
+ $addBtn.removeClass('is-active');
+ $menu.removeClass('is-visible');
+ return $('#emoji_search').blur();
+ } else {
+ $addBtn.addClass('is-active');
+ this.positionMenu($menu, $addBtn);
+ $menu.addClass('is-visible');
+ return $('#emoji_search').focus();
+ }
+ } else {
+ $addBtn.addClass('is-loading is-active');
+ url = this.getAwardMenuUrl();
+ return this.createEmojiMenu(url, (function(_this) {
+ return function() {
+ $addBtn.removeClass('is-loading');
+ $menu = $('.emoji-menu');
+ _this.positionMenu($menu, $addBtn);
+ if (!_this.frequentEmojiBlockRendered) {
+ _this.renderFrequentlyUsedBlock();
+ }
+ return setTimeout(function() {
+ $menu.addClass('is-visible');
+ $('#emoji_search').focus();
+ return _this.setupSearch();
+ }, 200);
+ };
+ })(this));
+ }
+ };
+
+ AwardsHandler.prototype.createEmojiMenu = function(awardMenuUrl, callback) {
+ return $.get(awardMenuUrl, function(response) {
+ $('body').append(response);
+ return callback();
+ });
+ };
+
+ AwardsHandler.prototype.positionMenu = function($menu, $addBtn) {
+ var css, position;
+ position = $addBtn.data('position');
+ css = {
+ top: ($addBtn.offset().top + $addBtn.outerHeight()) + "px"
+ };
+ if ((position != null) && position === 'right') {
+ css.left = (($addBtn.offset().left - $menu.outerWidth()) + 20) + "px";
+ $menu.addClass('is-aligned-right');
+ } else {
+ css.left = ($addBtn.offset().left) + "px";
+ $menu.removeClass('is-aligned-right');
+ }
+ return $menu.css(css);
+ };
+
+ AwardsHandler.prototype.addAward = function(votesBlock, awardUrl, emoji, checkMutuality, callback) {
+ if (checkMutuality == null) {
+ checkMutuality = true;
+ }
+ emoji = this.normilizeEmojiName(emoji);
+ this.postEmoji(awardUrl, emoji, (function(_this) {
+ return function() {
+ _this.addAwardToEmojiBar(votesBlock, emoji, checkMutuality);
+ return typeof callback === "function" ? callback() : void 0;
+ };
+ })(this));
+ return $('.emoji-menu').removeClass('is-visible');
+ };
+
+ AwardsHandler.prototype.addAwardToEmojiBar = function(votesBlock, emoji, checkForMutuality) {
+ var $emojiButton, counter;
+ if (checkForMutuality == null) {
+ checkForMutuality = true;
+ }
+ if (checkForMutuality) {
+ this.checkMutuality(votesBlock, emoji);
+ }
+ this.addEmojiToFrequentlyUsedList(emoji);
+ emoji = this.normilizeEmojiName(emoji);
+ $emojiButton = this.findEmojiIcon(votesBlock, emoji).parent();
+ if ($emojiButton.length > 0) {
+ if (this.isActive($emojiButton)) {
+ return this.decrementCounter($emojiButton, emoji);
+ } else {
+ counter = $emojiButton.find('.js-counter');
+ counter.text(parseInt(counter.text()) + 1);
+ $emojiButton.addClass('active');
+ this.addMeToUserList(votesBlock, emoji);
+ return this.animateEmoji($emojiButton);
+ }
+ } else {
+ votesBlock.removeClass('hidden');
+ return this.createEmoji(votesBlock, emoji);
+ }
+ };
+
+ AwardsHandler.prototype.getVotesBlock = function() {
+ var currentBlock;
+ currentBlock = $('.js-awards-block.current');
+ if (currentBlock.length) {
+ return currentBlock;
+ } else {
+ return $('.js-awards-block').eq(0);
+ }
+ };
+
+ AwardsHandler.prototype.getAwardUrl = function() {
+ return this.getVotesBlock().data('award-url');
+ };
+
+ AwardsHandler.prototype.checkMutuality = function(votesBlock, emoji) {
+ var $emojiButton, awardUrl, isAlreadyVoted, mutualVote;
+ awardUrl = this.getAwardUrl();
+ if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
+ mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
+ $emojiButton = votesBlock.find("[data-emoji=" + mutualVote + "]").parent();
+ isAlreadyVoted = $emojiButton.hasClass('active');
+ if (isAlreadyVoted) {
+ this.showEmojiLoader($emojiButton);
+ return this.addAward(votesBlock, awardUrl, mutualVote, false, function() {
+ return $emojiButton.removeClass('is-loading');
+ });
+ }
+ }
+ };
+
+ AwardsHandler.prototype.showEmojiLoader = function($emojiButton) {
+ var $loader;
+ $loader = $emojiButton.find('.fa-spinner');
+ if (!$loader.length) {
+ $emojiButton.append('<i class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading"></i>');
+ }
+ return $emojiButton.addClass('is-loading');
+ };
+
+ AwardsHandler.prototype.isActive = function($emojiButton) {
+ return $emojiButton.hasClass('active');
+ };
+
+ AwardsHandler.prototype.decrementCounter = function($emojiButton, emoji) {
+ var counter, counterNumber;
+ counter = $('.js-counter', $emojiButton);
+ counterNumber = parseInt(counter.text(), 10);
+ if (counterNumber > 1) {
+ counter.text(counterNumber - 1);
+ this.removeMeFromUserList($emojiButton, emoji);
+ } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
+ $emojiButton.tooltip('destroy');
+ counter.text('0');
+ this.removeMeFromUserList($emojiButton, emoji);
+ if ($emojiButton.parents('.note').length) {
+ this.removeEmoji($emojiButton);
+ }
+ } else {
+ this.removeEmoji($emojiButton);
+ }
+ return $emojiButton.removeClass('active');
+ };
+
+ AwardsHandler.prototype.removeEmoji = function($emojiButton) {
+ var $votesBlock;
+ $emojiButton.tooltip('destroy');
+ $emojiButton.remove();
+ $votesBlock = this.getVotesBlock();
+ if ($votesBlock.find('.js-emoji-btn').length === 0) {
+ return $votesBlock.addClass('hidden');
+ }
+ };
+
+ AwardsHandler.prototype.getAwardTooltip = function($awardBlock) {
+ return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || '';
+ };
+
+ AwardsHandler.prototype.removeMeFromUserList = function($emojiButton, emoji) {
+ var authors, awardBlock, newAuthors, originalTitle;
+ awardBlock = $emojiButton;
+ originalTitle = this.getAwardTooltip(awardBlock);
+ authors = originalTitle.split(', ');
+ authors.splice(authors.indexOf('me'), 1);
+ newAuthors = authors.join(', ');
+ awardBlock.closest('.js-emoji-btn').removeData('original-title').attr('data-original-title', newAuthors);
+ return this.resetTooltip(awardBlock);
+ };
+
+ AwardsHandler.prototype.addMeToUserList = function(votesBlock, emoji) {
+ var awardBlock, origTitle, users;
+ awardBlock = this.findEmojiIcon(votesBlock, emoji).parent();
+ origTitle = this.getAwardTooltip(awardBlock);
+ users = [];
+ if (origTitle) {
+ users = origTitle.trim().split(', ');
+ }
+ users.push('me');
+ awardBlock.attr('title', users.join(', '));
+ return this.resetTooltip(awardBlock);
+ };
+
+ AwardsHandler.prototype.resetTooltip = function(award) {
+ var cb;
+ award.tooltip('destroy');
+ cb = function() {
+ return award.tooltip();
+ };
+ return setTimeout(cb, 200);
+ };
+
+ AwardsHandler.prototype.createEmoji_ = function(votesBlock, emoji) {
+ var $emojiButton, buttonHtml, emojiCssClass;
+ emojiCssClass = this.resolveNameToCssClass(emoji);
+ buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'> <div class='icon emoji-icon " + emojiCssClass + "' data-emoji='" + emoji + "'></div> <span class='award-control-text js-counter'>1</span> </button>";
+ $emojiButton = $(buttonHtml);
+ $emojiButton.insertBefore(votesBlock.find('.js-award-holder')).find('.emoji-icon').data('emoji', emoji);
+ this.animateEmoji($emojiButton);
+ $('.award-control').tooltip();
+ return votesBlock.removeClass('current');
+ };
+
+ AwardsHandler.prototype.animateEmoji = function($emoji) {
+ var className;
+ className = 'pulse animated';
+ $emoji.addClass(className);
+ return setTimeout((function() {
+ return $emoji.removeClass(className);
+ }), 321);
+ };
+
+ AwardsHandler.prototype.createEmoji = function(votesBlock, emoji) {
+ if ($('.emoji-menu').length) {
+ return this.createEmoji_(votesBlock, emoji);
+ }
+ return this.createEmojiMenu(this.getAwardMenuUrl(), (function(_this) {
+ return function() {
+ return _this.createEmoji_(votesBlock, emoji);
+ };
+ })(this));
+ };
+
+ AwardsHandler.prototype.getAwardMenuUrl = function() {
+ return gon.award_menu_url;
+ };
+
+ AwardsHandler.prototype.resolveNameToCssClass = function(emoji) {
+ var emojiIcon, unicodeName;
+ emojiIcon = $(".emoji-menu-content [data-emoji='" + emoji + "']");
+ if (emojiIcon.length > 0) {
+ unicodeName = emojiIcon.data('unicode-name');
+ } else {
+ unicodeName = $(".emoji-menu-content [data-aliases*=':" + emoji + ":']").data('unicode-name');
+ }
+ return "emoji-" + unicodeName;
+ };
+
+ AwardsHandler.prototype.postEmoji = function(awardUrl, emoji, callback) {
+ return $.post(awardUrl, {
+ name: emoji
+ }, function(data) {
+ if (data.ok) {
+ return callback();
+ }
+ });
+ };
+
+ AwardsHandler.prototype.findEmojiIcon = function(votesBlock, emoji) {
+ return votesBlock.find(".js-emoji-btn [data-emoji='" + emoji + "']");
+ };
+
+ AwardsHandler.prototype.scrollToAwards = function() {
+ var options;
+ options = {
+ scrollTop: $('.awards').offset().top - 110
+ };
+ return $('body, html').animate(options, 200);
+ };
+
+ AwardsHandler.prototype.normilizeEmojiName = function(emoji) {
+ return this.aliases[emoji] || emoji;
+ };
+
+ AwardsHandler.prototype.addEmojiToFrequentlyUsedList = function(emoji) {
+ var frequentlyUsedEmojis;
+ frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
+ frequentlyUsedEmojis.push(emoji);
+ return $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), {
+ expires: 365
+ });
+ };
+
+ AwardsHandler.prototype.getFrequentlyUsedEmojis = function() {
+ var frequentlyUsedEmojis;
+ frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',');
+ return _.compact(_.uniq(frequentlyUsedEmojis));
+ };
+
+ AwardsHandler.prototype.renderFrequentlyUsedBlock = function() {
+ var emoji, frequentlyUsedEmojis, i, len, ul;
+ if ($.cookie('frequently_used_emojis')) {
+ frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
+ ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>");
+ for (i = 0, len = frequentlyUsedEmojis.length; i < len; i++) {
+ emoji = frequentlyUsedEmojis[i];
+ $(".emoji-menu-content [data-emoji='" + emoji + "']").closest('li').clone().appendTo(ul);
+ }
+ $('.emoji-menu-content').prepend(ul).prepend($('<h5>').text('Frequently used'));
+ }
+ return this.frequentEmojiBlockRendered = true;
+ };
+
+ AwardsHandler.prototype.setupSearch = function() {
+ return $('input.emoji-search').on('keyup', (function(_this) {
+ return function(ev) {
+ var found_emojis, h5, term, ul;
+ term = $(ev.target).val();
+ $('ul.emoji-menu-search, h5.emoji-search').remove();
+ if (term) {
+ h5 = $('<h5>').text('Search results');
+ found_emojis = _this.searchEmojis(term).show();
+ ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis);
+ $('.emoji-menu-content ul, .emoji-menu-content h5').hide();
+ return $('.emoji-menu-content').append(h5).append(ul);
+ } else {
+ return $('.emoji-menu-content').children().show();
+ }
+ };
+ })(this));
+ };
+
+ AwardsHandler.prototype.searchEmojis = function(term) {
+ return $(".emoji-menu-list:not(.frequent-emojis) [data-emoji*='" + term + "']").closest('li').clone();
+ };
+
+ return AwardsHandler;
+
+})();
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js
new file mode 100644
index 00000000000..f1b1336ee89
--- /dev/null
+++ b/app/assets/javascripts/behaviors/autosize.js
@@ -0,0 +1,25 @@
+
+/*= require jquery.ba-resize */
+
+/*= require autosize */
+$(function() {
+ var $fields;
+ $fields = $('.js-autosize');
+ $fields.on('autosize:resized', function() {
+ var $field;
+ $field = $(this);
+ return $field.data('height', $field.outerHeight());
+ });
+ $fields.on('resize.autosize', function() {
+ var $field;
+ $field = $(this);
+ if ($field.data('height') !== $field.outerHeight()) {
+ $field.data('height', $field.outerHeight());
+ autosize.destroy($field);
+ return $field.css('max-height', window.outerHeight);
+ }
+ });
+ autosize($fields);
+ autosize.update($fields);
+ return $fields.css('resize', 'vertical');
+});
diff --git a/app/assets/javascripts/behaviors/autosize.js.coffee b/app/assets/javascripts/behaviors/autosize.js.coffee
deleted file mode 100644
index a072fe48a98..00000000000
--- a/app/assets/javascripts/behaviors/autosize.js.coffee
+++ /dev/null
@@ -1,22 +0,0 @@
-#= require jquery.ba-resize
-#= require autosize
-
-$ ->
- $fields = $('.js-autosize')
-
- $fields.on 'autosize:resized', ->
- $field = $(@)
- $field.data('height', $field.outerHeight())
-
- $fields.on 'resize.autosize', ->
- $field = $(@)
-
- if $field.data('height') != $field.outerHeight()
- $field.data('height', $field.outerHeight())
- autosize.destroy($field)
- $field.css('max-height', window.outerHeight)
-
- autosize($fields)
- autosize.update($fields)
-
- $fields.css('resize', 'vertical')
diff --git a/app/assets/javascripts/behaviors/details_behavior.coffee b/app/assets/javascripts/behaviors/details_behavior.coffee
deleted file mode 100644
index decab3e1bed..00000000000
--- a/app/assets/javascripts/behaviors/details_behavior.coffee
+++ /dev/null
@@ -1,15 +0,0 @@
-$ ->
- $("body").on "click", ".js-details-target", ->
- container = $(@).closest(".js-details-container")
- container.toggleClass("open")
-
- # Show details content. Hides link after click.
- #
- # %div
- # %a.js-details-expand
- # %div.js-details-content
- #
- $("body").on "click", ".js-details-expand", (e) ->
- $(@).next('.js-details-content').removeClass("hide")
- $(@).hide()
- e.preventDefault()
diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js
new file mode 100644
index 00000000000..0df43dce550
--- /dev/null
+++ b/app/assets/javascripts/behaviors/details_behavior.js
@@ -0,0 +1,12 @@
+$(function() {
+ $("body").on("click", ".js-details-target", function() {
+ var container;
+ container = $(this).closest(".js-details-container");
+ return container.toggleClass("open");
+ });
+ return $("body").on("click", ".js-details-expand", function(e) {
+ $(this).next('.js-details-content').removeClass("hide");
+ $(this).hide();
+ return e.preventDefault();
+ });
+});
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
new file mode 100644
index 00000000000..ca83bf43d05
--- /dev/null
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -0,0 +1,54 @@
+
+/*= require extensions/jquery */
+var isMac, keyCodeIs;
+
+isMac = function() {
+ return navigator.userAgent.match(/Macintosh/);
+};
+
+keyCodeIs = function(e, keyCode) {
+ if ((e.originalEvent && e.originalEvent.repeat) || e.repeat) {
+ return false;
+ }
+ return e.keyCode === keyCode;
+};
+
+$(document).on('keydown.quick_submit', '.js-quick-submit', function(e) {
+ var $form, $submit_button;
+ if (!keyCodeIs(e, 13)) {
+ return;
+ }
+ if (!((e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey))) {
+ return;
+ }
+ e.preventDefault();
+ $form = $(e.target).closest('form');
+ $submit_button = $form.find('input[type=submit], button[type=submit]');
+ if ($submit_button.attr('disabled')) {
+ return;
+ }
+ $submit_button.disable();
+ return $form.submit();
+});
+
+$(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function(e) {
+ var $this, title;
+ if (!keyCodeIs(e, 9)) {
+ return;
+ }
+ if (isMac()) {
+ title = "You can also press &#8984;-Enter";
+ } else {
+ title = "You can also press Ctrl-Enter";
+ }
+ $this = $(this);
+ return $this.tooltip({
+ container: 'body',
+ html: 'true',
+ placement: 'auto top',
+ title: title,
+ trigger: 'manual'
+ }).tooltip('show').one('blur', function() {
+ return $this.tooltip('hide');
+ });
+});
diff --git a/app/assets/javascripts/behaviors/quick_submit.js.coffee b/app/assets/javascripts/behaviors/quick_submit.js.coffee
deleted file mode 100644
index 3cb96bacaa7..00000000000
--- a/app/assets/javascripts/behaviors/quick_submit.js.coffee
+++ /dev/null
@@ -1,56 +0,0 @@
-# Quick Submit behavior
-#
-# When a child field of a form with a `js-quick-submit` class receives a
-# "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
-# is submitted.
-#
-#= require extensions/jquery
-#
-# ### Example Markup
-#
-# <form action="/foo" class="js-quick-submit">
-# <input type="text" />
-# <textarea></textarea>
-# <input type="submit" value="Submit" />
-# </form>
-#
-isMac = ->
- navigator.userAgent.match(/Macintosh/)
-
-keyCodeIs = (e, keyCode) ->
- return false if (e.originalEvent && e.originalEvent.repeat) || e.repeat
- return e.keyCode == keyCode
-
-$(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
- return unless keyCodeIs(e, 13) # Enter
-
- return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
-
- e.preventDefault()
-
- $form = $(e.target).closest('form')
- $submit_button = $form.find('input[type=submit], button[type=submit]')
-
- return if $submit_button.attr('disabled')
-
- $submit_button.disable()
- $form.submit()
-
-# If the user tabs to a submit button on a `js-quick-submit` form, display a
-# tooltip to let them know they could've used the hotkey
-$(document).on 'keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', (e) ->
- return unless keyCodeIs(e, 9) # Tab
-
- if isMac()
- title = "You can also press &#8984;-Enter"
- else
- title = "You can also press Ctrl-Enter"
-
- $this = $(@)
- $this.tooltip(
- container: 'body'
- html: 'true'
- placement: 'auto top'
- title: title
- trigger: 'manual'
- ).tooltip('show').one('blur', -> $this.tooltip('hide'))
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
new file mode 100644
index 00000000000..b1977dcc11a
--- /dev/null
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -0,0 +1,41 @@
+
+/*= require extensions/jquery */
+$.fn.requiresInput = function() {
+ var $button, $form, fieldSelector, requireInput, required;
+ $form = $(this);
+ $button = $('button[type=submit], input[type=submit]', $form);
+ required = '[required=required]';
+ fieldSelector = "input" + required + ", select" + required + ", textarea" + required;
+ requireInput = function() {
+ var values;
+ values = _.map($(fieldSelector, $form), function(field) {
+ return field.value;
+ });
+ if (values.length && _.any(values, _.isEmpty)) {
+ return $button.disable();
+ } else {
+ return $button.enable();
+ }
+ };
+ requireInput();
+ return $form.on('change input', fieldSelector, requireInput);
+};
+
+$(function() {
+ var $form, hideOrShowHelpBlock;
+ $form = $('form.js-requires-input');
+ $form.requiresInput();
+ hideOrShowHelpBlock = function(form) {
+ var selected;
+ selected = $('.js-select-namespace option:selected');
+ if (selected.length && selected.data('options-parent') === 'groups') {
+ return form.find('.help-block').hide();
+ } else if (selected.length) {
+ return form.find('.help-block').show();
+ }
+ };
+ hideOrShowHelpBlock($form);
+ return $('.select2.js-select-namespace').change(function() {
+ return hideOrShowHelpBlock($form);
+ });
+});
diff --git a/app/assets/javascripts/behaviors/requires_input.js.coffee b/app/assets/javascripts/behaviors/requires_input.js.coffee
deleted file mode 100644
index 0faa570ce13..00000000000
--- a/app/assets/javascripts/behaviors/requires_input.js.coffee
+++ /dev/null
@@ -1,52 +0,0 @@
-# Requires Input behavior
-#
-# When called on a form with input fields with the `required` attribute, the
-# form's submit button will be disabled until all required fields have values.
-#
-#= require extensions/jquery
-#
-# ### Example Markup
-#
-# <form class="js-requires-input">
-# <input type="text" required="required">
-# <input type="submit" value="Submit">
-# </form>
-#
-$.fn.requiresInput = ->
- $form = $(this)
- $button = $('button[type=submit], input[type=submit]', $form)
-
- required = '[required=required]'
- fieldSelector = "input#{required}, select#{required}, textarea#{required}"
-
- requireInput = ->
- # Collect the input values of *all* required fields
- values = _.map $(fieldSelector, $form), (field) -> field.value
-
- # Disable the button if any required fields are empty
- if values.length && _.any(values, _.isEmpty)
- $button.disable()
- else
- $button.enable()
-
- # Set initial button state
- requireInput()
-
- $form.on 'change input', fieldSelector, requireInput
-
-$ ->
- $form = $('form.js-requires-input')
- $form.requiresInput()
-
- # Hide or Show the help block when creating a new project
- # based on the option selected
- hideOrShowHelpBlock = (form) ->
- selected = $('.js-select-namespace option:selected')
- if selected.length and selected.data('options-parent') is 'groups'
- return form.find('.help-block').hide()
- else if selected.length
- form.find('.help-block').show()
-
- hideOrShowHelpBlock($form)
-
- $('.select2.js-select-namespace').change -> hideOrShowHelpBlock($form)
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.coffee b/app/assets/javascripts/behaviors/toggler_behavior.coffee
deleted file mode 100644
index 177b6918270..00000000000
--- a/app/assets/javascripts/behaviors/toggler_behavior.coffee
+++ /dev/null
@@ -1,14 +0,0 @@
-$ ->
- # Toggle button. Show/hide content inside parent container.
- # Button does not change visibility. If button has icon - it changes chevron style.
- #
- # %div.js-toggle-container
- # %a.js-toggle-button
- # %div.js-toggle-content
- #
- $("body").on "click", ".js-toggle-button", (e) ->
- $(@).find('i').
- toggleClass('fa fa-chevron-down').
- toggleClass('fa fa-chevron-up')
- $(@).closest(".js-toggle-container").find(".js-toggle-content").toggle()
- e.preventDefault()
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
new file mode 100644
index 00000000000..12046abc010
--- /dev/null
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -0,0 +1,7 @@
+$(function() {
+ return $("body").on("click", ".js-toggle-button", function(e) {
+ $(this).find('i').toggleClass('fa fa-chevron-down').toggleClass('fa fa-chevron-up');
+ $(this).closest(".js-toggle-container").find(".js-toggle-content").toggle();
+ return e.preventDefault();
+ });
+});
diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js b/app/assets/javascripts/blob/blob_ci_yaml.js
new file mode 100644
index 00000000000..c77cf5b6d75
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_ci_yaml.js
@@ -0,0 +1,42 @@
+
+/*= require blob/template_selector */
+var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+this.BlobCiYamlSelector = (function(superClass) {
+ extend(BlobCiYamlSelector, superClass);
+
+ function BlobCiYamlSelector() {
+ return BlobCiYamlSelector.__super__.constructor.apply(this, arguments);
+ }
+
+ BlobCiYamlSelector.prototype.requestFile = function(query) {
+ return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this));
+ };
+
+ return BlobCiYamlSelector;
+
+})(TemplateSelector);
+
+this.BlobCiYamlSelectors = (function() {
+ function BlobCiYamlSelectors(opts) {
+ var ref;
+ this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-gitlab-ci-yml-selector'), this.editor = opts.editor;
+ this.$dropdowns.each((function(_this) {
+ return function(i, dropdown) {
+ var $dropdown;
+ $dropdown = $(dropdown);
+ return new BlobCiYamlSelector({
+ pattern: /(.gitlab-ci.yml)/,
+ data: $dropdown.data('data'),
+ wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
+ dropdown: $dropdown,
+ editor: _this.editor
+ });
+ };
+ })(this));
+ }
+
+ return BlobCiYamlSelectors;
+
+})();
diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.coffee b/app/assets/javascripts/blob/blob_ci_yaml.js.coffee
deleted file mode 100644
index d9a03d05529..00000000000
--- a/app/assets/javascripts/blob/blob_ci_yaml.js.coffee
+++ /dev/null
@@ -1,23 +0,0 @@
-#= require blob/template_selector
-
-class @BlobCiYamlSelector extends TemplateSelector
- requestFile: (query) ->
- Api.gitlabCiYml query.name, @requestFileSuccess.bind(@)
-
-class @BlobCiYamlSelectors
- constructor: (opts) ->
- {
- @$dropdowns = $('.js-gitlab-ci-yml-selector')
- @editor
- } = opts
-
- @$dropdowns.each (i, dropdown) =>
- $dropdown = $(dropdown)
-
- new BlobCiYamlSelector(
- pattern: /(.gitlab-ci.yml)/,
- data: $dropdown.data('data'),
- wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
- dropdown: $dropdown,
- editor: @editor
- )
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
new file mode 100644
index 00000000000..4e1e6a4849a
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -0,0 +1,59 @@
+this.BlobFileDropzone = (function() {
+ function BlobFileDropzone(form, method) {
+ var dropzone, form_dropzone, submitButton;
+ form_dropzone = form.find('.dropzone');
+ Dropzone.autoDiscover = false;
+ dropzone = form_dropzone.dropzone({
+ autoDiscover: false,
+ autoProcessQueue: false,
+ url: form.attr('action'),
+ method: method,
+ clickable: true,
+ uploadMultiple: false,
+ paramName: "file",
+ maxFilesize: gon.max_file_size || 10,
+ parallelUploads: 1,
+ maxFiles: 1,
+ addRemoveLinks: true,
+ previewsContainer: '.dropzone-previews',
+ headers: {
+ "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
+ },
+ init: function() {
+ this.on('addedfile', function(file) {
+ $('.dropzone-alerts').html('').hide();
+ });
+ this.on('success', function(header, response) {
+ window.location.href = response.filePath;
+ });
+ this.on('maxfilesexceeded', function(file) {
+ this.removeFile(file);
+ });
+ return this.on('sending', function(file, xhr, formData) {
+ formData.append('target_branch', form.find('.js-target-branch').val());
+ formData.append('create_merge_request', form.find('.js-create-merge-request').val());
+ formData.append('commit_message', form.find('.js-commit-message').val());
+ });
+ },
+ error: function(file, errorMessage) {
+ var stripped;
+ stripped = $("<div/>").html(errorMessage).text();
+ $('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show();
+ this.removeFile(file);
+ }
+ });
+ submitButton = form.find('#submit-all')[0];
+ submitButton.addEventListener('click', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
+ alert("Please select a file");
+ }
+ dropzone[0].dropzone.processQueue();
+ return false;
+ });
+ }
+
+ return BlobFileDropzone;
+
+})();
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
deleted file mode 100644
index 9df932817f6..00000000000
--- a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
+++ /dev/null
@@ -1,57 +0,0 @@
-class @BlobFileDropzone
- constructor: (form, method) ->
- form_dropzone = form.find('.dropzone')
- Dropzone.autoDiscover = false
- dropzone = form_dropzone.dropzone(
- autoDiscover: false
- autoProcessQueue: false
- url: form.attr('action')
- # Rails uses a hidden input field for PUT
- # http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails
- method: method
- clickable: true
- uploadMultiple: false
- paramName: "file"
- maxFilesize: gon.max_file_size or 10
- parallelUploads: 1
- maxFiles: 1
- addRemoveLinks: true
- previewsContainer: '.dropzone-previews'
- headers:
- "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
-
- init: ->
- this.on 'addedfile', (file) ->
- $('.dropzone-alerts').html('').hide()
-
- return
-
- this.on 'success', (header, response) ->
- window.location.href = response.filePath
- return
-
- this.on 'maxfilesexceeded', (file) ->
- @removeFile file
- return
-
- this.on 'sending', (file, xhr, formData) ->
- formData.append('target_branch', form.find('.js-target-branch').val())
- formData.append('create_merge_request', form.find('.js-create-merge-request').val())
- formData.append('commit_message', form.find('.js-commit-message').val())
- return
-
- # Override behavior of adding error underneath preview
- error: (file, errorMessage) ->
- stripped = $("<div/>").html(errorMessage).text();
- $('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show()
- @removeFile file
- return
- )
-
- submitButton = form.find('#submit-all')[0]
- submitButton.addEventListener 'click', (e) ->
- e.preventDefault()
- e.stopPropagation()
- alert "Please select a file" if dropzone[0].dropzone.getQueuedFiles().length == 0
- dropzone[0].dropzone.processQueue()
- return false
diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js b/app/assets/javascripts/blob/blob_gitignore_selector.js
new file mode 100644
index 00000000000..5d2049700f3
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_gitignore_selector.js
@@ -0,0 +1,19 @@
+
+/*= require blob/template_selector */
+var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+this.BlobGitignoreSelector = (function(superClass) {
+ extend(BlobGitignoreSelector, superClass);
+
+ function BlobGitignoreSelector() {
+ return BlobGitignoreSelector.__super__.constructor.apply(this, arguments);
+ }
+
+ BlobGitignoreSelector.prototype.requestFile = function(query) {
+ return Api.gitignoreText(query.name, this.requestFileSuccess.bind(this));
+ };
+
+ return BlobGitignoreSelector;
+
+})(TemplateSelector);
diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js.coffee b/app/assets/javascripts/blob/blob_gitignore_selector.js.coffee
deleted file mode 100644
index 8d0e3f363d1..00000000000
--- a/app/assets/javascripts/blob/blob_gitignore_selector.js.coffee
+++ /dev/null
@@ -1,5 +0,0 @@
-#= require blob/template_selector
-
-class @BlobGitignoreSelector extends TemplateSelector
- requestFile: (query) ->
- Api.gitignoreText query.name, @requestFileSuccess.bind(@)
diff --git a/app/assets/javascripts/blob/blob_gitignore_selectors.js b/app/assets/javascripts/blob/blob_gitignore_selectors.js
new file mode 100644
index 00000000000..1489ba902ef
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_gitignore_selectors.js
@@ -0,0 +1,22 @@
+this.BlobGitignoreSelectors = (function() {
+ function BlobGitignoreSelectors(opts) {
+ var ref;
+ this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-gitignore-selector'), this.editor = opts.editor;
+ this.$dropdowns.each((function(_this) {
+ return function(i, dropdown) {
+ var $dropdown;
+ $dropdown = $(dropdown);
+ return new BlobGitignoreSelector({
+ pattern: /(.gitignore)/,
+ data: $dropdown.data('data'),
+ wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
+ dropdown: $dropdown,
+ editor: _this.editor
+ });
+ };
+ })(this));
+ }
+
+ return BlobGitignoreSelectors;
+
+})();
diff --git a/app/assets/javascripts/blob/blob_gitignore_selectors.js.coffee b/app/assets/javascripts/blob/blob_gitignore_selectors.js.coffee
deleted file mode 100644
index a719ba25122..00000000000
--- a/app/assets/javascripts/blob/blob_gitignore_selectors.js.coffee
+++ /dev/null
@@ -1,17 +0,0 @@
-class @BlobGitignoreSelectors
- constructor: (opts) ->
- {
- @$dropdowns = $('.js-gitignore-selector')
- @editor
- } = opts
-
- @$dropdowns.each (i, dropdown) =>
- $dropdown = $(dropdown)
-
- new BlobGitignoreSelector(
- pattern: /(.gitignore)/,
- data: $dropdown.data('data'),
- wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
- dropdown: $dropdown,
- editor: @editor
- )
diff --git a/app/assets/javascripts/blob/blob_license_selector.js b/app/assets/javascripts/blob/blob_license_selector.js
new file mode 100644
index 00000000000..6b8cceeba3c
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_license_selector.js
@@ -0,0 +1,24 @@
+
+/*= require blob/template_selector */
+var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+this.BlobLicenseSelector = (function(superClass) {
+ extend(BlobLicenseSelector, superClass);
+
+ function BlobLicenseSelector() {
+ return BlobLicenseSelector.__super__.constructor.apply(this, arguments);
+ }
+
+ BlobLicenseSelector.prototype.requestFile = function(query) {
+ var data;
+ data = {
+ project: this.dropdown.data('project'),
+ fullname: this.dropdown.data('fullname')
+ };
+ return Api.licenseText(query.id, data, this.requestFileSuccess.bind(this));
+ };
+
+ return BlobLicenseSelector;
+
+})(TemplateSelector);
diff --git a/app/assets/javascripts/blob/blob_license_selector.js.coffee b/app/assets/javascripts/blob/blob_license_selector.js.coffee
deleted file mode 100644
index a3cc8dd844c..00000000000
--- a/app/assets/javascripts/blob/blob_license_selector.js.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-#= require blob/template_selector
-
-class @BlobLicenseSelector extends TemplateSelector
- requestFile: (query) ->
- data =
- project: @dropdown.data('project')
- fullname: @dropdown.data('fullname')
-
- Api.licenseText query.id, data, @requestFileSuccess.bind(@)
diff --git a/app/assets/javascripts/blob/blob_license_selectors.js b/app/assets/javascripts/blob/blob_license_selectors.js
new file mode 100644
index 00000000000..0f4ed9081ea
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_license_selectors.js
@@ -0,0 +1,22 @@
+this.BlobLicenseSelectors = (function() {
+ function BlobLicenseSelectors(opts) {
+ var ref;
+ this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-license-selector'), this.editor = opts.editor;
+ this.$dropdowns.each((function(_this) {
+ return function(i, dropdown) {
+ var $dropdown;
+ $dropdown = $(dropdown);
+ return new BlobLicenseSelector({
+ pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
+ data: $dropdown.data('data'),
+ wrapper: $dropdown.closest('.js-license-selector-wrap'),
+ dropdown: $dropdown,
+ editor: _this.editor
+ });
+ };
+ })(this));
+ }
+
+ return BlobLicenseSelectors;
+
+})();
diff --git a/app/assets/javascripts/blob/blob_license_selectors.js.coffee b/app/assets/javascripts/blob/blob_license_selectors.js.coffee
deleted file mode 100644
index 68438733108..00000000000
--- a/app/assets/javascripts/blob/blob_license_selectors.js.coffee
+++ /dev/null
@@ -1,17 +0,0 @@
-class @BlobLicenseSelectors
- constructor: (opts) ->
- {
- @$dropdowns = $('.js-license-selector')
- @editor
- } = opts
-
- @$dropdowns.each (i, dropdown) =>
- $dropdown = $(dropdown)
-
- new BlobLicenseSelector(
- pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
- data: $dropdown.data('data'),
- wrapper: $dropdown.closest('.js-license-selector-wrap'),
- dropdown: $dropdown,
- editor: @editor
- )
diff --git a/app/assets/javascripts/blob/edit_blob.js b/app/assets/javascripts/blob/edit_blob.js
new file mode 100644
index 00000000000..58d2e99ede3
--- /dev/null
+++ b/app/assets/javascripts/blob/edit_blob.js
@@ -0,0 +1,63 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.EditBlob = (function() {
+ function EditBlob(assets_path, ace_mode) {
+ if (ace_mode == null) {
+ ace_mode = null;
+ }
+ this.editModeLinkClickHandler = bind(this.editModeLinkClickHandler, this);
+ ace.config.set("modePath", assets_path + "/ace");
+ ace.config.loadModule("ace/ext/searchbox");
+ this.editor = ace.edit("editor");
+ this.editor.focus();
+ if (ace_mode) {
+ this.editor.getSession().setMode("ace/mode/" + ace_mode);
+ }
+ $('form').submit((function(_this) {
+ return function() {
+ return $("#file-content").val(_this.editor.getValue());
+ };
+ })(this));
+ this.initModePanesAndLinks();
+ new BlobLicenseSelectors({
+ editor: this.editor
+ });
+ new BlobGitignoreSelectors({
+ editor: this.editor
+ });
+ new BlobCiYamlSelectors({
+ editor: this.editor
+ });
+ }
+
+ EditBlob.prototype.initModePanesAndLinks = function() {
+ this.$editModePanes = $(".js-edit-mode-pane");
+ this.$editModeLinks = $(".js-edit-mode a");
+ return this.$editModeLinks.click(this.editModeLinkClickHandler);
+ };
+
+ EditBlob.prototype.editModeLinkClickHandler = function(event) {
+ var currentLink, currentPane, paneId;
+ event.preventDefault();
+ currentLink = $(event.target);
+ paneId = currentLink.attr("href");
+ currentPane = this.$editModePanes.filter(paneId);
+ this.$editModeLinks.parent().removeClass("active hover");
+ currentLink.parent().addClass("active hover");
+ this.$editModePanes.hide();
+ currentPane.fadeIn(200);
+ if (paneId === "#preview") {
+ return $.post(currentLink.data("preview-url"), {
+ content: this.editor.getValue()
+ }, function(response) {
+ currentPane.empty().append(response);
+ return currentPane.syntaxHighlight();
+ });
+ } else {
+ return this.editor.focus();
+ }
+ };
+
+ return EditBlob;
+
+})();
diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee
deleted file mode 100644
index 19e584519d7..00000000000
--- a/app/assets/javascripts/blob/edit_blob.js.coffee
+++ /dev/null
@@ -1,42 +0,0 @@
-class @EditBlob
- constructor: (assets_path, ace_mode = null) ->
- ace.config.set "modePath", "#{assets_path}/ace"
- ace.config.loadModule "ace/ext/searchbox"
- @editor = ace.edit("editor")
- @editor.focus()
- @editor.getSession().setMode "ace/mode/#{ace_mode}" if ace_mode
-
- # Before a form submission, move the content from the Ace editor into the
- # submitted textarea
- $('form').submit =>
- $("#file-content").val(@editor.getValue())
-
- @initModePanesAndLinks()
-
- new BlobLicenseSelectors { @editor }
- new BlobGitignoreSelectors { @editor }
- new BlobCiYamlSelectors { @editor }
-
- initModePanesAndLinks: ->
- @$editModePanes = $(".js-edit-mode-pane")
- @$editModeLinks = $(".js-edit-mode a")
- @$editModeLinks.click @editModeLinkClickHandler
-
- editModeLinkClickHandler: (event) =>
- event.preventDefault()
- currentLink = $(event.target)
- paneId = currentLink.attr("href")
- currentPane = @$editModePanes.filter(paneId)
- @$editModeLinks.parent().removeClass "active hover"
- currentLink.parent().addClass "active hover"
- @$editModePanes.hide()
- currentPane.fadeIn 200
- if paneId is "#preview"
- $.post currentLink.data("preview-url"),
- content: @editor.getValue()
- , (response) ->
- currentPane.empty().append response
- currentPane.syntaxHighlight()
-
- else
- @editor.focus()
diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js
new file mode 100644
index 00000000000..cb32b5d7ac5
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selector.js
@@ -0,0 +1,71 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.TemplateSelector = (function() {
+ function TemplateSelector(opts) {
+ var ref;
+ if (opts == null) {
+ opts = {};
+ }
+ this.onClick = bind(this.onClick, this);
+ this.dropdown = opts.dropdown, this.data = opts.data, this.pattern = opts.pattern, this.wrapper = opts.wrapper, this.editor = opts.editor, this.fileEndpoint = opts.fileEndpoint, this.$input = (ref = opts.$input) != null ? ref : $('#file_name');
+ this.buildDropdown();
+ this.bindEvents();
+ this.onFilenameUpdate();
+ }
+
+ TemplateSelector.prototype.buildDropdown = function() {
+ return this.dropdown.glDropdown({
+ data: this.data,
+ filterable: true,
+ selectable: true,
+ toggleLabel: this.toggleLabel,
+ search: {
+ fields: ['name']
+ },
+ clicked: this.onClick,
+ text: function(item) {
+ return item.name;
+ }
+ });
+ };
+
+ TemplateSelector.prototype.bindEvents = function() {
+ return this.$input.on('keyup blur', (function(_this) {
+ return function(e) {
+ return _this.onFilenameUpdate();
+ };
+ })(this));
+ };
+
+ TemplateSelector.prototype.toggleLabel = function(item) {
+ return item.name;
+ };
+
+ TemplateSelector.prototype.onFilenameUpdate = function() {
+ var filenameMatches;
+ if (!this.$input.length) {
+ return;
+ }
+ filenameMatches = this.pattern.test(this.$input.val().trim());
+ if (!filenameMatches) {
+ this.wrapper.addClass('hidden');
+ return;
+ }
+ return this.wrapper.removeClass('hidden');
+ };
+
+ TemplateSelector.prototype.onClick = function(item, el, e) {
+ e.preventDefault();
+ return this.requestFile(item);
+ };
+
+ TemplateSelector.prototype.requestFile = function(item) {};
+
+ TemplateSelector.prototype.requestFileSuccess = function(file) {
+ this.editor.setValue(file.content, 1);
+ return this.editor.focus();
+ };
+
+ return TemplateSelector;
+
+})();
diff --git a/app/assets/javascripts/blob/template_selector.js.coffee b/app/assets/javascripts/blob/template_selector.js.coffee
deleted file mode 100644
index 40c9169beac..00000000000
--- a/app/assets/javascripts/blob/template_selector.js.coffee
+++ /dev/null
@@ -1,60 +0,0 @@
-class @TemplateSelector
- constructor: (opts = {}) ->
- {
- @dropdown,
- @data,
- @pattern,
- @wrapper,
- @editor,
- @fileEndpoint,
- @$input = $('#file_name')
- } = opts
-
- @buildDropdown()
- @bindEvents()
- @onFilenameUpdate()
-
- buildDropdown: ->
- @dropdown.glDropdown(
- data: @data,
- filterable: true,
- selectable: true,
- toggleLabel: @toggleLabel,
- search:
- fields: ['name']
- clicked: @onClick
- text: (item) ->
- item.name
- )
-
- bindEvents: ->
- @$input.on('keyup blur', (e) =>
- @onFilenameUpdate()
- )
-
- toggleLabel: (item) ->
- item.name
-
- onFilenameUpdate: ->
- return unless @$input.length
-
- filenameMatches = @pattern.test(@$input.val().trim())
-
- if not filenameMatches
- @wrapper.addClass('hidden')
- return
-
- @wrapper.removeClass('hidden')
-
- onClick: (item, el, e) =>
- e.preventDefault()
- @requestFile(item)
-
- requestFile: (item) ->
- # To be implemented on the extending class
- # e.g.
- # Api.gitignoreText item.name, @requestFileSuccess.bind(@)
-
- requestFileSuccess: (file) ->
- @editor.setValue(file.content, 1)
- @editor.focus()
diff --git a/app/assets/javascripts/breakpoints.coffee b/app/assets/javascripts/breakpoints.coffee
deleted file mode 100644
index 5457430f921..00000000000
--- a/app/assets/javascripts/breakpoints.coffee
+++ /dev/null
@@ -1,37 +0,0 @@
-class @Breakpoints
- instance = null;
-
- class BreakpointInstance
- BREAKPOINTS = ["xs", "sm", "md", "lg"]
-
- constructor: ->
- @setup()
-
- setup: ->
- allDeviceSelector = BREAKPOINTS.map (breakpoint) ->
- ".device-#{breakpoint}"
- return if $(allDeviceSelector.join(",")).length
-
- # Create all the elements
- els = $.map BREAKPOINTS, (breakpoint) ->
- "<div class='device-#{breakpoint} visible-#{breakpoint}'></div>"
- $("body").append els.join('')
-
- visibleDevice: ->
- allDeviceSelector = BREAKPOINTS.map (breakpoint) ->
- ".device-#{breakpoint}"
- $(allDeviceSelector.join(",")).filter(":visible")
-
- getBreakpointSize: ->
- $visibleDevice = @visibleDevice
- # the page refreshed via turbolinks
- if not $visibleDevice().length
- @setup()
- $visibleDevice = @visibleDevice()
- return $visibleDevice.attr("class").split("visible-")[1]
-
- @get: ->
- return instance ?= new BreakpointInstance
-
-$ =>
- @bp = Breakpoints.get()
diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js
new file mode 100644
index 00000000000..d9e85e1db33
--- /dev/null
+++ b/app/assets/javascripts/breakpoints.js
@@ -0,0 +1,65 @@
+this.Breakpoints = (function() {
+ var BreakpointInstance, instance;
+
+ function Breakpoints() {}
+
+ instance = null;
+
+ BreakpointInstance = (function() {
+ var BREAKPOINTS;
+
+ BREAKPOINTS = ["xs", "sm", "md", "lg"];
+
+ function BreakpointInstance() {
+ this.setup();
+ }
+
+ BreakpointInstance.prototype.setup = function() {
+ var allDeviceSelector, els;
+ allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
+ return ".device-" + breakpoint;
+ });
+ if ($(allDeviceSelector.join(",")).length) {
+ return;
+ }
+ els = $.map(BREAKPOINTS, function(breakpoint) {
+ return "<div class='device-" + breakpoint + " visible-" + breakpoint + "'></div>";
+ });
+ return $("body").append(els.join(''));
+ };
+
+ BreakpointInstance.prototype.visibleDevice = function() {
+ var allDeviceSelector;
+ allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
+ return ".device-" + breakpoint;
+ });
+ return $(allDeviceSelector.join(",")).filter(":visible");
+ };
+
+ BreakpointInstance.prototype.getBreakpointSize = function() {
+ var $visibleDevice;
+ $visibleDevice = this.visibleDevice;
+ if (!$visibleDevice().length) {
+ this.setup();
+ }
+ $visibleDevice = this.visibleDevice();
+ return $visibleDevice.attr("class").split("visible-")[1];
+ };
+
+ return BreakpointInstance;
+
+ })();
+
+ Breakpoints.get = function() {
+ return instance != null ? instance : instance = new BreakpointInstance;
+ };
+
+ return Breakpoints;
+
+})();
+
+$((function(_this) {
+ return function() {
+ return _this.bp = Breakpoints.get();
+ };
+})(this));
diff --git a/app/assets/javascripts/broadcast_message.js b/app/assets/javascripts/broadcast_message.js
new file mode 100644
index 00000000000..f52d652e7bd
--- /dev/null
+++ b/app/assets/javascripts/broadcast_message.js
@@ -0,0 +1,31 @@
+$(function() {
+ var previewPath;
+ $('input#broadcast_message_color').on('input', function() {
+ var previewColor;
+ previewColor = $(this).val();
+ return $('div.broadcast-message-preview').css('background-color', previewColor);
+ });
+ $('input#broadcast_message_font').on('input', function() {
+ var previewColor;
+ previewColor = $(this).val();
+ return $('div.broadcast-message-preview').css('color', previewColor);
+ });
+ previewPath = $('textarea#broadcast_message_message').data('preview-path');
+ return $('textarea#broadcast_message_message').on('input', function() {
+ var message;
+ message = $(this).val();
+ if (message === '') {
+ return $('.js-broadcast-message-preview').text("Your message here");
+ } else {
+ return $.ajax({
+ url: previewPath,
+ type: "POST",
+ data: {
+ broadcast_message: {
+ message: message
+ }
+ }
+ });
+ }
+ });
+});
diff --git a/app/assets/javascripts/broadcast_message.js.coffee b/app/assets/javascripts/broadcast_message.js.coffee
deleted file mode 100644
index a38a329c4c2..00000000000
--- a/app/assets/javascripts/broadcast_message.js.coffee
+++ /dev/null
@@ -1,22 +0,0 @@
-$ ->
- $('input#broadcast_message_color').on 'input', ->
- previewColor = $(@).val()
- $('div.broadcast-message-preview').css('background-color', previewColor)
-
- $('input#broadcast_message_font').on 'input', ->
- previewColor = $(@).val()
- $('div.broadcast-message-preview').css('color', previewColor)
-
- previewPath = $('textarea#broadcast_message_message').data('preview-path')
-
- $('textarea#broadcast_message_message').on 'input', ->
- message = $(@).val()
-
- if message == ''
- $('.js-broadcast-message-preview').text("Your message here")
- else
- $.ajax(
- url: previewPath
- type: "POST"
- data: { broadcast_message: { message: message } }
- )
diff --git a/app/assets/javascripts/build.coffee b/app/assets/javascripts/build.coffee
deleted file mode 100644
index cf203ea43a0..00000000000
--- a/app/assets/javascripts/build.coffee
+++ /dev/null
@@ -1,114 +0,0 @@
-class @Build
- @interval: null
- @state: null
-
- constructor: (@page_url, @build_url, @build_status, @state) ->
- clearInterval(Build.interval)
-
- # Init breakpoint checker
- @bp = Breakpoints.get()
- @hideSidebar()
- $('.js-build-sidebar').niceScroll()
- $(document)
- .off 'click', '.js-sidebar-build-toggle'
- .on 'click', '.js-sidebar-build-toggle', @toggleSidebar
-
- $(window)
- .off 'resize.build'
- .on 'resize.build', @hideSidebar
-
- @updateArtifactRemoveDate()
-
- if $('#build-trace').length
- @getInitialBuildTrace()
- @initScrollButtonAffix()
-
- if @build_status is "running" or @build_status is "pending"
- #
- # Bind autoscroll button to follow build output
- #
- $('#autoscroll-button').on 'click', ->
- state = $(this).data("state")
- if "enabled" is state
- $(this).data "state", "disabled"
- $(this).text "enable autoscroll"
- else
- $(this).data "state", "enabled"
- $(this).text "disable autoscroll"
-
- #
- # Check for new build output if user still watching build page
- # Only valid for runnig build when output changes during time
- #
- Build.interval = setInterval =>
- if window.location.href.split("#").first() is @page_url
- @getBuildTrace()
- , 4000
-
- getInitialBuildTrace: ->
- $.ajax
- url: @build_url
- dataType: 'json'
- success: (build_data) ->
- $('.js-build-output').html build_data.trace_html
-
- if build_data.status is 'success' or build_data.status is 'failed'
- $('.js-build-refresh').remove()
-
- getBuildTrace: ->
- $.ajax
- url: "#{@page_url}/trace.json?state=#{encodeURIComponent(@state)}"
- dataType: "json"
- success: (log) =>
- if log.state
- @state = log.state
-
- if log.status is "running"
- if log.append
- $('.js-build-output').append log.html
- else
- $('.js-build-output').html log.html
- @checkAutoscroll()
- else if log.status isnt @build_status
- Turbolinks.visit @page_url
-
- checkAutoscroll: ->
- $("html,body").scrollTop $("#build-trace").height() if "enabled" is $("#autoscroll-button").data("state")
-
- initScrollButtonAffix: ->
- $buildScroll = $('#js-build-scroll')
- $body = $('body')
- $buildTrace = $('#build-trace')
-
- $buildScroll.affix(
- offset:
- bottom: ->
- $body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top)
- )
-
- shouldHideSidebar: ->
- bootstrapBreakpoint = @bp.getBreakpointSize()
-
- bootstrapBreakpoint is 'xs' or bootstrapBreakpoint is 'sm'
-
- toggleSidebar: =>
- if @shouldHideSidebar()
- $('.js-build-sidebar')
- .toggleClass 'right-sidebar-expanded right-sidebar-collapsed'
-
- hideSidebar: =>
- if @shouldHideSidebar()
- $('.js-build-sidebar')
- .removeClass 'right-sidebar-expanded'
- .addClass 'right-sidebar-collapsed'
- else
- $('.js-build-sidebar')
- .removeClass 'right-sidebar-collapsed'
- .addClass 'right-sidebar-expanded'
-
- updateArtifactRemoveDate: ->
- $date = $('.js-artifacts-remove')
-
- if $date.length
- date = $date.text()
- $date.text $.timefor(new Date(date), ' ')
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
new file mode 100644
index 00000000000..a5bc79dcbf6
--- /dev/null
+++ b/app/assets/javascripts/build.js
@@ -0,0 +1,136 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.Build = (function() {
+ Build.interval = null;
+
+ Build.state = null;
+
+ function Build(page_url, build_url, build_status, state1) {
+ this.page_url = page_url;
+ this.build_url = build_url;
+ this.build_status = build_status;
+ this.state = state1;
+ this.hideSidebar = bind(this.hideSidebar, this);
+ this.toggleSidebar = bind(this.toggleSidebar, this);
+ clearInterval(Build.interval);
+ this.bp = Breakpoints.get();
+ this.hideSidebar();
+ $('.js-build-sidebar').niceScroll();
+ $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
+ $(window).off('resize.build').on('resize.build', this.hideSidebar);
+ this.updateArtifactRemoveDate();
+ if ($('#build-trace').length) {
+ this.getInitialBuildTrace();
+ this.initScrollButtonAffix();
+ }
+ if (this.build_status === "running" || this.build_status === "pending") {
+ $('#autoscroll-button').on('click', function() {
+ var state;
+ state = $(this).data("state");
+ if ("enabled" === state) {
+ $(this).data("state", "disabled");
+ return $(this).text("enable autoscroll");
+ } else {
+ $(this).data("state", "enabled");
+ return $(this).text("disable autoscroll");
+ }
+ });
+ Build.interval = setInterval((function(_this) {
+ return function() {
+ if (window.location.href.split("#").first() === _this.page_url) {
+ return _this.getBuildTrace();
+ }
+ };
+ })(this), 4000);
+ }
+ }
+
+ Build.prototype.getInitialBuildTrace = function() {
+ return $.ajax({
+ url: this.build_url,
+ dataType: 'json',
+ success: function(build_data) {
+ $('.js-build-output').html(build_data.trace_html);
+ if (build_data.status === 'success' || build_data.status === 'failed') {
+ return $('.js-build-refresh').remove();
+ }
+ }
+ });
+ };
+
+ Build.prototype.getBuildTrace = function() {
+ return $.ajax({
+ url: this.page_url + "/trace.json?state=" + (encodeURIComponent(this.state)),
+ dataType: "json",
+ success: (function(_this) {
+ return function(log) {
+ if (log.state) {
+ _this.state = log.state;
+ }
+ if (log.status === "running") {
+ if (log.append) {
+ $('.js-build-output').append(log.html);
+ } else {
+ $('.js-build-output').html(log.html);
+ }
+ return _this.checkAutoscroll();
+ } else if (log.status !== _this.build_status) {
+ return Turbolinks.visit(_this.page_url);
+ }
+ };
+ })(this)
+ });
+ };
+
+ Build.prototype.checkAutoscroll = function() {
+ if ("enabled" === $("#autoscroll-button").data("state")) {
+ return $("html,body").scrollTop($("#build-trace").height());
+ }
+ };
+
+ Build.prototype.initScrollButtonAffix = function() {
+ var $body, $buildScroll, $buildTrace;
+ $buildScroll = $('#js-build-scroll');
+ $body = $('body');
+ $buildTrace = $('#build-trace');
+ return $buildScroll.affix({
+ offset: {
+ bottom: function() {
+ return $body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top);
+ }
+ }
+ });
+ };
+
+ Build.prototype.shouldHideSidebar = function() {
+ var bootstrapBreakpoint;
+ bootstrapBreakpoint = this.bp.getBreakpointSize();
+ return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
+ };
+
+ Build.prototype.toggleSidebar = function() {
+ if (this.shouldHideSidebar()) {
+ return $('.js-build-sidebar').toggleClass('right-sidebar-expanded right-sidebar-collapsed');
+ }
+ };
+
+ Build.prototype.hideSidebar = function() {
+ if (this.shouldHideSidebar()) {
+ return $('.js-build-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
+ } else {
+ return $('.js-build-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
+ }
+ };
+
+ Build.prototype.updateArtifactRemoveDate = function() {
+ var $date, date;
+ $date = $('.js-artifacts-remove');
+ if ($date.length) {
+ date = $date.text();
+ return $date.text($.timefor(new Date(date), ' '));
+ }
+ };
+
+ return Build;
+
+})();
diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js
new file mode 100644
index 00000000000..2e52b2f9076
--- /dev/null
+++ b/app/assets/javascripts/build_artifacts.js
@@ -0,0 +1,24 @@
+this.BuildArtifacts = (function() {
+ function BuildArtifacts() {
+ this.disablePropagation();
+ this.setupEntryClick();
+ }
+
+ BuildArtifacts.prototype.disablePropagation = function() {
+ $('.top-block').on('click', '.download', function(e) {
+ return e.stopPropagation();
+ });
+ return $('.tree-holder').on('click', 'tr[data-link] a', function(e) {
+ return e.stopImmediatePropagation();
+ });
+ };
+
+ BuildArtifacts.prototype.setupEntryClick = function() {
+ return $('.tree-holder').on('click', 'tr[data-link]', function(e) {
+ return window.location = this.dataset.link;
+ });
+ };
+
+ return BuildArtifacts;
+
+})();
diff --git a/app/assets/javascripts/build_artifacts.js.coffee b/app/assets/javascripts/build_artifacts.js.coffee
deleted file mode 100644
index 5ae6cba56c8..00000000000
--- a/app/assets/javascripts/build_artifacts.js.coffee
+++ /dev/null
@@ -1,14 +0,0 @@
-class @BuildArtifacts
- constructor: () ->
- @disablePropagation()
- @setupEntryClick()
-
- disablePropagation: ->
- $('.top-block').on 'click', '.download', (e) ->
- e.stopPropagation()
- $('.tree-holder').on 'click', 'tr[data-link] a', (e) ->
- e.stopImmediatePropagation()
-
- setupEntryClick: ->
- $('.tree-holder').on 'click', 'tr[data-link]', (e) ->
- window.location = @dataset.link
diff --git a/app/assets/javascripts/commit.js b/app/assets/javascripts/commit.js
new file mode 100644
index 00000000000..0708c86feb2
--- /dev/null
+++ b/app/assets/javascripts/commit.js
@@ -0,0 +1,10 @@
+this.Commit = (function() {
+ function Commit() {
+ $('.files .diff-file').each(function() {
+ return new CommitFile(this);
+ });
+ }
+
+ return Commit;
+
+})();
diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee
deleted file mode 100644
index 0566e239191..00000000000
--- a/app/assets/javascripts/commit.js.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-class @Commit
- constructor: ->
- $('.files .diff-file').each ->
- new CommitFile(this)
diff --git a/app/assets/javascripts/commit/file.js b/app/assets/javascripts/commit/file.js
new file mode 100644
index 00000000000..1c7bc6dcf1a
--- /dev/null
+++ b/app/assets/javascripts/commit/file.js
@@ -0,0 +1,10 @@
+this.CommitFile = (function() {
+ function CommitFile(file) {
+ if ($('.image', file).length) {
+ new ImageFile(file);
+ }
+ }
+
+ return CommitFile;
+
+})();
diff --git a/app/assets/javascripts/commit/file.js.coffee b/app/assets/javascripts/commit/file.js.coffee
deleted file mode 100644
index 83e793863b6..00000000000
--- a/app/assets/javascripts/commit/file.js.coffee
+++ /dev/null
@@ -1,5 +0,0 @@
-class @CommitFile
-
- constructor: (file) ->
- if $('.image', file).length
- new ImageFile(file)
diff --git a/app/assets/javascripts/commit/image-file.js b/app/assets/javascripts/commit/image-file.js
new file mode 100644
index 00000000000..bde70ad275d
--- /dev/null
+++ b/app/assets/javascripts/commit/image-file.js
@@ -0,0 +1,172 @@
+this.ImageFile = (function() {
+ var prepareFrames;
+
+ ImageFile.availWidth = 900;
+
+ ImageFile.viewModes = ['two-up', 'swipe'];
+
+ function ImageFile(file) {
+ this.file = file;
+ this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) {
+ return function(deletedWidth, deletedHeight) {
+ return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) {
+ if (width === deletedWidth && height === deletedHeight) {
+ return _this.initViewModes();
+ } else {
+ return _this.initView('two-up');
+ }
+ });
+ };
+ })(this));
+ }
+
+ ImageFile.prototype.initViewModes = function() {
+ var viewMode;
+ viewMode = ImageFile.viewModes[0];
+ $('.view-modes', this.file).removeClass('hide');
+ $('.view-modes-menu', this.file).on('click', 'li', (function(_this) {
+ return function(event) {
+ if (!$(event.currentTarget).hasClass('active')) {
+ return _this.activateViewMode(event.currentTarget.className);
+ }
+ };
+ })(this));
+ return this.activateViewMode(viewMode);
+ };
+
+ ImageFile.prototype.activateViewMode = function(viewMode) {
+ $('.view-modes-menu li', this.file).removeClass('active').filter("." + viewMode).addClass('active');
+ return $(".view:visible:not(." + viewMode + ")", this.file).fadeOut(200, (function(_this) {
+ return function() {
+ $(".view." + viewMode, _this.file).fadeIn(200);
+ return _this.initView(viewMode);
+ };
+ })(this));
+ };
+
+ ImageFile.prototype.initView = function(viewMode) {
+ return this.views[viewMode].call(this);
+ };
+
+ prepareFrames = function(view) {
+ var maxHeight, maxWidth;
+ maxWidth = 0;
+ maxHeight = 0;
+ $('.frame', view).each((function(_this) {
+ return function(index, frame) {
+ var height, width;
+ width = $(frame).width();
+ height = $(frame).height();
+ maxWidth = width > maxWidth ? width : maxWidth;
+ return maxHeight = height > maxHeight ? height : maxHeight;
+ };
+ })(this)).css({
+ width: maxWidth,
+ height: maxHeight
+ });
+ return [maxWidth, maxHeight];
+ };
+
+ ImageFile.prototype.views = {
+ 'two-up': function() {
+ return $('.two-up.view .wrap', this.file).each((function(_this) {
+ return function(index, wrap) {
+ $('img', wrap).each(function() {
+ var currentWidth;
+ currentWidth = $(this).width();
+ if (currentWidth > ImageFile.availWidth / 2) {
+ return $(this).width(ImageFile.availWidth / 2);
+ }
+ });
+ return _this.requestImageInfo($('img', wrap), function(width, height) {
+ $('.image-info .meta-width', wrap).text(width + "px");
+ $('.image-info .meta-height', wrap).text(height + "px");
+ return $('.image-info', wrap).removeClass('hide');
+ });
+ };
+ })(this));
+ },
+ 'swipe': function() {
+ var maxHeight, maxWidth;
+ maxWidth = 0;
+ maxHeight = 0;
+ return $('.swipe.view', this.file).each((function(_this) {
+ return function(index, view) {
+ var ref;
+ ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
+ $('.swipe-frame', view).css({
+ width: maxWidth + 16,
+ height: maxHeight + 28
+ });
+ $('.swipe-wrap', view).css({
+ width: maxWidth + 1,
+ height: maxHeight + 2
+ });
+ return $('.swipe-bar', view).css({
+ left: 0
+ }).draggable({
+ axis: 'x',
+ containment: 'parent',
+ drag: function(event) {
+ return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
+ },
+ stop: function(event) {
+ return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
+ }
+ });
+ };
+ })(this));
+ },
+ 'onion-skin': function() {
+ var dragTrackWidth, maxHeight, maxWidth;
+ maxWidth = 0;
+ maxHeight = 0;
+ dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
+ return $('.onion-skin.view', this.file).each((function(_this) {
+ return function(index, view) {
+ var ref;
+ ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
+ $('.onion-skin-frame', view).css({
+ width: maxWidth + 16,
+ height: maxHeight + 28
+ });
+ $('.swipe-wrap', view).css({
+ width: maxWidth + 1,
+ height: maxHeight + 2
+ });
+ return $('.dragger', view).css({
+ left: dragTrackWidth
+ }).draggable({
+ axis: 'x',
+ containment: 'parent',
+ drag: function(event) {
+ return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
+ },
+ stop: function(event) {
+ return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
+ }
+ });
+ };
+ })(this));
+ }
+ };
+
+ ImageFile.prototype.requestImageInfo = function(img, callback) {
+ var domImg;
+ domImg = img.get(0);
+ if (domImg) {
+ if (domImg.complete) {
+ return callback.call(this, domImg.naturalWidth, domImg.naturalHeight);
+ } else {
+ return img.on('load', (function(_this) {
+ return function() {
+ return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight);
+ };
+ })(this));
+ }
+ }
+ };
+
+ return ImageFile;
+
+})();
diff --git a/app/assets/javascripts/commit/image-file.js.coffee b/app/assets/javascripts/commit/image-file.js.coffee
deleted file mode 100644
index 9c723f51e54..00000000000
--- a/app/assets/javascripts/commit/image-file.js.coffee
+++ /dev/null
@@ -1,127 +0,0 @@
-class @ImageFile
-
- # Width where images must fits in, for 2-up this gets divided by 2
- @availWidth = 900
- @viewModes = ['two-up', 'swipe']
-
- constructor: (@file) ->
- # Determine if old and new file has same dimensions, if not show 'two-up' view
- this.requestImageInfo $('.two-up.view .frame.deleted img', @file), (deletedWidth, deletedHeight) =>
- this.requestImageInfo $('.two-up.view .frame.added img', @file), (width, height) =>
- if width == deletedWidth && height == deletedHeight
- this.initViewModes()
- else
- this.initView('two-up')
-
- initViewModes: ->
- viewMode = ImageFile.viewModes[0]
-
- $('.view-modes', @file).removeClass 'hide'
- $('.view-modes-menu', @file).on 'click', 'li', (event) =>
- unless $(event.currentTarget).hasClass('active')
- this.activateViewMode(event.currentTarget.className)
-
- this.activateViewMode(viewMode)
-
- activateViewMode: (viewMode) ->
- $('.view-modes-menu li', @file)
- .removeClass('active')
- .filter(".#{viewMode}").addClass 'active'
- $(".view:visible:not(.#{viewMode})", @file).fadeOut 200, =>
- $(".view.#{viewMode}", @file).fadeIn(200)
- this.initView viewMode
-
- initView: (viewMode) ->
- this.views[viewMode].call(this)
-
- prepareFrames = (view) ->
- maxWidth = 0
- maxHeight = 0
- $('.frame', view).each (index, frame) =>
- width = $(frame).width()
- height = $(frame).height()
- maxWidth = if width > maxWidth then width else maxWidth
- maxHeight = if height > maxHeight then height else maxHeight
- .css
- width: maxWidth
- height: maxHeight
-
- [maxWidth, maxHeight]
-
- views:
- 'two-up': ->
- $('.two-up.view .wrap', @file).each (index, wrap) =>
- $('img', wrap).each ->
- currentWidth = $(this).width()
- if currentWidth > ImageFile.availWidth / 2
- $(this).width ImageFile.availWidth / 2
-
- this.requestImageInfo $('img', wrap), (width, height) ->
- $('.image-info .meta-width', wrap).text "#{width}px"
- $('.image-info .meta-height', wrap).text "#{height}px"
- $('.image-info', wrap).removeClass('hide')
-
- 'swipe': ->
- maxWidth = 0
- maxHeight = 0
-
- $('.swipe.view', @file).each (index, view) =>
-
- [maxWidth, maxHeight] = prepareFrames(view)
-
- $('.swipe-frame', view).css
- width: maxWidth + 16
- height: maxHeight + 28
-
- $('.swipe-wrap', view).css
- width: maxWidth + 1
- height: maxHeight + 2
-
- $('.swipe-bar', view).css
- left: 0
- .draggable
- axis: 'x'
- containment: 'parent'
- drag: (event) ->
- $('.swipe-wrap', view).width (maxWidth + 1) - $(this).position().left
- stop: (event) ->
- $('.swipe-wrap', view).width (maxWidth + 1) - $(this).position().left
-
- 'onion-skin': ->
- maxWidth = 0
- maxHeight = 0
-
- dragTrackWidth = $('.drag-track', @file).width() - $('.dragger', @file).width()
-
- $('.onion-skin.view', @file).each (index, view) =>
-
- [maxWidth, maxHeight] = prepareFrames(view)
-
- $('.onion-skin-frame', view).css
- width: maxWidth + 16
- height: maxHeight + 28
-
- $('.swipe-wrap', view).css
- width: maxWidth + 1
- height: maxHeight + 2
-
- $('.dragger', view).css
- left: dragTrackWidth
- .draggable
- axis: 'x'
- containment: 'parent'
- drag: (event) ->
- $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth)
- stop: (event) ->
- $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth)
-
-
-
- requestImageInfo: (img, callback) ->
- domImg = img.get(0)
- if domImg
- if domImg.complete
- callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
- else
- img.on 'load', =>
- callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
new file mode 100644
index 00000000000..29787bc6a76
--- /dev/null
+++ b/app/assets/javascripts/commits.js
@@ -0,0 +1,55 @@
+this.CommitsList = (function() {
+ function CommitsList() {}
+
+ CommitsList.timer = null;
+
+ CommitsList.init = function(limit) {
+ $("body").on("click", ".day-commits-table li.commit", function(event) {
+ if (event.target.nodeName !== "A") {
+ location.href = $(this).attr("url");
+ e.stopPropagation();
+ return false;
+ }
+ });
+ Pager.init(limit, false);
+ this.content = $("#commits-list");
+ this.searchField = $("#commits-search");
+ return this.initSearch();
+ };
+
+ CommitsList.initSearch = function() {
+ this.timer = null;
+ return this.searchField.keyup((function(_this) {
+ return function() {
+ clearTimeout(_this.timer);
+ return _this.timer = setTimeout(_this.filterResults, 500);
+ };
+ })(this));
+ };
+
+ CommitsList.filterResults = function() {
+ var commitsUrl, form, search;
+ form = $(".commits-search-form");
+ search = CommitsList.searchField.val();
+ commitsUrl = form.attr("action") + '?' + form.serialize();
+ CommitsList.content.fadeTo('fast', 0.5);
+ return $.ajax({
+ type: "GET",
+ url: form.attr("action"),
+ data: form.serialize(),
+ complete: function() {
+ return CommitsList.content.fadeTo('fast', 1.0);
+ },
+ success: function(data) {
+ CommitsList.content.html(data.html);
+ return history.replaceState({
+ page: commitsUrl
+ }, document.title, commitsUrl);
+ },
+ dataType: "json"
+ });
+ };
+
+ return CommitsList;
+
+})();
diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee
deleted file mode 100644
index 0acb4c1955e..00000000000
--- a/app/assets/javascripts/commits.js.coffee
+++ /dev/null
@@ -1,39 +0,0 @@
-class @CommitsList
- @timer = null
-
- @init: (limit) ->
- $("body").on "click", ".day-commits-table li.commit", (event) ->
- if event.target.nodeName != "A"
- location.href = $(this).attr("url")
- e.stopPropagation()
- return false
-
- Pager.init limit, false
-
- @content = $("#commits-list")
- @searchField = $("#commits-search")
- @initSearch()
-
- @initSearch: ->
- @timer = null
- @searchField.keyup =>
- clearTimeout(@timer)
- @timer = setTimeout(@filterResults, 500)
-
- @filterResults: =>
- form = $(".commits-search-form")
- search = @searchField.val()
- commitsUrl = form.attr("action") + '?' + form.serialize()
- @content.fadeTo('fast', 0.5)
-
- $.ajax
- type: "GET"
- url: form.attr("action")
- data: form.serialize()
- complete: =>
- @content.fadeTo('fast', 1.0)
- success: (data) =>
- @content.html(data.html)
- # Change url so if user reload a page - search results are saved
- history.replaceState {page: commitsUrl}, document.title, commitsUrl
- dataType: "json"
diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js
new file mode 100644
index 00000000000..24cf3cd0727
--- /dev/null
+++ b/app/assets/javascripts/compare.js
@@ -0,0 +1,88 @@
+this.Compare = (function() {
+ function Compare(opts) {
+ this.opts = opts;
+ this.source_loading = $(".js-source-loading");
+ this.target_loading = $(".js-target-loading");
+ $('.js-compare-dropdown').each((function(_this) {
+ return function(i, dropdown) {
+ var $dropdown;
+ $dropdown = $(dropdown);
+ return $dropdown.glDropdown({
+ selectable: true,
+ fieldName: $dropdown.data('field-name'),
+ filterable: true,
+ id: function(obj, $el) {
+ return $el.data('id');
+ },
+ toggleLabel: function(obj, $el) {
+ return $el.text().trim();
+ },
+ clicked: function(e, el) {
+ if ($dropdown.is('.js-target-branch')) {
+ return _this.getTargetHtml();
+ } else if ($dropdown.is('.js-source-branch')) {
+ return _this.getSourceHtml();
+ } else if ($dropdown.is('.js-target-project')) {
+ return _this.getTargetProject();
+ }
+ }
+ });
+ };
+ })(this));
+ this.initialState();
+ }
+
+ Compare.prototype.initialState = function() {
+ this.getSourceHtml();
+ return this.getTargetHtml();
+ };
+
+ Compare.prototype.getTargetProject = function() {
+ return $.ajax({
+ url: this.opts.targetProjectUrl,
+ data: {
+ target_project_id: $("input[name='merge_request[target_project_id]']").val()
+ },
+ beforeSend: function() {
+ return $('.mr_target_commit').empty();
+ },
+ success: function(html) {
+ return $('.js-target-branch-dropdown .dropdown-content').html(html);
+ }
+ });
+ };
+
+ Compare.prototype.getSourceHtml = function() {
+ return this.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
+ ref: $("input[name='merge_request[source_branch]']").val()
+ });
+ };
+
+ Compare.prototype.getTargetHtml = function() {
+ return this.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
+ target_project_id: $("input[name='merge_request[target_project_id]']").val(),
+ ref: $("input[name='merge_request[target_branch]']").val()
+ });
+ };
+
+ Compare.prototype.sendAjax = function(url, loading, target, data) {
+ var $target;
+ $target = $(target);
+ return $.ajax({
+ url: url,
+ data: data,
+ beforeSend: function() {
+ loading.show();
+ return $target.empty();
+ },
+ success: function(html) {
+ loading.hide();
+ $target.html(html);
+ return $('.js-timeago', $target).timeago();
+ }
+ });
+ };
+
+ return Compare;
+
+})();
diff --git a/app/assets/javascripts/compare.js.coffee b/app/assets/javascripts/compare.js.coffee
deleted file mode 100644
index f20992ead3e..00000000000
--- a/app/assets/javascripts/compare.js.coffee
+++ /dev/null
@@ -1,67 +0,0 @@
-class @Compare
- constructor: (@opts) ->
- @source_loading = $ ".js-source-loading"
- @target_loading = $ ".js-target-loading"
-
- $('.js-compare-dropdown').each (i, dropdown) =>
- $dropdown = $(dropdown)
-
- $dropdown.glDropdown(
- selectable: true
- fieldName: $dropdown.data 'field-name'
- filterable: true
- id: (obj, $el) ->
- $el.data 'id'
- toggleLabel: (obj, $el) ->
- $el.text().trim()
- clicked: (e, el) =>
- if $dropdown.is '.js-target-branch'
- @getTargetHtml()
- else if $dropdown.is '.js-source-branch'
- @getSourceHtml()
- else if $dropdown.is '.js-target-project'
- @getTargetProject()
- )
-
- @initialState()
-
- initialState: ->
- @getSourceHtml()
- @getTargetHtml()
-
- getTargetProject: ->
- $.ajax(
- url: @opts.targetProjectUrl
- data:
- target_project_id: $("input[name='merge_request[target_project_id]']").val()
- beforeSend: ->
- $('.mr_target_commit').empty()
- success: (html) ->
- $('.js-target-branch-dropdown .dropdown-content').html html
- )
-
- getSourceHtml: ->
- @sendAjax(@opts.sourceBranchUrl, @source_loading, '.mr_source_commit',
- ref: $("input[name='merge_request[source_branch]']").val()
- )
-
- getTargetHtml: ->
- @sendAjax(@opts.targetBranchUrl, @target_loading, '.mr_target_commit',
- target_project_id: $("input[name='merge_request[target_project_id]']").val()
- ref: $("input[name='merge_request[target_branch]']").val()
- )
-
- sendAjax: (url, loading, target, data) ->
- $target = $(target)
-
- $.ajax(
- url: url
- data: data
- beforeSend: ->
- loading.show()
- $target.empty()
- success: (html) ->
- loading.hide()
- $target.html html
- $('.js-timeago', $target).timeago()
- )
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
new file mode 100644
index 00000000000..53500c702f6
--- /dev/null
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -0,0 +1,48 @@
+this.CompareAutocomplete = (function() {
+ function CompareAutocomplete() {
+ this.initDropdown();
+ }
+
+ CompareAutocomplete.prototype.initDropdown = function() {
+ return $('.js-compare-dropdown').each(function() {
+ var $dropdown, selected;
+ $dropdown = $(this);
+ selected = $dropdown.data('selected');
+ return $dropdown.glDropdown({
+ data: function(term, callback) {
+ return $.ajax({
+ url: $dropdown.data('refs-url'),
+ data: {
+ ref: $dropdown.data('ref')
+ }
+ }).done(function(refs) {
+ return callback(refs);
+ });
+ },
+ selectable: true,
+ filterable: true,
+ filterByText: true,
+ fieldName: $dropdown.attr('name'),
+ filterInput: 'input[type="text"]',
+ renderRow: function(ref) {
+ var link;
+ if (ref.header != null) {
+ return $('<li />').addClass('dropdown-header').text(ref.header);
+ } else {
+ link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
+ return $('<li />').append(link);
+ }
+ },
+ id: function(obj, $el) {
+ return $el.attr('data-ref');
+ },
+ toggleLabel: function(obj, $el) {
+ return $el.text().trim();
+ }
+ });
+ });
+ };
+
+ return CompareAutocomplete;
+
+})();
diff --git a/app/assets/javascripts/compare_autocomplete.js.coffee b/app/assets/javascripts/compare_autocomplete.js.coffee
deleted file mode 100644
index 7ad9fd97637..00000000000
--- a/app/assets/javascripts/compare_autocomplete.js.coffee
+++ /dev/null
@@ -1,41 +0,0 @@
-class @CompareAutocomplete
- constructor: ->
- @initDropdown()
-
- initDropdown: ->
- $('.js-compare-dropdown').each ->
- $dropdown = $(@)
- selected = $dropdown.data('selected')
-
- $dropdown.glDropdown(
- data: (term, callback) ->
- $.ajax(
- url: $dropdown.data('refs-url')
- data:
- ref: $dropdown.data('ref')
- ).done (refs) ->
- callback(refs)
- selectable: true
- filterable: true
- filterByText: true
- fieldName: $dropdown.attr('name')
- filterInput: 'input[type="text"]'
- renderRow: (ref) ->
- if ref.header?
- $('<li />')
- .addClass('dropdown-header')
- .text(ref.header)
- else
- link = $('<a />')
- .attr('href', '#')
- .addClass(if ref is selected then 'is-active' else '')
- .text(ref)
- .attr('data-ref', escape(ref))
-
- $('<li />')
- .append(link)
- id: (obj, $el) ->
- $el.attr('data-ref')
- toggleLabel: (obj, $el) ->
- $el.text().trim()
- )
diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js
new file mode 100644
index 00000000000..d5c6dfb7b6d
--- /dev/null
+++ b/app/assets/javascripts/confirm_danger_modal.js
@@ -0,0 +1,29 @@
+this.ConfirmDangerModal = (function() {
+ function ConfirmDangerModal(form, text) {
+ var project_path, submit;
+ this.form = form;
+ $('.js-confirm-text').text(text || '');
+ $('.js-confirm-danger-input').val('');
+ $('#modal-confirm-danger').modal('show');
+ project_path = $('.js-confirm-danger-match').text();
+ submit = $('.js-confirm-danger-submit');
+ submit.disable();
+ $('.js-confirm-danger-input').off('input');
+ $('.js-confirm-danger-input').on('input', function() {
+ if (rstrip($(this).val()) === project_path) {
+ return submit.enable();
+ } else {
+ return submit.disable();
+ }
+ });
+ $('.js-confirm-danger-submit').off('click');
+ $('.js-confirm-danger-submit').on('click', (function(_this) {
+ return function() {
+ return _this.form.submit();
+ };
+ })(this));
+ }
+
+ return ConfirmDangerModal;
+
+})();
diff --git a/app/assets/javascripts/confirm_danger_modal.js.coffee b/app/assets/javascripts/confirm_danger_modal.js.coffee
deleted file mode 100644
index 66e34dd4a08..00000000000
--- a/app/assets/javascripts/confirm_danger_modal.js.coffee
+++ /dev/null
@@ -1,20 +0,0 @@
-class @ConfirmDangerModal
- constructor: (form, text) ->
- @form = form
- $('.js-confirm-text').text(text || '')
- $('.js-confirm-danger-input').val('')
- $('#modal-confirm-danger').modal('show')
- project_path = $('.js-confirm-danger-match').text()
- submit = $('.js-confirm-danger-submit')
- submit.disable()
-
- $('.js-confirm-danger-input').off 'input'
- $('.js-confirm-danger-input').on 'input', ->
- if rstrip($(@).val()) is project_path
- submit.enable()
- else
- submit.disable()
-
- $('.js-confirm-danger-submit').off 'click'
- $('.js-confirm-danger-submit').on 'click', =>
- @form.submit()
diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js
new file mode 100644
index 00000000000..b5c562c7112
--- /dev/null
+++ b/app/assets/javascripts/copy_to_clipboard.js
@@ -0,0 +1,38 @@
+
+/*= require clipboard */
+var genericError, genericSuccess, showTooltip;
+
+genericSuccess = function(e) {
+ showTooltip(e.trigger, 'Copied!');
+ e.clearSelection();
+ return $(e.trigger).blur();
+};
+
+genericError = function(e) {
+ var key;
+ if (/Mac/i.test(navigator.userAgent)) {
+ key = '&#8984;';
+ } else {
+ key = 'Ctrl';
+ }
+ return showTooltip(e.trigger, "Press " + key + "-C to copy");
+};
+
+showTooltip = function(target, title) {
+ return $(target).tooltip({
+ container: 'body',
+ html: 'true',
+ placement: 'auto bottom',
+ title: title,
+ trigger: 'manual'
+ }).tooltip('show').one('mouseleave', function() {
+ return $(this).tooltip('hide');
+ });
+};
+
+$(function() {
+ var clipboard;
+ clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
+ clipboard.on('success', genericSuccess);
+ return clipboard.on('error', genericError);
+});
diff --git a/app/assets/javascripts/copy_to_clipboard.js.coffee b/app/assets/javascripts/copy_to_clipboard.js.coffee
deleted file mode 100644
index 24301e01b10..00000000000
--- a/app/assets/javascripts/copy_to_clipboard.js.coffee
+++ /dev/null
@@ -1,37 +0,0 @@
-#= require clipboard
-
-genericSuccess = (e) ->
- showTooltip(e.trigger, 'Copied!')
-
- # Clear the selection and blur the trigger so it loses its border
- e.clearSelection()
- $(e.trigger).blur()
-
-# Safari doesn't support `execCommand`, so instead we inform the user to
-# copy manually.
-#
-# See http://clipboardjs.com/#browser-support
-genericError = (e) ->
- if /Mac/i.test(navigator.userAgent)
- key = '&#8984;' # Command
- else
- key = 'Ctrl'
-
- showTooltip(e.trigger, "Press #{key}-C to copy")
-
-showTooltip = (target, title) ->
- $(target).
- tooltip(
- container: 'body'
- html: 'true'
- placement: 'auto bottom'
- title: title
- trigger: 'manual'
- ).
- tooltip('show').
- one('mouseleave', -> $(this).tooltip('hide'))
-
-$ ->
- clipboard = new Clipboard '[data-clipboard-target], [data-clipboard-text]'
- clipboard.on 'success', genericSuccess
- clipboard.on 'error', genericError
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
new file mode 100644
index 00000000000..8e6519aabe5
--- /dev/null
+++ b/app/assets/javascripts/diff.js
@@ -0,0 +1,74 @@
+this.Diff = (function() {
+ var UNFOLD_COUNT;
+
+ UNFOLD_COUNT = 20;
+
+ function Diff() {
+ $('.files .diff-file').singleFileDiff();
+ this.filesCommentButton = $('.files .diff-file').filesCommentButton();
+ $(document).off('click', '.js-unfold');
+ $(document).on('click', '.js-unfold', (function(_this) {
+ return function(event) {
+ var line_number, link, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom;
+ target = $(event.target);
+ unfoldBottom = target.hasClass('js-unfold-bottom');
+ unfold = true;
+ ref = _this.lineNumbers(target.parent()), old_line = ref[0], line_number = ref[1];
+ offset = line_number - old_line;
+ if (unfoldBottom) {
+ line_number += 1;
+ since = line_number;
+ to = line_number + UNFOLD_COUNT;
+ } else {
+ ref1 = _this.lineNumbers(target.parent().prev()), prev_old_line = ref1[0], prev_new_line = ref1[1];
+ line_number -= 1;
+ to = line_number;
+ if (line_number - UNFOLD_COUNT > prev_new_line + 1) {
+ since = line_number - UNFOLD_COUNT;
+ } else {
+ since = prev_new_line + 1;
+ unfold = false;
+ }
+ }
+ link = target.parents('.diff-file').attr('data-blob-diff-path');
+ params = {
+ since: since,
+ to: to,
+ bottom: unfoldBottom,
+ offset: offset,
+ unfold: unfold,
+ indent: 1
+ };
+ return $.get(link, params, function(response) {
+ return target.parent().replaceWith(response);
+ });
+ };
+ })(this));
+ }
+
+ Diff.prototype.lineNumbers = function(line) {
+ var i, l, len, line_number, line_numbers, lines, results;
+ if (!line.children().length) {
+ return [0, 0];
+ }
+ lines = line.children().slice(0, 2);
+ line_numbers = (function() {
+ var i, len, results;
+ results = [];
+ for (i = 0, len = lines.length; i < len; i++) {
+ l = lines[i];
+ results.push($(l).attr('data-linenumber'));
+ }
+ return results;
+ })();
+ results = [];
+ for (i = 0, len = line_numbers.length; i < len; i++) {
+ line_number = line_numbers[i];
+ results.push(parseInt(line_number));
+ }
+ return results;
+ };
+
+ return Diff;
+
+})();
diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee
deleted file mode 100644
index c132cc8c542..00000000000
--- a/app/assets/javascripts/diff.js.coffee
+++ /dev/null
@@ -1,51 +0,0 @@
-class @Diff
- UNFOLD_COUNT = 20
- constructor: ->
- $('.files .diff-file').singleFileDiff()
- @filesCommentButton = $('.files .diff-file').filesCommentButton()
-
- $(document).off('click', '.js-unfold')
- $(document).on('click', '.js-unfold', (event) =>
- target = $(event.target)
- unfoldBottom = target.hasClass('js-unfold-bottom')
- unfold = true
-
- [old_line, line_number] = @lineNumbers(target.parent())
- offset = line_number - old_line
-
- if unfoldBottom
- line_number += 1
- since = line_number
- to = line_number + UNFOLD_COUNT
- else
- [prev_old_line, prev_new_line] = @lineNumbers(target.parent().prev())
- line_number -= 1
- to = line_number
- if line_number - UNFOLD_COUNT > prev_new_line + 1
- since = line_number - UNFOLD_COUNT
- else
- since = prev_new_line + 1
- unfold = false
-
- link = target.parents('.diff-file').attr('data-blob-diff-path')
- params =
- since: since
- to: to
- bottom: unfoldBottom
- offset: offset
- unfold: unfold
- # indent is used to compensate for single space indent to fit
- # '+' and '-' prepended to diff lines,
- # see https://gitlab.com/gitlab-org/gitlab-ce/issues/707
- indent: 1
-
- $.get(link, params, (response) ->
- target.parent().replaceWith(response)
- )
- )
-
- lineNumbers: (line) ->
- return ([0, 0]) unless line.children().length
- lines = line.children().slice(0, 2)
- line_numbers = ($(l).attr('data-linenumber') for l in lines)
- (parseInt(line_number) for line_number in line_numbers)
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
new file mode 100644
index 00000000000..43d0bb09d4e
--- /dev/null
+++ b/app/assets/javascripts/dispatcher.js
@@ -0,0 +1,252 @@
+var Dispatcher;
+
+$(function() {
+ return new Dispatcher();
+});
+
+Dispatcher = (function() {
+ function Dispatcher() {
+ this.initSearch();
+ this.initPageScripts();
+ }
+
+ Dispatcher.prototype.initPageScripts = function() {
+ var page, path, shortcut_handler;
+ page = $('body').attr('data-page');
+ if (!page) {
+ return false;
+ }
+ path = page.split(':');
+ shortcut_handler = null;
+ switch (page) {
+ case 'projects:issues:index':
+ Issuable.init();
+ new IssuableBulkActions();
+ shortcut_handler = new ShortcutsNavigation();
+ break;
+ case 'projects:issues:show':
+ new Issue();
+ shortcut_handler = new ShortcutsIssuable();
+ new ZenMode();
+ break;
+ case 'projects:milestones:show':
+ case 'groups:milestones:show':
+ case 'dashboard:milestones:show':
+ new Milestone();
+ break;
+ case 'dashboard:todos:index':
+ new Todos();
+ break;
+ case 'projects:milestones:new':
+ case 'projects:milestones:edit':
+ new ZenMode();
+ new DueDateSelect();
+ new GLForm($('.milestone-form'));
+ break;
+ case 'groups:milestones:new':
+ new ZenMode();
+ break;
+ case 'projects:compare:show':
+ new Diff();
+ break;
+ case 'projects:issues:new':
+ case 'projects:issues:edit':
+ shortcut_handler = new ShortcutsNavigation();
+ new GLForm($('.issue-form'));
+ new IssuableForm($('.issue-form'));
+ break;
+ case 'projects:merge_requests:new':
+ case 'projects:merge_requests:edit':
+ new Diff();
+ shortcut_handler = new ShortcutsNavigation();
+ new GLForm($('.merge-request-form'));
+ new IssuableForm($('.merge-request-form'));
+ break;
+ case 'projects:tags:new':
+ new ZenMode();
+ new GLForm($('.tag-form'));
+ break;
+ case 'projects:releases:edit':
+ new ZenMode();
+ new GLForm($('.release-form'));
+ break;
+ case 'projects:merge_requests:show':
+ new Diff();
+ shortcut_handler = new ShortcutsIssuable(true);
+ new ZenMode();
+ new MergedButtons();
+ break;
+ case 'projects:merge_requests:commits':
+ case 'projects:merge_requests:builds':
+ new MergedButtons();
+ break;
+ case "projects:merge_requests:diffs":
+ new Diff();
+ new ZenMode();
+ new MergedButtons();
+ break;
+ case 'projects:merge_requests:index':
+ shortcut_handler = new ShortcutsNavigation();
+ Issuable.init();
+ break;
+ case 'dashboard:activity':
+ new Activities();
+ break;
+ case 'dashboard:projects:starred':
+ new Activities();
+ break;
+ case 'projects:commit:show':
+ new Commit();
+ new Diff();
+ new ZenMode();
+ shortcut_handler = new ShortcutsNavigation();
+ break;
+ case 'projects:commits:show':
+ case 'projects:activity':
+ shortcut_handler = new ShortcutsNavigation();
+ break;
+ case 'projects:show':
+ shortcut_handler = new ShortcutsNavigation();
+ new NotificationsForm();
+ if ($('#tree-slider').length) {
+ new TreeView();
+ }
+ break;
+ case 'groups:activity':
+ new Activities();
+ break;
+ case 'groups:show':
+ shortcut_handler = new ShortcutsNavigation();
+ new NotificationsForm();
+ new NotificationsDropdown();
+ break;
+ case 'groups:group_members:index':
+ new GroupMembers();
+ new UsersSelect();
+ break;
+ case 'projects:project_members:index':
+ new ProjectMembers();
+ new UsersSelect();
+ break;
+ case 'groups:new':
+ case 'groups:edit':
+ case 'admin:groups:edit':
+ case 'admin:groups:new':
+ new GroupAvatar();
+ break;
+ case 'projects:tree:show':
+ shortcut_handler = new ShortcutsNavigation();
+ new TreeView();
+ break;
+ case 'projects:find_file:show':
+ shortcut_handler = true;
+ break;
+ case 'projects:blob:show':
+ case 'projects:blame:show':
+ new LineHighlighter();
+ shortcut_handler = new ShortcutsNavigation();
+ new ShortcutsBlob(true);
+ break;
+ case 'projects:labels:new':
+ case 'projects:labels:edit':
+ new Labels();
+ break;
+ case 'projects:labels:index':
+ if ($('.prioritized-labels').length) {
+ new LabelManager();
+ }
+ break;
+ case 'projects:network:show':
+ shortcut_handler = true;
+ break;
+ case 'projects:forks:new':
+ new ProjectFork();
+ break;
+ case 'projects:artifacts:browse':
+ new BuildArtifacts();
+ break;
+ case 'projects:group_links:index':
+ new GroupsSelect();
+ break;
+ case 'search:show':
+ new Search();
+ }
+ switch (path.first()) {
+ case 'admin':
+ new Admin();
+ switch (path[1]) {
+ case 'groups':
+ new UsersSelect();
+ break;
+ case 'projects':
+ new NamespaceSelects();
+ }
+ break;
+ case 'dashboard':
+ case 'root':
+ shortcut_handler = new ShortcutsDashboardNavigation();
+ break;
+ case 'profiles':
+ new NotificationsForm();
+ new NotificationsDropdown();
+ break;
+ case 'projects':
+ new Project();
+ new ProjectAvatar();
+ switch (path[1]) {
+ case 'compare':
+ new CompareAutocomplete();
+ break;
+ case 'edit':
+ shortcut_handler = new ShortcutsNavigation();
+ new ProjectNew();
+ break;
+ case 'new':
+ new ProjectNew();
+ break;
+ case 'show':
+ new ProjectNew();
+ new ProjectShow();
+ new NotificationsDropdown();
+ break;
+ case 'wikis':
+ new Wikis();
+ shortcut_handler = new ShortcutsNavigation();
+ new ZenMode();
+ new GLForm($('.wiki-form'));
+ break;
+ case 'snippets':
+ shortcut_handler = new ShortcutsNavigation();
+ if (path[2] === 'show') {
+ new ZenMode();
+ }
+ break;
+ case 'labels':
+ case 'graphs':
+ case 'compare':
+ case 'pipelines':
+ case 'forks':
+ case 'milestones':
+ case 'project_members':
+ case 'deploy_keys':
+ case 'builds':
+ case 'hooks':
+ case 'services':
+ case 'protected_branches':
+ shortcut_handler = new ShortcutsNavigation();
+ }
+ }
+ if (!shortcut_handler) {
+ return new Shortcuts();
+ }
+ };
+
+ Dispatcher.prototype.initSearch = function() {
+ if ($('.search').length) {
+ return new SearchAutocomplete();
+ }
+ };
+
+ return Dispatcher;
+
+})();
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
deleted file mode 100644
index afaa6407b05..00000000000
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ /dev/null
@@ -1,171 +0,0 @@
-$ ->
- new Dispatcher()
-
-class Dispatcher
- constructor: () ->
- @initSearch()
- @initPageScripts()
-
- initPageScripts: ->
- page = $('body').attr('data-page')
-
- unless page
- return false
-
- path = page.split(':')
- shortcut_handler = null
- switch page
- when 'projects:issues:index'
- Issuable.init()
- new IssuableBulkActions()
- shortcut_handler = new ShortcutsNavigation()
- when 'projects:issues:show'
- new Issue()
- shortcut_handler = new ShortcutsIssuable()
- new ZenMode()
- when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
- new Milestone()
- when 'dashboard:todos:index'
- new Todos()
- when 'projects:milestones:new', 'projects:milestones:edit'
- new ZenMode()
- new DueDateSelect()
- new GLForm($('.milestone-form'))
- when 'groups:milestones:new'
- new ZenMode()
- when 'projects:compare:show'
- new Diff()
- when 'projects:issues:new','projects:issues:edit'
- shortcut_handler = new ShortcutsNavigation()
- new GLForm($('.issue-form'))
- new IssuableForm($('.issue-form'))
- when 'projects:merge_requests:new', 'projects:merge_requests:edit'
- new Diff()
- shortcut_handler = new ShortcutsNavigation()
- new GLForm($('.merge-request-form'))
- new IssuableForm($('.merge-request-form'))
- when 'projects:tags:new'
- new ZenMode()
- new GLForm($('.tag-form'))
- when 'projects:releases:edit'
- new ZenMode()
- new GLForm($('.release-form'))
- when 'projects:merge_requests:show'
- new Diff()
- shortcut_handler = new ShortcutsIssuable(true)
- new ZenMode()
- new MergedButtons()
- when 'projects:merge_requests:commits', 'projects:merge_requests:builds'
- new MergedButtons()
- when "projects:merge_requests:diffs"
- new Diff()
- new ZenMode()
- new MergedButtons()
- when 'projects:merge_requests:index'
- shortcut_handler = new ShortcutsNavigation()
- Issuable.init()
- when 'dashboard:activity'
- new Activities()
- when 'dashboard:projects:starred'
- new Activities()
- when 'projects:commit:show'
- new Commit()
- new Diff()
- new ZenMode()
- shortcut_handler = new ShortcutsNavigation()
- when 'projects:commits:show', 'projects:activity'
- shortcut_handler = new ShortcutsNavigation()
- when 'projects:show'
- shortcut_handler = new ShortcutsNavigation()
-
- new NotificationsForm()
- new TreeView() if $('#tree-slider').length
- when 'groups:activity'
- new Activities()
- when 'groups:show'
- shortcut_handler = new ShortcutsNavigation()
- new NotificationsForm()
- new NotificationsDropdown()
- when 'groups:group_members:index'
- new GroupMembers()
- new UsersSelect()
- when 'projects:project_members:index'
- new ProjectMembers()
- new UsersSelect()
- when 'groups:new', 'groups:edit', 'admin:groups:edit', 'admin:groups:new'
- new GroupAvatar()
- when 'projects:tree:show'
- shortcut_handler = new ShortcutsNavigation()
- new TreeView()
- when 'projects:find_file:show'
- shortcut_handler = true
- when 'projects:blob:show', 'projects:blame:show'
- new LineHighlighter()
- shortcut_handler = new ShortcutsNavigation()
- new ShortcutsBlob true
- when 'projects:labels:new', 'projects:labels:edit'
- new Labels()
- when 'projects:labels:index'
- new LabelManager() if $('.prioritized-labels').length
- when 'projects:network:show'
- # Ensure we don't create a particular shortcut handler here. This is
- # already created, where the network graph is created.
- shortcut_handler = true
- when 'projects:forks:new'
- new ProjectFork()
- when 'projects:artifacts:browse'
- new BuildArtifacts()
- when 'projects:group_links:index'
- new GroupsSelect()
- when 'search:show'
- new Search()
-
- switch path.first()
- when 'admin'
- new Admin()
- switch path[1]
- when 'groups'
- new UsersSelect()
- when 'projects'
- new NamespaceSelects()
- when 'dashboard', 'root'
- shortcut_handler = new ShortcutsDashboardNavigation()
- when 'profiles'
- new NotificationsForm()
- new NotificationsDropdown()
- when 'projects'
- new Project()
- new ProjectAvatar()
- switch path[1]
- when 'compare'
- new CompareAutocomplete()
- when 'edit'
- shortcut_handler = new ShortcutsNavigation()
- new ProjectNew()
- when 'new'
- new ProjectNew()
- when 'show'
- new ProjectNew()
- new ProjectShow()
- new NotificationsDropdown()
- when 'wikis'
- new Wikis()
- shortcut_handler = new ShortcutsNavigation()
- new ZenMode()
- new GLForm($('.wiki-form'))
- when 'snippets'
- shortcut_handler = new ShortcutsNavigation()
- new ZenMode() if path[2] == 'show'
- when 'labels', 'graphs', 'compare', 'pipelines', 'forks', \
- 'milestones', 'project_members', 'deploy_keys', 'builds', \
- 'hooks', 'services', 'protected_branches'
- shortcut_handler = new ShortcutsNavigation()
-
- # If we haven't installed a custom shortcut handler, install the default one
- if not shortcut_handler
- new Shortcuts()
-
- initSearch: ->
-
- # Only when search form is present
- new SearchAutocomplete() if $('.search').length
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
new file mode 100644
index 00000000000..4bcdaafe3da
--- /dev/null
+++ b/app/assets/javascripts/dropzone_input.js
@@ -0,0 +1,215 @@
+
+/*= require markdown_preview */
+this.DropzoneInput = (function() {
+ function DropzoneInput(form) {
+ var $mdArea, alertAttr, alertClass, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divAlert, divHover, divSpinner, dropzone, form_dropzone, form_textarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, max_file_size, pasteText, project_uploads_path, showError, showSpinner, uploadFile, uploadProgress;
+ Dropzone.autoDiscover = false;
+ alertClass = "alert alert-danger alert-dismissable div-dropzone-alert";
+ alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\"";
+ divHover = "<div class=\"div-dropzone-hover\"></div>";
+ divSpinner = "<div class=\"div-dropzone-spinner\"></div>";
+ divAlert = "<div class=\"" + alertClass + "\"></div>";
+ iconPaperclip = "<i class=\"fa fa-paperclip div-dropzone-icon\"></i>";
+ iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>";
+ uploadProgress = $("<div class=\"div-dropzone-progress\"></div>");
+ btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>";
+ project_uploads_path = window.project_uploads_path || null;
+ max_file_size = gon.max_file_size || 10;
+ form_textarea = $(form).find(".js-gfm-input");
+ form_textarea.wrap("<div class=\"div-dropzone\"></div>");
+ form_textarea.on('paste', (function(_this) {
+ return function(event) {
+ return handlePaste(event);
+ };
+ })(this));
+ $mdArea = $(form_textarea).closest('.md-area');
+ $(form).setupMarkdownPreview();
+ form_dropzone = $(form).find('.div-dropzone');
+ form_dropzone.parent().addClass("div-dropzone-wrapper");
+ form_dropzone.append(divHover);
+ form_dropzone.find(".div-dropzone-hover").append(iconPaperclip);
+ form_dropzone.append(divSpinner);
+ form_dropzone.find(".div-dropzone-spinner").append(iconSpinner);
+ form_dropzone.find(".div-dropzone-spinner").append(uploadProgress);
+ form_dropzone.find(".div-dropzone-spinner").css({
+ "opacity": 0,
+ "display": "none"
+ });
+ dropzone = form_dropzone.dropzone({
+ url: project_uploads_path,
+ dictDefaultMessage: "",
+ clickable: true,
+ paramName: "file",
+ maxFilesize: max_file_size,
+ uploadMultiple: false,
+ headers: {
+ "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
+ },
+ previewContainer: false,
+ processing: function() {
+ return $(".div-dropzone-alert").alert("close");
+ },
+ dragover: function() {
+ $mdArea.addClass('is-dropzone-hover');
+ form.find(".div-dropzone-hover").css("opacity", 0.7);
+ },
+ dragleave: function() {
+ $mdArea.removeClass('is-dropzone-hover');
+ form.find(".div-dropzone-hover").css("opacity", 0);
+ },
+ drop: function() {
+ $mdArea.removeClass('is-dropzone-hover');
+ form.find(".div-dropzone-hover").css("opacity", 0);
+ form_textarea.focus();
+ },
+ success: function(header, response) {
+ pasteText(response.link.markdown);
+ },
+ error: function(temp) {
+ var checkIfMsgExists, errorAlert;
+ errorAlert = $(form).find('.error-alert');
+ checkIfMsgExists = errorAlert.children().length;
+ if (checkIfMsgExists === 0) {
+ errorAlert.append(divAlert);
+ $(".div-dropzone-alert").append(btnAlert + "Attaching the file failed.");
+ }
+ },
+ totaluploadprogress: function(totalUploadProgress) {
+ uploadProgress.text(Math.round(totalUploadProgress) + "%");
+ },
+ sending: function() {
+ form_dropzone.find(".div-dropzone-spinner").css({
+ "opacity": 0.7,
+ "display": "inherit"
+ });
+ },
+ queuecomplete: function() {
+ uploadProgress.text("");
+ $(".dz-preview").remove();
+ $(".markdown-area").trigger("input");
+ $(".div-dropzone-spinner").css({
+ "opacity": 0,
+ "display": "none"
+ });
+ }
+ });
+ child = $(dropzone[0]).children("textarea");
+ handlePaste = function(event) {
+ var filename, image, pasteEvent, text;
+ pasteEvent = event.originalEvent;
+ if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) {
+ image = isImage(pasteEvent);
+ if (image) {
+ event.preventDefault();
+ filename = getFilename(pasteEvent) || "image.png";
+ text = "{{" + filename + "}}";
+ pasteText(text);
+ return uploadFile(image.getAsFile(), filename);
+ }
+ }
+ };
+ isImage = function(data) {
+ var i, item;
+ i = 0;
+ while (i < data.clipboardData.items.length) {
+ item = data.clipboardData.items[i];
+ if (item.type.indexOf("image") !== -1) {
+ return item;
+ }
+ i++;
+ }
+ return false;
+ };
+ pasteText = function(text) {
+ var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
+ caretStart = $(child)[0].selectionStart;
+ caretEnd = $(child)[0].selectionEnd;
+ textEnd = $(child).val().length;
+ beforeSelection = $(child).val().substring(0, caretStart);
+ afterSelection = $(child).val().substring(caretEnd, textEnd);
+ $(child).val(beforeSelection + text + afterSelection);
+ child.get(0).setSelectionRange(caretStart + text.length, caretEnd + text.length);
+ return form_textarea.trigger("input");
+ };
+ getFilename = function(e) {
+ var value;
+ if (window.clipboardData && window.clipboardData.getData) {
+ value = window.clipboardData.getData("Text");
+ } else if (e.clipboardData && e.clipboardData.getData) {
+ value = e.clipboardData.getData("text/plain");
+ }
+ value = value.split("\r");
+ return value.first();
+ };
+ uploadFile = function(item, filename) {
+ var formData;
+ formData = new FormData();
+ formData.append("file", item, filename);
+ return $.ajax({
+ url: project_uploads_path,
+ type: "POST",
+ data: formData,
+ dataType: "json",
+ processData: false,
+ contentType: false,
+ headers: {
+ "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
+ },
+ beforeSend: function() {
+ showSpinner();
+ return closeAlertMessage();
+ },
+ success: function(e, textStatus, response) {
+ return insertToTextArea(filename, response.responseJSON.link.markdown);
+ },
+ error: function(response) {
+ return showError(response.responseJSON.message);
+ },
+ complete: function() {
+ return closeSpinner();
+ }
+ });
+ };
+ insertToTextArea = function(filename, url) {
+ return $(child).val(function(index, val) {
+ return val.replace("{{" + filename + "}}", url + "\n");
+ });
+ };
+ appendToTextArea = function(url) {
+ return $(child).val(function(index, val) {
+ return val + url + "\n";
+ });
+ };
+ showSpinner = function(e) {
+ return form.find(".div-dropzone-spinner").css({
+ "opacity": 0.7,
+ "display": "inherit"
+ });
+ };
+ closeSpinner = function() {
+ return form.find(".div-dropzone-spinner").css({
+ "opacity": 0,
+ "display": "none"
+ });
+ };
+ showError = function(message) {
+ var checkIfMsgExists, errorAlert;
+ errorAlert = $(form).find('.error-alert');
+ checkIfMsgExists = errorAlert.children().length;
+ if (checkIfMsgExists === 0) {
+ errorAlert.append(divAlert);
+ return $(".div-dropzone-alert").append(btnAlert + message);
+ }
+ };
+ closeAlertMessage = function() {
+ return form.find(".div-dropzone-alert").alert("close");
+ };
+ form.find(".markdown-selector").click(function(e) {
+ e.preventDefault();
+ $(this).closest('.gfm-form').find('.div-dropzone').click();
+ });
+ }
+
+ return DropzoneInput;
+
+})();
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
deleted file mode 100644
index 665246e2a7d..00000000000
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ /dev/null
@@ -1,201 +0,0 @@
-#= require markdown_preview
-
-class @DropzoneInput
- constructor: (form) ->
- Dropzone.autoDiscover = false
- alertClass = "alert alert-danger alert-dismissable div-dropzone-alert"
- alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\""
- divHover = "<div class=\"div-dropzone-hover\"></div>"
- divSpinner = "<div class=\"div-dropzone-spinner\"></div>"
- divAlert = "<div class=\"" + alertClass + "\"></div>"
- iconPaperclip = "<i class=\"fa fa-paperclip div-dropzone-icon\"></i>"
- iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>"
- uploadProgress = $("<div class=\"div-dropzone-progress\"></div>")
- btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
- project_uploads_path = window.project_uploads_path or null
- max_file_size = gon.max_file_size or 10
-
- form_textarea = $(form).find(".js-gfm-input")
- form_textarea.wrap "<div class=\"div-dropzone\"></div>"
- form_textarea.on 'paste', (event) =>
- handlePaste(event)
-
- $mdArea = $(form_textarea).closest('.md-area')
-
- $(form).setupMarkdownPreview()
-
- form_dropzone = $(form).find('.div-dropzone')
- form_dropzone.parent().addClass "div-dropzone-wrapper"
- form_dropzone.append divHover
- form_dropzone.find(".div-dropzone-hover").append iconPaperclip
- form_dropzone.append divSpinner
- form_dropzone.find(".div-dropzone-spinner").append iconSpinner
- form_dropzone.find(".div-dropzone-spinner").append uploadProgress
- form_dropzone.find(".div-dropzone-spinner").css
- "opacity": 0
- "display": "none"
-
- dropzone = form_dropzone.dropzone(
- url: project_uploads_path
- dictDefaultMessage: ""
- clickable: true
- paramName: "file"
- maxFilesize: max_file_size
- uploadMultiple: false
- headers:
- "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
-
- previewContainer: false
-
- processing: ->
- $(".div-dropzone-alert").alert "close"
-
- dragover: ->
- $mdArea.addClass 'is-dropzone-hover'
- form.find(".div-dropzone-hover").css "opacity", 0.7
- return
-
- dragleave: ->
- $mdArea.removeClass 'is-dropzone-hover'
- form.find(".div-dropzone-hover").css "opacity", 0
- return
-
- drop: ->
- $mdArea.removeClass 'is-dropzone-hover'
- form.find(".div-dropzone-hover").css "opacity", 0
- form_textarea.focus()
- return
-
- success: (header, response) ->
- pasteText response.link.markdown
- return
-
- error: (temp) ->
- errorAlert = $(form).find('.error-alert')
- checkIfMsgExists = errorAlert.children().length
- if checkIfMsgExists is 0
- errorAlert.append divAlert
- $(".div-dropzone-alert").append "#{btnAlert}Attaching the file failed."
- return
-
- totaluploadprogress: (totalUploadProgress) ->
- uploadProgress.text Math.round(totalUploadProgress) + "%"
- return
-
- sending: ->
- form_dropzone.find(".div-dropzone-spinner").css
- "opacity": 0.7
- "display": "inherit"
- return
-
- queuecomplete: ->
- uploadProgress.text ""
- $(".dz-preview").remove()
- $(".markdown-area").trigger "input"
- $(".div-dropzone-spinner").css
- "opacity": 0
- "display": "none"
- return
- )
-
- child = $(dropzone[0]).children("textarea")
-
- handlePaste = (event) ->
- pasteEvent = event.originalEvent
- if pasteEvent.clipboardData and pasteEvent.clipboardData.items
- image = isImage(pasteEvent)
- if image
- event.preventDefault()
-
- filename = getFilename(pasteEvent) or "image.png"
- text = "{{" + filename + "}}"
- pasteText(text)
- uploadFile image.getAsFile(), filename
-
- isImage = (data) ->
- i = 0
- while i < data.clipboardData.items.length
- item = data.clipboardData.items[i]
- if item.type.indexOf("image") isnt -1
- return item
- i++
- return false
-
- pasteText = (text) ->
- caretStart = $(child)[0].selectionStart
- caretEnd = $(child)[0].selectionEnd
- textEnd = $(child).val().length
-
- beforeSelection = $(child).val().substring 0, caretStart
- afterSelection = $(child).val().substring caretEnd, textEnd
- $(child).val beforeSelection + text + afterSelection
- child.get(0).setSelectionRange caretStart + text.length, caretEnd + text.length
- form_textarea.trigger "input"
-
- getFilename = (e) ->
- if window.clipboardData and window.clipboardData.getData
- value = window.clipboardData.getData("Text")
- else if e.clipboardData and e.clipboardData.getData
- value = e.clipboardData.getData("text/plain")
-
- value = value.split("\r")
- value.first()
-
- uploadFile = (item, filename) ->
- formData = new FormData()
- formData.append "file", item, filename
- $.ajax
- url: project_uploads_path
- type: "POST"
- data: formData
- dataType: "json"
- processData: false
- contentType: false
- headers:
- "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
-
- beforeSend: ->
- showSpinner()
- closeAlertMessage()
-
- success: (e, textStatus, response) ->
- insertToTextArea(filename, response.responseJSON.link.markdown)
-
- error: (response) ->
- showError(response.responseJSON.message)
-
- complete: ->
- closeSpinner()
-
- insertToTextArea = (filename, url) ->
- $(child).val (index, val) ->
- val.replace("{{" + filename + "}}", url + "\n")
-
- appendToTextArea = (url) ->
- $(child).val (index, val) ->
- val + url + "\n"
-
- showSpinner = (e) ->
- form.find(".div-dropzone-spinner").css
- "opacity": 0.7
- "display": "inherit"
-
- closeSpinner = ->
- form.find(".div-dropzone-spinner").css
- "opacity": 0
- "display": "none"
-
- showError = (message) ->
- errorAlert = $(form).find('.error-alert')
- checkIfMsgExists = errorAlert.children().length
- if checkIfMsgExists is 0
- errorAlert.append divAlert
- $(".div-dropzone-alert").append btnAlert + message
-
- closeAlertMessage = ->
- form.find(".div-dropzone-alert").alert "close"
-
- form.find(".markdown-selector").click (e) ->
- e.preventDefault()
- $(@).closest('.gfm-form').find('.div-dropzone').click()
- return
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
new file mode 100644
index 00000000000..0a660aa3259
--- /dev/null
+++ b/app/assets/javascripts/due_date_select.js
@@ -0,0 +1,101 @@
+this.DueDateSelect = (function() {
+ function DueDateSelect() {
+ var $datePicker, $dueDate, $loading;
+ $datePicker = $('.datepicker');
+ if ($datePicker.length) {
+ $dueDate = $('#milestone_due_date');
+ $datePicker.datepicker({
+ dateFormat: 'yy-mm-dd',
+ onSelect: function(dateText, inst) {
+ return $dueDate.val(dateText);
+ }
+ }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()));
+ }
+ $('.js-clear-due-date').on('click', function(e) {
+ e.preventDefault();
+ return $.datepicker._clearDate($datePicker);
+ });
+ $loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
+ $('.js-due-date-select').each(function(i, dropdown) {
+ var $block, $dropdown, $dropdownParent, $selectbox, $sidebarValue, $value, $valueContent, abilityName, addDueDate, fieldName, issueUpdateURL;
+ $dropdown = $(dropdown);
+ $dropdownParent = $dropdown.closest('.dropdown');
+ $datePicker = $dropdownParent.find('.js-due-date-calendar');
+ $block = $dropdown.closest('.block');
+ $selectbox = $dropdown.closest('.selectbox');
+ $value = $block.find('.value');
+ $valueContent = $block.find('.value-content');
+ $sidebarValue = $('.js-due-date-sidebar-value', $block);
+ fieldName = $dropdown.data('field-name');
+ abilityName = $dropdown.data('ability-name');
+ issueUpdateURL = $dropdown.data('issue-update');
+ $dropdown.glDropdown({
+ hidden: function() {
+ $selectbox.hide();
+ return $value.css('display', '');
+ }
+ });
+ addDueDate = function(isDropdown) {
+ var data, date, mediumDate, value;
+ value = $("input[name='" + fieldName + "']").val();
+ if (value !== '') {
+ date = new Date(value.replace(new RegExp('-', 'g'), ','));
+ mediumDate = $.datepicker.formatDate('M d, yy', date);
+ } else {
+ mediumDate = 'No due date';
+ }
+ data = {};
+ data[abilityName] = {};
+ data[abilityName].due_date = value;
+ return $.ajax({
+ type: 'PUT',
+ url: issueUpdateURL,
+ data: data,
+ dataType: 'json',
+ beforeSend: function() {
+ var cssClass;
+ $loading.fadeIn();
+ if (isDropdown) {
+ $dropdown.trigger('loading.gl.dropdown');
+ $selectbox.hide();
+ }
+ $value.css('display', '');
+ cssClass = Date.parse(mediumDate) ? 'bold' : 'no-value';
+ $valueContent.html("<span class='" + cssClass + "'>" + mediumDate + "</span>");
+ $sidebarValue.html(mediumDate);
+ if (value !== '') {
+ return $('.js-remove-due-date-holder').removeClass('hidden');
+ } else {
+ return $('.js-remove-due-date-holder').addClass('hidden');
+ }
+ }
+ }).done(function(data) {
+ if (isDropdown) {
+ $dropdown.trigger('loaded.gl.dropdown');
+ $dropdown.dropdown('toggle');
+ }
+ return $loading.fadeOut();
+ });
+ };
+ $block.on('click', '.js-remove-due-date', function(e) {
+ e.preventDefault();
+ $("input[name='" + fieldName + "']").val('');
+ return addDueDate(false);
+ });
+ return $datePicker.datepicker({
+ dateFormat: 'yy-mm-dd',
+ defaultDate: $("input[name='" + fieldName + "']").val(),
+ altField: "input[name='" + fieldName + "']",
+ onSelect: function() {
+ return addDueDate(true);
+ }
+ });
+ });
+ $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', function(e) {
+ return e.stopImmediatePropagation();
+ });
+ }
+
+ return DueDateSelect;
+
+})();
diff --git a/app/assets/javascripts/due_date_select.js.coffee b/app/assets/javascripts/due_date_select.js.coffee
deleted file mode 100644
index d65c018dad5..00000000000
--- a/app/assets/javascripts/due_date_select.js.coffee
+++ /dev/null
@@ -1,99 +0,0 @@
-class @DueDateSelect
- constructor: ->
- # Milestone edit/new form
- $datePicker = $('.datepicker')
-
- if $datePicker.length
- $dueDate = $('#milestone_due_date')
- $datePicker.datepicker
- dateFormat: 'yy-mm-dd'
- onSelect: (dateText, inst) ->
- $dueDate.val(dateText)
- .datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()))
-
- $('.js-clear-due-date').on 'click', (e) ->
- e.preventDefault()
- $.datepicker._clearDate($datePicker)
-
- # Issuable sidebar
- $loading = $('.js-issuable-update .due_date')
- .find('.block-loading')
- .hide()
-
- $('.js-due-date-select').each (i, dropdown) ->
- $dropdown = $(dropdown)
- $dropdownParent = $dropdown.closest('.dropdown')
- $datePicker = $dropdownParent.find('.js-due-date-calendar')
- $block = $dropdown.closest('.block')
- $selectbox = $dropdown.closest('.selectbox')
- $value = $block.find('.value')
- $valueContent = $block.find('.value-content')
- $sidebarValue = $('.js-due-date-sidebar-value', $block)
-
- fieldName = $dropdown.data('field-name')
- abilityName = $dropdown.data('ability-name')
- issueUpdateURL = $dropdown.data('issue-update')
-
- $dropdown.glDropdown(
- hidden: ->
- $selectbox.hide()
- $value.css('display', '')
- )
-
- addDueDate = (isDropdown) ->
- # Create the post date
- value = $("input[name='#{fieldName}']").val()
-
- if value isnt ''
- date = new Date value.replace(new RegExp('-', 'g'), ',')
- mediumDate = $.datepicker.formatDate 'M d, yy', date
- else
- mediumDate = 'No due date'
-
- data = {}
- data[abilityName] = {}
- data[abilityName].due_date = value
-
- $.ajax(
- type: 'PUT'
- url: issueUpdateURL
- data: data
- dataType: 'json'
- beforeSend: ->
- $loading.fadeIn()
- if isDropdown
- $dropdown.trigger('loading.gl.dropdown')
- $selectbox.hide()
- $value.css('display', '')
-
- cssClass = if Date.parse(mediumDate) then 'bold' else 'no-value'
- $valueContent.html("<span class='#{cssClass}'>#{mediumDate}</span>")
- $sidebarValue.html(mediumDate)
-
- if value isnt ''
- $('.js-remove-due-date-holder').removeClass 'hidden'
- else
- $('.js-remove-due-date-holder').addClass 'hidden'
- ).done (data) ->
- if isDropdown
- $dropdown.trigger('loaded.gl.dropdown')
- $dropdown.dropdown('toggle')
- $loading.fadeOut()
-
- $block.on 'click', '.js-remove-due-date', (e) ->
- e.preventDefault()
- $("input[name='#{fieldName}']").val ''
- addDueDate(false)
-
- $datePicker.datepicker(
- dateFormat: 'yy-mm-dd',
- defaultDate: $("input[name='#{fieldName}']").val()
- altField: "input[name='#{fieldName}']"
- onSelect: ->
- addDueDate(true)
- )
-
- $(document)
- .off 'click', '.ui-datepicker-header a'
- .on 'click', '.ui-datepicker-header a', (e) ->
- e.stopImmediatePropagation()
diff --git a/app/assets/javascripts/extensions/jquery.js b/app/assets/javascripts/extensions/jquery.js
new file mode 100644
index 00000000000..bc0a02cee29
--- /dev/null
+++ b/app/assets/javascripts/extensions/jquery.js
@@ -0,0 +1,11 @@
+$.fn.extend({
+ disable: function() {
+ return $(this).attr('disabled', 'disabled').addClass('disabled');
+ }
+});
+
+$.fn.extend({
+ enable: function() {
+ return $(this).removeAttr('disabled').removeClass('disabled');
+ }
+});
diff --git a/app/assets/javascripts/extensions/jquery.js.coffee b/app/assets/javascripts/extensions/jquery.js.coffee
deleted file mode 100644
index 0a9db8eb5ef..00000000000
--- a/app/assets/javascripts/extensions/jquery.js.coffee
+++ /dev/null
@@ -1,11 +0,0 @@
-# Disable an element and add the 'disabled' Bootstrap class
-$.fn.extend disable: ->
- $(@)
- .attr('disabled', 'disabled')
- .addClass('disabled')
-
-# Enable an element and remove the 'disabled' Bootstrap class
-$.fn.extend enable: ->
- $(@)
- .removeAttr('disabled')
- .removeClass('disabled')
diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js
new file mode 100644
index 00000000000..f7ce9a7139c
--- /dev/null
+++ b/app/assets/javascripts/files_comment_button.js
@@ -0,0 +1,138 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.FilesCommentButton = (function() {
+ var COMMENT_BUTTON_CLASS, COMMENT_BUTTON_TEMPLATE, DEBOUNCE_TIMEOUT_DURATION, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
+
+ COMMENT_BUTTON_CLASS = '.add-diff-note';
+
+ COMMENT_BUTTON_TEMPLATE = _.template('<button name="button" type="submit" class="btn <%- COMMENT_BUTTON_CLASS %> js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>');
+
+ LINE_HOLDER_CLASS = '.line_holder';
+
+ LINE_NUMBER_CLASS = 'diff-line-num';
+
+ LINE_CONTENT_CLASS = 'line_content';
+
+ UNFOLDABLE_LINE_CLASS = 'js-unfold';
+
+ EMPTY_CELL_CLASS = 'empty-cell';
+
+ OLD_LINE_CLASS = 'old_line';
+
+ LINE_COLUMN_CLASSES = "." + LINE_NUMBER_CLASS + ", .line_content";
+
+ TEXT_FILE_SELECTOR = '.text-file';
+
+ DEBOUNCE_TIMEOUT_DURATION = 100;
+
+ function FilesCommentButton(filesContainerElement) {
+ var debounce;
+ this.filesContainerElement = filesContainerElement;
+ this.destroy = bind(this.destroy, this);
+ this.render = bind(this.render, this);
+ this.VIEW_TYPE = $('input#view[type=hidden]').val();
+ debounce = _.debounce(this.render, DEBOUNCE_TIMEOUT_DURATION);
+ $(document).off('mouseover', LINE_COLUMN_CLASSES).off('mouseleave', LINE_COLUMN_CLASSES).on('mouseover', LINE_COLUMN_CLASSES, debounce).on('mouseleave', LINE_COLUMN_CLASSES, this.destroy);
+ }
+
+ FilesCommentButton.prototype.render = function(e) {
+ var $currentTarget, buttonParentElement, lineContentElement, textFileElement;
+ $currentTarget = $(e.currentTarget);
+ buttonParentElement = this.getButtonParent($currentTarget);
+ if (!this.shouldRender(e, buttonParentElement)) {
+ return;
+ }
+ textFileElement = this.getTextFileElement($currentTarget);
+ lineContentElement = this.getLineContent($currentTarget);
+ buttonParentElement.append(this.buildButton({
+ noteableType: textFileElement.attr('data-noteable-type'),
+ noteableID: textFileElement.attr('data-noteable-id'),
+ commitID: textFileElement.attr('data-commit-id'),
+ noteType: lineContentElement.attr('data-note-type'),
+ position: lineContentElement.attr('data-position'),
+ lineType: lineContentElement.attr('data-line-type'),
+ discussionID: lineContentElement.attr('data-discussion-id'),
+ lineCode: lineContentElement.attr('data-line-code')
+ }));
+ };
+
+ FilesCommentButton.prototype.destroy = function(e) {
+ if (this.isMovingToSameType(e)) {
+ return;
+ }
+ $(COMMENT_BUTTON_CLASS, this.getButtonParent($(e.currentTarget))).remove();
+ };
+
+ FilesCommentButton.prototype.buildButton = function(buttonAttributes) {
+ var initializedButtonTemplate;
+ initializedButtonTemplate = COMMENT_BUTTON_TEMPLATE({
+ COMMENT_BUTTON_CLASS: COMMENT_BUTTON_CLASS.substr(1)
+ });
+ return $(initializedButtonTemplate).attr({
+ 'data-noteable-type': buttonAttributes.noteableType,
+ 'data-noteable-id': buttonAttributes.noteableID,
+ 'data-commit-id': buttonAttributes.commitID,
+ 'data-note-type': buttonAttributes.noteType,
+ 'data-line-code': buttonAttributes.lineCode,
+ 'data-position': buttonAttributes.position,
+ 'data-discussion-id': buttonAttributes.discussionID,
+ 'data-line-type': buttonAttributes.lineType
+ });
+ };
+
+ FilesCommentButton.prototype.getTextFileElement = function(hoveredElement) {
+ return $(hoveredElement.closest(TEXT_FILE_SELECTOR));
+ };
+
+ FilesCommentButton.prototype.getLineContent = function(hoveredElement) {
+ if (hoveredElement.hasClass(LINE_CONTENT_CLASS)) {
+ return hoveredElement;
+ }
+ if (this.VIEW_TYPE === 'inline') {
+ return $(hoveredElement).closest(LINE_HOLDER_CLASS).find("." + LINE_CONTENT_CLASS);
+ } else {
+ return $(hoveredElement).next("." + LINE_CONTENT_CLASS);
+ }
+ };
+
+ FilesCommentButton.prototype.getButtonParent = function(hoveredElement) {
+ if (this.VIEW_TYPE === 'inline') {
+ if (hoveredElement.hasClass(OLD_LINE_CLASS)) {
+ return hoveredElement;
+ }
+ return hoveredElement.parent().find("." + OLD_LINE_CLASS);
+ } else {
+ if (hoveredElement.hasClass(LINE_NUMBER_CLASS)) {
+ return hoveredElement;
+ }
+ return $(hoveredElement).prev("." + LINE_NUMBER_CLASS);
+ }
+ };
+
+ FilesCommentButton.prototype.isMovingToSameType = function(e) {
+ var newButtonParent;
+ newButtonParent = this.getButtonParent($(e.toElement));
+ if (!newButtonParent) {
+ return false;
+ }
+ return newButtonParent.is(this.getButtonParent($(e.currentTarget)));
+ };
+
+ FilesCommentButton.prototype.shouldRender = function(e, buttonParentElement) {
+ return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) && $(COMMENT_BUTTON_CLASS, buttonParentElement).length === 0;
+ };
+
+ return FilesCommentButton;
+
+})();
+
+$.fn.filesCommentButton = function() {
+ if (!(this && (this.parent().data('can-create-note') != null))) {
+ return;
+ }
+ return this.each(function() {
+ if (!$.data(this, 'filesCommentButton')) {
+ return $.data(this, 'filesCommentButton', new FilesCommentButton($(this)));
+ }
+ });
+};
diff --git a/app/assets/javascripts/files_comment_button.js.coffee b/app/assets/javascripts/files_comment_button.js.coffee
deleted file mode 100644
index 5ab82c39fcd..00000000000
--- a/app/assets/javascripts/files_comment_button.js.coffee
+++ /dev/null
@@ -1,98 +0,0 @@
-class @FilesCommentButton
- COMMENT_BUTTON_CLASS = '.add-diff-note'
- COMMENT_BUTTON_TEMPLATE = _.template '<button name="button" type="submit" class="btn <%- COMMENT_BUTTON_CLASS %> js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>'
- LINE_HOLDER_CLASS = '.line_holder'
- LINE_NUMBER_CLASS = 'diff-line-num'
- LINE_CONTENT_CLASS = 'line_content'
- UNFOLDABLE_LINE_CLASS = 'js-unfold'
- EMPTY_CELL_CLASS = 'empty-cell'
- OLD_LINE_CLASS = 'old_line'
- LINE_COLUMN_CLASSES = ".#{LINE_NUMBER_CLASS}, .line_content"
- TEXT_FILE_SELECTOR = '.text-file'
- DEBOUNCE_TIMEOUT_DURATION = 100
-
- constructor: (@filesContainerElement) ->
- @VIEW_TYPE = $('input#view[type=hidden]').val()
-
- debounce = _.debounce @render, DEBOUNCE_TIMEOUT_DURATION
-
- $(document)
- .off 'mouseover', LINE_COLUMN_CLASSES
- .off 'mouseleave', LINE_COLUMN_CLASSES
- .on 'mouseover', LINE_COLUMN_CLASSES, debounce
- .on 'mouseleave', LINE_COLUMN_CLASSES, @destroy
-
- render: (e) =>
- $currentTarget = $(e.currentTarget)
- buttonParentElement = @getButtonParent $currentTarget
- return unless @shouldRender e, buttonParentElement
-
- textFileElement = @getTextFileElement $currentTarget
- lineContentElement = @getLineContent $currentTarget
-
- buttonParentElement.append @buildButton
- noteableType: textFileElement.attr 'data-noteable-type'
- noteableID: textFileElement.attr 'data-noteable-id'
- commitID: textFileElement.attr 'data-commit-id'
- noteType: lineContentElement.attr 'data-note-type'
- position: lineContentElement.attr 'data-position'
- lineType: lineContentElement.attr 'data-line-type'
- discussionID: lineContentElement.attr 'data-discussion-id'
- lineCode: lineContentElement.attr 'data-line-code'
- return
-
- destroy: (e) =>
- return if @isMovingToSameType e
- $(COMMENT_BUTTON_CLASS, @getButtonParent $(e.currentTarget)).remove()
- return
-
- buildButton: (buttonAttributes) ->
- initializedButtonTemplate = COMMENT_BUTTON_TEMPLATE
- COMMENT_BUTTON_CLASS: COMMENT_BUTTON_CLASS.substr 1
- $(initializedButtonTemplate).attr
- 'data-noteable-type': buttonAttributes.noteableType
- 'data-noteable-id': buttonAttributes.noteableID
- 'data-commit-id': buttonAttributes.commitID
- 'data-note-type': buttonAttributes.noteType
- 'data-line-code': buttonAttributes.lineCode
- 'data-position': buttonAttributes.position
- 'data-discussion-id': buttonAttributes.discussionID
- 'data-line-type': buttonAttributes.lineType
-
- getTextFileElement: (hoveredElement) ->
- $(hoveredElement.closest TEXT_FILE_SELECTOR)
-
- getLineContent: (hoveredElement) ->
- return hoveredElement if hoveredElement.hasClass LINE_CONTENT_CLASS
-
- if @VIEW_TYPE is 'inline'
- return $(hoveredElement).closest(LINE_HOLDER_CLASS).find ".#{LINE_CONTENT_CLASS}"
- else
- return $(hoveredElement).next ".#{LINE_CONTENT_CLASS}"
-
- getButtonParent: (hoveredElement) ->
- if @VIEW_TYPE is 'inline'
- return hoveredElement if hoveredElement.hasClass OLD_LINE_CLASS
-
- hoveredElement.parent().find ".#{OLD_LINE_CLASS}"
- else
- return hoveredElement if hoveredElement.hasClass LINE_NUMBER_CLASS
-
- $(hoveredElement).prev ".#{LINE_NUMBER_CLASS}"
-
- isMovingToSameType: (e) ->
- newButtonParent = @getButtonParent $(e.toElement)
- return false unless newButtonParent
- newButtonParent.is @getButtonParent $(e.currentTarget)
-
- shouldRender: (e, buttonParentElement) ->
- (not buttonParentElement.hasClass(EMPTY_CELL_CLASS) and \
- not buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) and \
- $(COMMENT_BUTTON_CLASS, buttonParentElement).length is 0)
-
-$.fn.filesCommentButton = ->
- return unless this and @parent().data('can-create-note')?
-
- @each ->
- unless $.data this, 'filesCommentButton'
- $.data this, 'filesCommentButton', new FilesCommentButton $(this)
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
new file mode 100644
index 00000000000..1a2c36fdbcc
--- /dev/null
+++ b/app/assets/javascripts/flash.js
@@ -0,0 +1,40 @@
+this.Flash = (function() {
+ var hideFlash;
+
+ hideFlash = function() {
+ return $(this).fadeOut();
+ };
+
+ function Flash(message, type, parent) {
+ var flash, textDiv;
+ if (type == null) {
+ type = 'alert';
+ }
+ if (parent == null) {
+ parent = null;
+ }
+ if (parent) {
+ this.flashContainer = parent.find('.flash-container');
+ } else {
+ this.flashContainer = $('.flash-container-page');
+ }
+ this.flashContainer.html('');
+ flash = $('<div/>', {
+ "class": "flash-" + type
+ });
+ flash.on('click', hideFlash);
+ textDiv = $('<div/>', {
+ "class": 'flash-text',
+ text: message
+ });
+ textDiv.appendTo(flash);
+ if (this.flashContainer.parent().hasClass('content-wrapper')) {
+ textDiv.addClass('container-fluid container-limited');
+ }
+ flash.appendTo(this.flashContainer);
+ this.flashContainer.show();
+ }
+
+ return Flash;
+
+})();
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
deleted file mode 100644
index 5a493041538..00000000000
--- a/app/assets/javascripts/flash.js.coffee
+++ /dev/null
@@ -1,28 +0,0 @@
-class @Flash
- hideFlash = -> $(@).fadeOut()
-
- constructor: (message, type = 'alert', parent = null)->
- if parent
- @flashContainer = parent.find('.flash-container')
- else
- @flashContainer = $('.flash-container-page')
-
- @flashContainer.html('')
-
- flash = $('<div/>',
- class: "flash-#{type}"
- )
- flash.on 'click', hideFlash
-
- textDiv = $('<div/>',
- class: 'flash-text',
- text: message
- )
- textDiv.appendTo(flash)
-
- if @flashContainer.parent().hasClass('content-wrapper')
- textDiv.addClass('container-fluid container-limited')
-
- flash.appendTo(@flashContainer)
- @flashContainer.show()
-
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
new file mode 100644
index 00000000000..46089baa290
--- /dev/null
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -0,0 +1,269 @@
+if (window.GitLab == null) {
+ window.GitLab = {};
+}
+
+GitLab.GfmAutoComplete = {
+ dataLoading: false,
+ dataLoaded: false,
+ cachedData: {},
+ dataSource: '',
+ Emoji: {
+ template: '<li>${name} <img alt="${name}" height="20" src="${path}" width="20" /></li>'
+ },
+ Members: {
+ template: '<li>${username} <small>${title}</small></li>'
+ },
+ Labels: {
+ template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
+ },
+ Issues: {
+ template: '<li><small>${id}</small> ${title}</li>'
+ },
+ Milestones: {
+ template: '<li>${title}</li>'
+ },
+ Loading: {
+ template: '<li><i class="fa fa-refresh fa-spin"></i> Loading...</li>'
+ },
+ DefaultOptions: {
+ sorter: function(query, items, searchKey) {
+ if ((items[0].name != null) && items[0].name === 'loading') {
+ return items;
+ }
+ return $.fn.atwho["default"].callbacks.sorter(query, items, searchKey);
+ },
+ filter: function(query, data, searchKey) {
+ if (data[0] === 'loading') {
+ return data;
+ }
+ return $.fn.atwho["default"].callbacks.filter(query, data, searchKey);
+ },
+ beforeInsert: function(value) {
+ if (!GitLab.GfmAutoComplete.dataLoaded) {
+ return this.at;
+ } else {
+ return value;
+ }
+ }
+ },
+ setup: function(wrap) {
+ this.input = $('.js-gfm-input');
+ this.destroyAtWho();
+ this.setupAtWho();
+ if (this.dataSource) {
+ if (!this.dataLoading && !this.cachedData) {
+ this.dataLoading = true;
+ setTimeout((function(_this) {
+ return function() {
+ var fetch;
+ fetch = _this.fetchData(_this.dataSource);
+ return fetch.done(function(data) {
+ _this.dataLoading = false;
+ return _this.loadData(data);
+ });
+ };
+ })(this), 1000);
+ }
+ if (this.cachedData != null) {
+ return this.loadData(this.cachedData);
+ }
+ }
+ },
+ setupAtWho: function() {
+ this.input.atwho({
+ at: ':',
+ displayTpl: (function(_this) {
+ return function(value) {
+ if (value.path != null) {
+ return _this.Emoji.template;
+ } else {
+ return _this.Loading.template;
+ }
+ };
+ })(this),
+ insertTpl: ':${name}:',
+ data: ['loading'],
+ callbacks: {
+ sorter: this.DefaultOptions.sorter,
+ filter: this.DefaultOptions.filter,
+ beforeInsert: this.DefaultOptions.beforeInsert
+ }
+ });
+ this.input.atwho({
+ at: '@',
+ displayTpl: (function(_this) {
+ return function(value) {
+ if (value.username != null) {
+ return _this.Members.template;
+ } else {
+ return _this.Loading.template;
+ }
+ };
+ })(this),
+ insertTpl: '${atwho-at}${username}',
+ searchKey: 'search',
+ data: ['loading'],
+ callbacks: {
+ sorter: this.DefaultOptions.sorter,
+ filter: this.DefaultOptions.filter,
+ beforeInsert: this.DefaultOptions.beforeInsert,
+ beforeSave: function(members) {
+ return $.map(members, function(m) {
+ var title;
+ if (m.username == null) {
+ return m;
+ }
+ title = m.name;
+ if (m.count) {
+ title += " (" + m.count + ")";
+ }
+ return {
+ username: m.username,
+ title: sanitize(title),
+ search: sanitize(m.username + " " + m.name)
+ };
+ });
+ }
+ }
+ });
+ this.input.atwho({
+ at: '#',
+ alias: 'issues',
+ searchKey: 'search',
+ displayTpl: (function(_this) {
+ return function(value) {
+ if (value.title != null) {
+ return _this.Issues.template;
+ } else {
+ return _this.Loading.template;
+ }
+ };
+ })(this),
+ data: ['loading'],
+ insertTpl: '${atwho-at}${id}',
+ callbacks: {
+ sorter: this.DefaultOptions.sorter,
+ filter: this.DefaultOptions.filter,
+ beforeInsert: this.DefaultOptions.beforeInsert,
+ beforeSave: function(issues) {
+ return $.map(issues, function(i) {
+ if (i.title == null) {
+ return i;
+ }
+ return {
+ id: i.iid,
+ title: sanitize(i.title),
+ search: i.iid + " " + i.title
+ };
+ });
+ }
+ }
+ });
+ this.input.atwho({
+ at: '%',
+ alias: 'milestones',
+ searchKey: 'search',
+ displayTpl: (function(_this) {
+ return function(value) {
+ if (value.title != null) {
+ return _this.Milestones.template;
+ } else {
+ return _this.Loading.template;
+ }
+ };
+ })(this),
+ insertTpl: '${atwho-at}"${title}"',
+ data: ['loading'],
+ callbacks: {
+ beforeSave: function(milestones) {
+ return $.map(milestones, function(m) {
+ if (m.title == null) {
+ return m;
+ }
+ return {
+ id: m.iid,
+ title: sanitize(m.title),
+ search: "" + m.title
+ };
+ });
+ }
+ }
+ });
+ this.input.atwho({
+ at: '!',
+ alias: 'mergerequests',
+ searchKey: 'search',
+ displayTpl: (function(_this) {
+ return function(value) {
+ if (value.title != null) {
+ return _this.Issues.template;
+ } else {
+ return _this.Loading.template;
+ }
+ };
+ })(this),
+ data: ['loading'],
+ insertTpl: '${atwho-at}${id}',
+ callbacks: {
+ sorter: this.DefaultOptions.sorter,
+ filter: this.DefaultOptions.filter,
+ beforeInsert: this.DefaultOptions.beforeInsert,
+ beforeSave: function(merges) {
+ return $.map(merges, function(m) {
+ if (m.title == null) {
+ return m;
+ }
+ return {
+ id: m.iid,
+ title: sanitize(m.title),
+ search: m.iid + " " + m.title
+ };
+ });
+ }
+ }
+ });
+ return this.input.atwho({
+ at: '~',
+ alias: 'labels',
+ searchKey: 'search',
+ displayTpl: this.Labels.template,
+ insertTpl: '${atwho-at}${title}',
+ callbacks: {
+ beforeSave: function(merges) {
+ var sanitizeLabelTitle;
+ sanitizeLabelTitle = function(title) {
+ if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) {
+ return "\"" + (sanitize(title)) + "\"";
+ } else {
+ return sanitize(title);
+ }
+ };
+ return $.map(merges, function(m) {
+ return {
+ title: sanitizeLabelTitle(m.title),
+ color: m.color,
+ search: "" + m.title
+ };
+ });
+ }
+ }
+ });
+ },
+ destroyAtWho: function() {
+ return this.input.atwho('destroy');
+ },
+ fetchData: function(dataSource) {
+ return $.getJSON(dataSource);
+ },
+ loadData: function(data) {
+ this.cachedData = data;
+ this.dataLoaded = true;
+ this.input.atwho('load', '@', data.members);
+ this.input.atwho('load', 'issues', data.issues);
+ this.input.atwho('load', 'milestones', data.milestones);
+ this.input.atwho('load', 'mergerequests', data.mergerequests);
+ this.input.atwho('load', ':', data.emojis);
+ this.input.atwho('load', '~', data.labels);
+ return $(':focus').trigger('keyup');
+ }
+};
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
deleted file mode 100644
index 4a851d9c9fb..00000000000
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ /dev/null
@@ -1,228 +0,0 @@
-# Creates the variables for setting up GFM auto-completion
-
-window.GitLab ?= {}
-GitLab.GfmAutoComplete =
- dataLoading: false
- dataLoaded: false
- cachedData: {}
- dataSource: ''
-
- # Emoji
- Emoji:
- template: '<li>${name} <img alt="${name}" height="20" src="${path}" width="20" /></li>'
-
- # Team Members
- Members:
- template: '<li>${username} <small>${title}</small></li>'
-
- Labels:
- template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
-
- # Issues and MergeRequests
- Issues:
- template: '<li><small>${id}</small> ${title}</li>'
-
- # Milestones
- Milestones:
- template: '<li>${title}</li>'
-
- Loading:
- template: '<li><i class="fa fa-refresh fa-spin"></i> Loading...</li>'
-
- DefaultOptions:
- sorter: (query, items, searchKey) ->
- return items if items[0].name? and items[0].name is 'loading'
-
- $.fn.atwho.default.callbacks.sorter(query, items, searchKey)
- filter: (query, data, searchKey) ->
- return data if data[0] is 'loading'
-
- $.fn.atwho.default.callbacks.filter(query, data, searchKey)
- beforeInsert: (value) ->
- if not GitLab.GfmAutoComplete.dataLoaded
- @at
- else
- value
-
- # Add GFM auto-completion to all input fields, that accept GFM input.
- setup: (wrap) ->
- @input = $('.js-gfm-input')
-
- # destroy previous instances
- @destroyAtWho()
-
- # set up instances
- @setupAtWho()
-
- if @dataSource
- if not @dataLoading and not @cachedData
- @dataLoading = true
-
- # We should wait until initializations are done
- # and only trigger the last .setup since
- # The previous .dataSource belongs to the previous issuable
- # and the last one will have the **proper** .dataSource property
- # TODO: Make this a singleton and turn off events when moving to another page
- setTimeout( =>
- fetch = @fetchData(@dataSource)
- fetch.done (data) =>
- @dataLoading = false
- @loadData(data)
- , 1000)
-
- if @cachedData?
- @loadData(@cachedData)
-
- setupAtWho: ->
- # Emoji
- @input.atwho
- at: ':'
- displayTpl: (value) =>
- if value.path?
- @Emoji.template
- else
- @Loading.template
- insertTpl: ':${name}:'
- data: ['loading']
- callbacks:
- sorter: @DefaultOptions.sorter
- filter: @DefaultOptions.filter
- beforeInsert: @DefaultOptions.beforeInsert
-
- # Team Members
- @input.atwho
- at: '@'
- displayTpl: (value) =>
- if value.username?
- @Members.template
- else
- @Loading.template
- insertTpl: '${atwho-at}${username}'
- searchKey: 'search'
- data: ['loading']
- callbacks:
- sorter: @DefaultOptions.sorter
- filter: @DefaultOptions.filter
- beforeInsert: @DefaultOptions.beforeInsert
- beforeSave: (members) ->
- $.map members, (m) ->
- return m if not m.username?
-
- title = m.name
- title += " (#{m.count})" if m.count
-
- username: m.username
- title: sanitize(title)
- search: sanitize("#{m.username} #{m.name}")
-
- @input.atwho
- at: '#'
- alias: 'issues'
- searchKey: 'search'
- displayTpl: (value) =>
- if value.title?
- @Issues.template
- else
- @Loading.template
- data: ['loading']
- insertTpl: '${atwho-at}${id}'
- callbacks:
- sorter: @DefaultOptions.sorter
- filter: @DefaultOptions.filter
- beforeInsert: @DefaultOptions.beforeInsert
- beforeSave: (issues) ->
- $.map issues, (i) ->
- return i if not i.title?
-
- id: i.iid
- title: sanitize(i.title)
- search: "#{i.iid} #{i.title}"
-
- @input.atwho
- at: '%'
- alias: 'milestones'
- searchKey: 'search'
- displayTpl: (value) =>
- if value.title?
- @Milestones.template
- else
- @Loading.template
- insertTpl: '${atwho-at}"${title}"'
- data: ['loading']
- callbacks:
- beforeSave: (milestones) ->
- $.map milestones, (m) ->
- return m if not m.title?
-
- id: m.iid
- title: sanitize(m.title)
- search: "#{m.title}"
-
- @input.atwho
- at: '!'
- alias: 'mergerequests'
- searchKey: 'search'
- displayTpl: (value) =>
- if value.title?
- @Issues.template
- else
- @Loading.template
- data: ['loading']
- insertTpl: '${atwho-at}${id}'
- callbacks:
- sorter: @DefaultOptions.sorter
- filter: @DefaultOptions.filter
- beforeInsert: @DefaultOptions.beforeInsert
- beforeSave: (merges) ->
- $.map merges, (m) ->
- return m if not m.title?
-
- id: m.iid
- title: sanitize(m.title)
- search: "#{m.iid} #{m.title}"
-
- @input.atwho
- at: '~'
- alias: 'labels'
- searchKey: 'search'
- displayTpl: @Labels.template
- insertTpl: '${atwho-at}${title}'
- callbacks:
- beforeSave: (merges) ->
- sanitizeLabelTitle = (title)->
- if /[\w\?&]+\s+[\w\?&]+/g.test(title)
- "\"#{sanitize(title)}\""
- else
- sanitize(title)
-
- $.map merges, (m) ->
- title: sanitizeLabelTitle(m.title)
- color: m.color
- search: "#{m.title}"
-
- destroyAtWho: ->
- @input.atwho('destroy')
-
- fetchData: (dataSource) ->
- $.getJSON(dataSource)
-
- loadData: (data) ->
- @cachedData = data
- @dataLoaded = true
-
- # load members
- @input.atwho 'load', '@', data.members
- # load issues
- @input.atwho 'load', 'issues', data.issues
- # load milestones
- @input.atwho 'load', 'milestones', data.milestones
- # load merge requests
- @input.atwho 'load', 'mergerequests', data.mergerequests
- # load emojis
- @input.atwho 'load', ':', data.emojis
- # load labels
- @input.atwho 'load', '~', data.labels
-
- # This trigger at.js again
- # otherwise we would be stuck with loading until the user types
- $(':focus').trigger('keyup')
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
new file mode 100644
index 00000000000..de665e3144f
--- /dev/null
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -0,0 +1,702 @@
+var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+GitLabDropdownFilter = (function() {
+ var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS;
+
+ BLUR_KEYCODES = [27, 40];
+
+ ARROW_KEY_CODES = [38, 40];
+
+ HAS_VALUE_CLASS = "has-value";
+
+ function GitLabDropdownFilter(input, options) {
+ var $clearButton, $inputContainer, ref, timeout;
+ this.input = input;
+ this.options = options;
+ this.filterInputBlur = (ref = this.options.filterInputBlur) != null ? ref : true;
+ $inputContainer = this.input.parent();
+ $clearButton = $inputContainer.find('.js-dropdown-input-clear');
+ this.indeterminateIds = [];
+ $clearButton.on('click', (function(_this) {
+ return function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ return _this.input.val('').trigger('keyup').focus();
+ };
+ })(this));
+ timeout = "";
+ this.input.on("keyup", (function(_this) {
+ return function(e) {
+ var keyCode;
+ keyCode = e.which;
+ if (ARROW_KEY_CODES.indexOf(keyCode) >= 0) {
+ return;
+ }
+ if (_this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
+ $inputContainer.addClass(HAS_VALUE_CLASS);
+ } else if (_this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
+ $inputContainer.removeClass(HAS_VALUE_CLASS);
+ }
+ if (keyCode === 13) {
+ return false;
+ }
+ if (_this.options.remote) {
+ clearTimeout(timeout);
+ return timeout = setTimeout(function() {
+ var blur_field;
+ blur_field = _this.shouldBlur(keyCode);
+ if (blur_field && _this.filterInputBlur) {
+ _this.input.blur();
+ }
+ return _this.options.query(_this.input.val(), function(data) {
+ return _this.options.callback(data);
+ });
+ }, 250);
+ } else {
+ return _this.filter(_this.input.val());
+ }
+ };
+ })(this));
+ }
+
+ GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
+ return BLUR_KEYCODES.indexOf(keyCode) >= 0;
+ };
+
+ GitLabDropdownFilter.prototype.filter = function(search_text) {
+ var data, elements, group, key, results, tmp;
+ if (this.options.onFilter) {
+ this.options.onFilter(search_text);
+ }
+ data = this.options.data();
+ if ((data != null) && !this.options.filterByText) {
+ results = data;
+ if (search_text !== '') {
+ if (_.isArray(data)) {
+ results = fuzzaldrinPlus.filter(data, search_text, {
+ key: this.options.keys
+ });
+ } else {
+ if (gl.utils.isObject(data)) {
+ results = {};
+ for (key in data) {
+ group = data[key];
+ tmp = fuzzaldrinPlus.filter(group, search_text, {
+ key: this.options.keys
+ });
+ if (tmp.length) {
+ results[key] = tmp.map(function(item) {
+ return item;
+ });
+ }
+ }
+ }
+ }
+ }
+ return this.options.callback(results);
+ } else {
+ elements = this.options.elements();
+ if (search_text) {
+ return elements.each(function() {
+ var $el, matches;
+ $el = $(this);
+ matches = fuzzaldrinPlus.match($el.text().trim(), search_text);
+ if (!$el.is('.dropdown-header')) {
+ if (matches.length) {
+ return $el.show();
+ } else {
+ return $el.hide();
+ }
+ }
+ });
+ } else {
+ return elements.show();
+ }
+ }
+ };
+
+ return GitLabDropdownFilter;
+
+})();
+
+GitLabDropdownRemote = (function() {
+ function GitLabDropdownRemote(dataEndpoint, options) {
+ this.dataEndpoint = dataEndpoint;
+ this.options = options;
+ }
+
+ GitLabDropdownRemote.prototype.execute = function() {
+ if (typeof this.dataEndpoint === "string") {
+ return this.fetchData();
+ } else if (typeof this.dataEndpoint === "function") {
+ if (this.options.beforeSend) {
+ this.options.beforeSend();
+ }
+ return this.dataEndpoint("", (function(_this) {
+ return function(data) {
+ if (_this.options.success) {
+ _this.options.success(data);
+ }
+ if (_this.options.beforeSend) {
+ return _this.options.beforeSend();
+ }
+ };
+ })(this));
+ }
+ };
+
+ GitLabDropdownRemote.prototype.fetchData = function() {
+ return $.ajax({
+ url: this.dataEndpoint,
+ dataType: this.options.dataType,
+ beforeSend: (function(_this) {
+ return function() {
+ if (_this.options.beforeSend) {
+ return _this.options.beforeSend();
+ }
+ };
+ })(this),
+ success: (function(_this) {
+ return function(data) {
+ if (_this.options.success) {
+ return _this.options.success(data);
+ }
+ };
+ })(this)
+ });
+ };
+
+ return GitLabDropdownRemote;
+
+})();
+
+GitLabDropdown = (function() {
+ var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, currentIndex;
+
+ LOADING_CLASS = "is-loading";
+
+ PAGE_TWO_CLASS = "is-page-two";
+
+ ACTIVE_CLASS = "is-active";
+
+ INDETERMINATE_CLASS = "is-indeterminate";
+
+ currentIndex = -1;
+
+ FILTER_INPUT = '.dropdown-input .dropdown-input-field';
+
+ function GitLabDropdown(el1, options) {
+ var ref, ref1, ref2, ref3, searchFields, selector, self;
+ this.el = el1;
+ this.options = options;
+ this.updateLabel = bind(this.updateLabel, this);
+ this.hidden = bind(this.hidden, this);
+ this.opened = bind(this.opened, this);
+ this.shouldPropagate = bind(this.shouldPropagate, this);
+ self = this;
+ selector = $(this.el).data("target");
+ this.dropdown = selector != null ? $(selector) : $(this.el).parent();
+ ref = this.options, this.filterInput = (ref1 = ref.filterInput) != null ? ref1 : this.getElement(FILTER_INPUT), this.highlight = (ref2 = ref.highlight) != null ? ref2 : false, this.filterInputBlur = (ref3 = ref.filterInputBlur) != null ? ref3 : true;
+ self = this;
+ if (_.isString(this.filterInput)) {
+ this.filterInput = this.getElement(this.filterInput);
+ }
+ searchFields = this.options.search ? this.options.search.fields : [];
+ if (this.options.data) {
+ if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) {
+ this.fullData = this.options.data;
+ this.parseData(this.options.data);
+ } else {
+ this.remote = new GitLabDropdownRemote(this.options.data, {
+ dataType: this.options.dataType,
+ beforeSend: this.toggleLoading.bind(this),
+ success: (function(_this) {
+ return function(data) {
+ _this.fullData = data;
+ _this.parseData(_this.fullData);
+ if (_this.options.filterable && _this.filter && _this.filter.input) {
+ return _this.filter.input.trigger('keyup');
+ }
+ };
+ })(this)
+ });
+ }
+ }
+ if (this.options.filterable) {
+ this.filter = new GitLabDropdownFilter(this.filterInput, {
+ filterInputBlur: this.filterInputBlur,
+ filterByText: this.options.filterByText,
+ onFilter: this.options.onFilter,
+ remote: this.options.filterRemote,
+ query: this.options.data,
+ keys: searchFields,
+ elements: (function(_this) {
+ return function() {
+ selector = '.dropdown-content li:not(.divider)';
+ if (_this.dropdown.find('.dropdown-toggle-page').length) {
+ selector = ".dropdown-page-one " + selector;
+ }
+ return $(selector);
+ };
+ })(this),
+ data: (function(_this) {
+ return function() {
+ return _this.fullData;
+ };
+ })(this),
+ callback: (function(_this) {
+ return function(data) {
+ _this.parseData(data);
+ if (_this.filterInput.val() !== '') {
+ selector = '.dropdown-content li:not(.divider):visible';
+ if (_this.dropdown.find('.dropdown-toggle-page').length) {
+ selector = ".dropdown-page-one " + selector;
+ }
+ $(selector, _this.dropdown).first().find('a').addClass('is-focused');
+ return currentIndex = 0;
+ }
+ };
+ })(this)
+ });
+ }
+ this.dropdown.on("shown.bs.dropdown", this.opened);
+ this.dropdown.on("hidden.bs.dropdown", this.hidden);
+ $(this.el).on("update.label", this.updateLabel);
+ this.dropdown.on("click", ".dropdown-menu, .dropdown-menu-close", this.shouldPropagate);
+ this.dropdown.on('keyup', (function(_this) {
+ return function(e) {
+ if (e.which === 27) {
+ return $('.dropdown-menu-close', _this.dropdown).trigger('click');
+ }
+ };
+ })(this));
+ this.dropdown.on('blur', 'a', (function(_this) {
+ return function(e) {
+ var $dropdownMenu, $relatedTarget;
+ if (e.relatedTarget != null) {
+ $relatedTarget = $(e.relatedTarget);
+ $dropdownMenu = $relatedTarget.closest('.dropdown-menu');
+ if ($dropdownMenu.length === 0) {
+ return _this.dropdown.removeClass('open');
+ }
+ }
+ };
+ })(this));
+ if (this.dropdown.find(".dropdown-toggle-page").length) {
+ this.dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on("click", (function(_this) {
+ return function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ return _this.togglePage();
+ };
+ })(this));
+ }
+ if (this.options.selectable) {
+ selector = ".dropdown-content a";
+ if (this.dropdown.find(".dropdown-toggle-page").length) {
+ selector = ".dropdown-page-one .dropdown-content a";
+ }
+ this.dropdown.on("click", selector, function(e) {
+ var $el, selected;
+ $el = $(this);
+ selected = self.rowClicked($el);
+ if (self.options.clicked) {
+ self.options.clicked(selected, $el, e);
+ }
+ return $el.trigger('blur');
+ });
+ }
+ }
+
+ GitLabDropdown.prototype.getElement = function(selector) {
+ return this.dropdown.find(selector);
+ };
+
+ GitLabDropdown.prototype.toggleLoading = function() {
+ return $('.dropdown-menu', this.dropdown).toggleClass(LOADING_CLASS);
+ };
+
+ GitLabDropdown.prototype.togglePage = function() {
+ var menu;
+ menu = $('.dropdown-menu', this.dropdown);
+ if (menu.hasClass(PAGE_TWO_CLASS)) {
+ if (this.remote) {
+ this.remote.execute();
+ }
+ }
+ menu.toggleClass(PAGE_TWO_CLASS);
+ return this.dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus();
+ };
+
+ GitLabDropdown.prototype.parseData = function(data) {
+ var full_html, groupData, html, name;
+ this.renderedData = data;
+ if (this.options.filterable && data.length === 0) {
+ html = [this.noResults()];
+ } else {
+ if (gl.utils.isObject(data)) {
+ html = [];
+ for (name in data) {
+ groupData = data[name];
+ html.push(this.renderItem({
+ header: name
+ }, name));
+ this.renderData(groupData, name).map(function(item) {
+ return html.push(item);
+ });
+ }
+ } else {
+ html = this.renderData(data);
+ }
+ }
+ full_html = this.renderMenu(html);
+ return this.appendMenu(full_html);
+ };
+
+ GitLabDropdown.prototype.renderData = function(data, group) {
+ if (group == null) {
+ group = false;
+ }
+ return data.map((function(_this) {
+ return function(obj, index) {
+ return _this.renderItem(obj, group, index);
+ };
+ })(this));
+ };
+
+ GitLabDropdown.prototype.shouldPropagate = function(e) {
+ var $target;
+ if (this.options.multiSelect) {
+ $target = $(e.target);
+ if (!$target.hasClass('dropdown-menu-close') && !$target.hasClass('dropdown-menu-close-icon') && !$target.data('is-link')) {
+ e.stopPropagation();
+ return false;
+ } else {
+ return true;
+ }
+ }
+ };
+
+ GitLabDropdown.prototype.opened = function() {
+ var contentHtml;
+ this.addArrowKeyEvent();
+ if (this.options.setIndeterminateIds) {
+ this.options.setIndeterminateIds.call(this);
+ }
+ if (this.options.setActiveIds) {
+ this.options.setActiveIds.call(this);
+ }
+ if (this.fullData && this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
+ this.parseData(this.fullData);
+ }
+ contentHtml = $('.dropdown-content', this.dropdown).html();
+ if (this.remote && contentHtml === "") {
+ this.remote.execute();
+ }
+ if (this.options.filterable) {
+ this.filterInput.focus();
+ }
+ return this.dropdown.trigger('shown.gl.dropdown');
+ };
+
+ GitLabDropdown.prototype.hidden = function(e) {
+ var $input;
+ this.removeArrayKeyEvent();
+ $input = this.dropdown.find(".dropdown-input-field");
+ if (this.options.filterable) {
+ $input.blur().val("");
+ }
+ if (!this.options.persistWhenHide) {
+ $input.trigger("keyup");
+ }
+ if (this.dropdown.find(".dropdown-toggle-page").length) {
+ $('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS);
+ }
+ if (this.options.hidden) {
+ this.options.hidden.call(this, e);
+ }
+ return this.dropdown.trigger('hidden.gl.dropdown');
+ };
+
+ GitLabDropdown.prototype.renderMenu = function(html) {
+ var menu_html;
+ menu_html = "";
+ if (this.options.renderMenu) {
+ menu_html = this.options.renderMenu(html);
+ } else {
+ menu_html = $('<ul />').append(html);
+ }
+ return menu_html;
+ };
+
+ GitLabDropdown.prototype.appendMenu = function(html) {
+ var selector;
+ selector = '.dropdown-content';
+ if (this.dropdown.find(".dropdown-toggle-page").length) {
+ selector = ".dropdown-page-one .dropdown-content";
+ }
+ return $(selector, this.dropdown).empty().append(html);
+ };
+
+ GitLabDropdown.prototype.renderItem = function(data, group, index) {
+ var cssClass, field, fieldName, groupAttrs, html, selected, text, url, value;
+ if (group == null) {
+ group = false;
+ }
+ if (index == null) {
+ index = false;
+ }
+ html = "";
+ if (data === "divider") {
+ return "<li class='divider'></li>";
+ }
+ if (data === "separator") {
+ return "<li class='separator'></li>";
+ }
+ if (data.header != null) {
+ return "<li class='dropdown-header'>" + data.header + "</li>";
+ }
+ if (this.options.renderRow) {
+ html = this.options.renderRow.call(this.options, data, this);
+ } else {
+ if (!selected) {
+ value = this.options.id ? this.options.id(data) : data.id;
+ fieldName = this.options.fieldName;
+ field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
+ if (field.length) {
+ selected = true;
+ }
+ }
+ if (this.options.url != null) {
+ url = this.options.url(data);
+ } else {
+ url = data.url != null ? data.url : '#';
+ }
+ if (this.options.text != null) {
+ text = this.options.text(data);
+ } else {
+ text = data.text != null ? data.text : '';
+ }
+ cssClass = "";
+ if (selected) {
+ cssClass = "is-active";
+ }
+ if (this.highlight) {
+ text = this.highlightTextMatches(text, this.filterInput.val());
+ }
+ if (group) {
+ groupAttrs = "data-group='" + group + "' data-index='" + index + "'";
+ } else {
+ groupAttrs = '';
+ }
+ html = "<li> <a href='" + url + "' " + groupAttrs + " class='" + cssClass + "'> " + text + " </a> </li>";
+ }
+ return html;
+ };
+
+ GitLabDropdown.prototype.highlightTextMatches = function(text, term) {
+ var occurrences;
+ occurrences = fuzzaldrinPlus.match(text, term);
+ return text.split('').map(function(character, i) {
+ if (indexOf.call(occurrences, i) >= 0) {
+ return "<b>" + character + "</b>";
+ } else {
+ return character;
+ }
+ }).join('');
+ };
+
+ GitLabDropdown.prototype.noResults = function() {
+ var html;
+ return html = "<li class='dropdown-menu-empty-link'> <a href='#' class='is-focused'> No matching results. </a> </li>";
+ };
+
+ GitLabDropdown.prototype.highlightRow = function(index) {
+ var selector;
+ if (this.filterInput.val() !== "") {
+ selector = '.dropdown-content li:first-child a';
+ if (this.dropdown.find(".dropdown-toggle-page").length) {
+ selector = ".dropdown-page-one .dropdown-content li:first-child a";
+ }
+ return this.getElement(selector).addClass('is-focused');
+ }
+ };
+
+ GitLabDropdown.prototype.rowClicked = function(el) {
+ var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value;
+ fieldName = this.options.fieldName;
+ isInput = $(this.el).is('input');
+ if (this.renderedData) {
+ groupName = el.data('group');
+ if (groupName) {
+ selectedIndex = el.data('index');
+ selectedObject = this.renderedData[groupName][selectedIndex];
+ } else {
+ selectedIndex = el.closest('li').index();
+ selectedObject = this.renderedData[selectedIndex];
+ }
+ }
+ value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
+ if (isInput) {
+ field = $(this.el);
+ } else {
+ field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
+ }
+ if (el.hasClass(ACTIVE_CLASS)) {
+ el.removeClass(ACTIVE_CLASS);
+ if (isInput) {
+ field.val('');
+ } else {
+ field.remove();
+ }
+ if (this.options.toggleLabel) {
+ return this.updateLabel(selectedObject, el, this);
+ } else {
+ return selectedObject;
+ }
+ } else if (el.hasClass(INDETERMINATE_CLASS)) {
+ el.addClass(ACTIVE_CLASS);
+ el.removeClass(INDETERMINATE_CLASS);
+ if (value == null) {
+ field.remove();
+ }
+ if (!field.length && fieldName) {
+ this.addInput(fieldName, value);
+ }
+ return selectedObject;
+ } else {
+ if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
+ this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
+ if (!isInput) {
+ this.dropdown.parent().find("input[name='" + fieldName + "']").remove();
+ }
+ }
+ if (value == null) {
+ field.remove();
+ }
+ el.addClass(ACTIVE_CLASS);
+ if (this.options.toggleLabel) {
+ this.updateLabel(selectedObject, el, this);
+ }
+ if (value != null) {
+ if (!field.length && fieldName) {
+ this.addInput(fieldName, value);
+ } else {
+ field.val(value).trigger('change');
+ }
+ }
+ return selectedObject;
+ }
+ };
+
+ GitLabDropdown.prototype.addInput = function(fieldName, value) {
+ var $input;
+ $input = $('<input>').attr('type', 'hidden').attr('name', fieldName).val(value);
+ if (this.options.inputId != null) {
+ $input.attr('id', this.options.inputId);
+ }
+ return this.dropdown.before($input);
+ };
+
+ GitLabDropdown.prototype.selectRowAtIndex = function(e, index) {
+ var $el, selector;
+ selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(" + index + ") a";
+ if (this.dropdown.find(".dropdown-toggle-page").length) {
+ selector = ".dropdown-page-one " + selector;
+ }
+ $el = $(selector, this.dropdown);
+ if ($el.length) {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ return $el.first().trigger('click');
+ }
+ };
+
+ GitLabDropdown.prototype.addArrowKeyEvent = function() {
+ var $input, ARROW_KEY_CODES, selector;
+ ARROW_KEY_CODES = [38, 40];
+ $input = this.dropdown.find(".dropdown-input-field");
+ selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator)';
+ if (this.dropdown.find(".dropdown-toggle-page").length) {
+ selector = ".dropdown-page-one " + selector;
+ }
+ return $('body').on('keydown', (function(_this) {
+ return function(e) {
+ var $listItems, PREV_INDEX, currentKeyCode;
+ currentKeyCode = e.which;
+ if (ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0) {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ PREV_INDEX = currentIndex;
+ $listItems = $(selector, _this.dropdown);
+ if (currentKeyCode === 40) {
+ if (currentIndex < ($listItems.length - 1)) {
+ currentIndex += 1;
+ }
+ } else if (currentKeyCode === 38) {
+ if (currentIndex > 0) {
+ currentIndex -= 1;
+ }
+ }
+ if (currentIndex !== PREV_INDEX) {
+ _this.highlightRowAtIndex($listItems, currentIndex);
+ }
+ return false;
+ }
+ if (currentKeyCode === 13 && currentIndex !== -1) {
+ return _this.selectRowAtIndex(e, currentIndex);
+ }
+ };
+ })(this));
+ };
+
+ GitLabDropdown.prototype.removeArrayKeyEvent = function() {
+ return $('body').off('keydown');
+ };
+
+ GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) {
+ var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop;
+ $('.is-focused', this.dropdown).removeClass('is-focused');
+ $listItem = $listItems.eq(index);
+ $listItem.find('a:first-child').addClass("is-focused");
+ $dropdownContent = $listItem.closest('.dropdown-content');
+ dropdownScrollTop = $dropdownContent.scrollTop();
+ dropdownContentHeight = $dropdownContent.outerHeight();
+ dropdownContentTop = $dropdownContent.prop('offsetTop');
+ dropdownContentBottom = dropdownContentTop + dropdownContentHeight;
+ listItemHeight = $listItem.outerHeight();
+ listItemTop = $listItem.prop('offsetTop');
+ listItemBottom = listItemTop + listItemHeight;
+ if (listItemBottom > dropdownContentBottom + dropdownScrollTop) {
+ return $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom);
+ } else if (listItemTop < dropdownContentTop + dropdownScrollTop) {
+ return $dropdownContent.scrollTop(listItemTop - dropdownContentTop);
+ }
+ };
+
+ GitLabDropdown.prototype.updateLabel = function(selected, el, instance) {
+ if (selected == null) {
+ selected = null;
+ }
+ if (el == null) {
+ el = null;
+ }
+ if (instance == null) {
+ instance = null;
+ }
+ return $(this.el).find(".dropdown-toggle-text").text(this.options.toggleLabel(selected, el, instance));
+ };
+
+ return GitLabDropdown;
+
+})();
+
+$.fn.glDropdown = function(opts) {
+ return this.each(function() {
+ if (!$.data(this, 'glDropdown')) {
+ return $.data(this, 'glDropdown', new GitLabDropdown(this, opts));
+ }
+ });
+};
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
deleted file mode 100644
index 7086ece29b8..00000000000
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ /dev/null
@@ -1,638 +0,0 @@
-class GitLabDropdownFilter
- BLUR_KEYCODES = [27, 40]
- ARROW_KEY_CODES = [38, 40]
- HAS_VALUE_CLASS = "has-value"
-
- constructor: (@input, @options) ->
- {
- @filterInputBlur = true
- } = @options
-
- $inputContainer = @input.parent()
- $clearButton = $inputContainer.find('.js-dropdown-input-clear')
-
- @indeterminateIds = []
-
- # Clear click
- $clearButton.on 'click', (e) =>
- e.preventDefault()
- e.stopPropagation()
- @input
- .val('')
- .trigger('keyup')
- .focus()
-
- # Key events
- timeout = ""
- @input.on "keyup", (e) =>
- keyCode = e.which
-
- return if ARROW_KEY_CODES.indexOf(keyCode) >= 0
-
- if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS
- $inputContainer.addClass HAS_VALUE_CLASS
- else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS
- $inputContainer.removeClass HAS_VALUE_CLASS
-
- if keyCode is 13
- return false
-
- # Only filter asynchronously only if option remote is set
- if @options.remote
- clearTimeout timeout
- timeout = setTimeout =>
- blur_field = @shouldBlur keyCode
-
- if blur_field and @filterInputBlur
- @input.blur()
-
- @options.query @input.val(), (data) =>
- @options.callback(data)
- , 250
- else
- @filter @input.val()
-
- shouldBlur: (keyCode) ->
- return BLUR_KEYCODES.indexOf(keyCode) >= 0
-
- filter: (search_text) ->
- @options.onFilter(search_text) if @options.onFilter
- data = @options.data()
-
- if data? and not @options.filterByText
- results = data
-
- if search_text isnt ''
- # When data is an array of objects therefore [object Array] e.g.
- # [
- # { prop: 'foo' },
- # { prop: 'baz' }
- # ]
- if _.isArray(data)
- results = fuzzaldrinPlus.filter(data, search_text,
- key: @options.keys
- )
- else
- # If data is grouped therefore an [object Object]. e.g.
- # {
- # groupName1: [
- # { prop: 'foo' },
- # { prop: 'baz' }
- # ],
- # groupName2: [
- # { prop: 'abc' },
- # { prop: 'def' }
- # ]
- # }
- if gl.utils.isObject data
- results = {}
- for key, group of data
- tmp = fuzzaldrinPlus.filter(group, search_text,
- key: @options.keys
- )
-
- if tmp.length
- results[key] = tmp.map (item) -> item
-
- @options.callback results
- else
- elements = @options.elements()
-
- if search_text
- elements.each ->
- $el = $(@)
- matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
-
- unless $el.is('.dropdown-header')
- if matches.length
- $el.show()
- else
- $el.hide()
- else
- elements.show()
-
-class GitLabDropdownRemote
- constructor: (@dataEndpoint, @options) ->
-
- execute: ->
- if typeof @dataEndpoint is "string"
- @fetchData()
- else if typeof @dataEndpoint is "function"
- if @options.beforeSend
- @options.beforeSend()
-
- # Fetch the data by calling the data funcfion
- @dataEndpoint "", (data) =>
- if @options.success
- @options.success(data)
-
- if @options.beforeSend
- @options.beforeSend()
-
- # Fetch the data through ajax if the data is a string
- fetchData: ->
- $.ajax(
- url: @dataEndpoint,
- dataType: @options.dataType,
- beforeSend: =>
- if @options.beforeSend
- @options.beforeSend()
- success: (data) =>
- if @options.success
- @options.success(data)
- )
-
-class GitLabDropdown
- LOADING_CLASS = "is-loading"
- PAGE_TWO_CLASS = "is-page-two"
- ACTIVE_CLASS = "is-active"
- INDETERMINATE_CLASS = "is-indeterminate"
- currentIndex = -1
-
- FILTER_INPUT = '.dropdown-input .dropdown-input-field'
-
- constructor: (@el, @options) ->
- self = @
- selector = $(@el).data "target"
- @dropdown = if selector? then $(selector) else $(@el).parent()
-
- # Set Defaults
- {
- # If no input is passed create a default one
- @filterInput = @getElement(FILTER_INPUT)
- @highlight = false
- @filterInputBlur = true
- } = @options
-
- self = @
-
- # If selector was passed
- if _.isString(@filterInput)
- @filterInput = @getElement(@filterInput)
-
- searchFields = if @options.search then @options.search.fields else [];
-
- if @options.data
- # If we provided data
- # data could be an array of objects or a group of arrays
- if _.isObject(@options.data) and not _.isFunction(@options.data)
- @fullData = @options.data
- @parseData @options.data
- else
- # Remote data
- @remote = new GitLabDropdownRemote @options.data, {
- dataType: @options.dataType,
- beforeSend: @toggleLoading.bind(@)
- success: (data) =>
- @fullData = data
-
- @parseData @fullData
-
- @filter.input.trigger('keyup') if @options.filterable and @filter and @filter.input
- }
-
- # Init filterable
- if @options.filterable
- @filter = new GitLabDropdownFilter @filterInput,
- filterInputBlur: @filterInputBlur
- filterByText: @options.filterByText
- onFilter: @options.onFilter
- remote: @options.filterRemote
- query: @options.data
- keys: searchFields
- elements: =>
- selector = '.dropdown-content li:not(.divider)'
-
- if @dropdown.find('.dropdown-toggle-page').length
- selector = ".dropdown-page-one #{selector}"
-
- return $(selector)
- data: =>
- return @fullData
- callback: (data) =>
- @parseData data
-
- unless @filterInput.val() is ''
- selector = '.dropdown-content li:not(.divider):visible'
-
- if @dropdown.find('.dropdown-toggle-page').length
- selector = ".dropdown-page-one #{selector}"
-
- $(selector, @dropdown)
- .first()
- .find('a')
- .addClass('is-focused')
-
- currentIndex = 0
-
-
- # Event listeners
-
- @dropdown.on "shown.bs.dropdown", @opened
- @dropdown.on "hidden.bs.dropdown", @hidden
- $(@el).on "update.label", @updateLabel
- @dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate
- @dropdown.on 'keyup', (e) =>
- if e.which is 27 # Escape key
- $('.dropdown-menu-close', @dropdown).trigger 'click'
- @dropdown.on 'blur', 'a', (e) =>
- if e.relatedTarget?
- $relatedTarget = $(e.relatedTarget)
- $dropdownMenu = $relatedTarget.closest('.dropdown-menu')
-
- if $dropdownMenu.length is 0
- @dropdown.removeClass('open')
-
- if @dropdown.find(".dropdown-toggle-page").length
- @dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) =>
- e.preventDefault()
- e.stopPropagation()
-
- @togglePage()
-
- if @options.selectable
- selector = ".dropdown-content a"
-
- if @dropdown.find(".dropdown-toggle-page").length
- selector = ".dropdown-page-one .dropdown-content a"
-
- @dropdown.on "click", selector, (e) ->
- $el = $(@)
- selected = self.rowClicked $el
-
- if self.options.clicked
- self.options.clicked(selected, $el, e)
-
- $el.trigger('blur')
-
- # Finds an element inside wrapper element
- getElement: (selector) ->
- @dropdown.find selector
-
- toggleLoading: ->
- $('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS
-
- togglePage: ->
- menu = $('.dropdown-menu', @dropdown)
-
- if menu.hasClass(PAGE_TWO_CLASS)
- if @remote
- @remote.execute()
-
- menu.toggleClass PAGE_TWO_CLASS
-
- # Focus first visible input on active page
- @dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus()
-
- parseData: (data) ->
- @renderedData = data
-
- if @options.filterable and data.length is 0
- # render no matching results
- html = [@noResults()]
- else
- # Handle array groups
- if gl.utils.isObject data
- html = []
- for name, groupData of data
- # Add header for each group
- html.push(@renderItem(header: name, name))
-
- @renderData(groupData, name)
- .map (item) ->
- html.push item
- else
- # Render each row
- html = @renderData(data)
-
- # Render the full menu
- full_html = @renderMenu(html)
-
- @appendMenu(full_html)
-
- renderData: (data, group = false) ->
- data.map (obj, index) =>
- return @renderItem(obj, group, index)
-
- shouldPropagate: (e) =>
- if @options.multiSelect
- $target = $(e.target)
-
- if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link')
- e.stopPropagation()
- return false
- else
- return true
-
- opened: =>
- @addArrowKeyEvent()
-
- if @options.setIndeterminateIds
- @options.setIndeterminateIds.call(@)
-
- if @options.setActiveIds
- @options.setActiveIds.call(@)
-
- # Makes indeterminate items effective
- if @fullData and @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
- @parseData @fullData
-
- contentHtml = $('.dropdown-content', @dropdown).html()
- if @remote && contentHtml is ""
- @remote.execute()
-
- if @options.filterable
- @filterInput.focus()
-
- @dropdown.trigger('shown.gl.dropdown')
-
- hidden: (e) =>
- @removeArrayKeyEvent()
-
- $input = @dropdown.find(".dropdown-input-field")
-
- if @options.filterable
- $input
- .blur()
- .val("")
-
- # Triggering 'keyup' will re-render the dropdown which is not always required
- # specially if we want to keep the state of the dropdown needed for bulk-assignment
- if not @options.persistWhenHide
- $input.trigger("keyup")
-
- if @dropdown.find(".dropdown-toggle-page").length
- $('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
-
- if @options.hidden
- @options.hidden.call(@,e)
-
- @dropdown.trigger('hidden.gl.dropdown')
-
-
- # Render the full menu
- renderMenu: (html) ->
- menu_html = ""
-
- if @options.renderMenu
- menu_html = @options.renderMenu(html)
- else
- menu_html = $('<ul />')
- .append(html)
-
- return menu_html
-
- # Append the menu into the dropdown
- appendMenu: (html) ->
- selector = '.dropdown-content'
- if @dropdown.find(".dropdown-toggle-page").length
- selector = ".dropdown-page-one .dropdown-content"
- $(selector, @dropdown)
- .empty()
- .append(html)
-
- # Render the row
- renderItem: (data, group = false, index = false) ->
- html = ""
-
- # Divider
- return "<li class='divider'></li>" if data is "divider"
-
- # Separator is a full-width divider
- return "<li class='separator'></li>" if data is "separator"
-
- # Header
- return "<li class='dropdown-header'>#{data.header}</li>" if data.header?
-
- if @options.renderRow
- # Call the render function
- html = @options.renderRow.call(@options, data, @)
- else
- if not selected
- value = if @options.id then @options.id(data) else data.id
- fieldName = @options.fieldName
- field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
- if field.length
- selected = true
-
- # Set URL
- if @options.url?
- url = @options.url(data)
- else
- url = if data.url? then data.url else '#'
-
- # Set Text
- if @options.text?
- text = @options.text(data)
- else
- text = if data.text? then data.text else ''
-
- cssClass = "";
-
- if selected
- cssClass = "is-active"
-
- if @highlight
- text = @highlightTextMatches(text, @filterInput.val())
-
- if group
- groupAttrs = "data-group='#{group}' data-index='#{index}'"
- else
- groupAttrs = ''
-
- html = "<li>
- <a href='#{url}' #{groupAttrs} class='#{cssClass}'>
- #{text}
- </a>
- </li>"
-
- return html
-
- highlightTextMatches: (text, term) ->
- occurrences = fuzzaldrinPlus.match(text, term)
- text.split('').map((character, i) ->
- if i in occurrences then "<b>#{character}</b>" else character
- ).join('')
-
- noResults: ->
- html = "<li class='dropdown-menu-empty-link'>
- <a href='#' class='is-focused'>
- No matching results.
- </a>
- </li>"
-
- highlightRow: (index) ->
- if @filterInput.val() isnt ""
- selector = '.dropdown-content li:first-child a'
- if @dropdown.find(".dropdown-toggle-page").length
- selector = ".dropdown-page-one .dropdown-content li:first-child a"
-
- @getElement(selector).addClass 'is-focused'
-
- rowClicked: (el) ->
- fieldName = @options.fieldName
- isInput = $(@el).is('input')
-
- if @renderedData
- groupName = el.data('group')
- if groupName
- selectedIndex = el.data('index')
- selectedObject = @renderedData[groupName][selectedIndex]
- else
- selectedIndex = el.closest('li').index()
- selectedObject = @renderedData[selectedIndex]
-
- value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
-
- if isInput
- field = $(@el)
- else
- field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
-
- if el.hasClass(ACTIVE_CLASS)
- el.removeClass(ACTIVE_CLASS)
-
- if isInput
- field.val('')
- else
- field.remove()
-
- # Toggle the dropdown label
- if @options.toggleLabel
- @updateLabel(selectedObject, el, @)
- else
- selectedObject
- else if el.hasClass(INDETERMINATE_CLASS)
- el.addClass ACTIVE_CLASS
- el.removeClass INDETERMINATE_CLASS
-
- if not value?
- field.remove()
-
- if not field.length and fieldName
- @addInput(fieldName, value)
-
- return selectedObject
- else
- if not @options.multiSelect or el.hasClass('dropdown-clear-active')
- @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
-
- unless isInput
- @dropdown.parent().find("input[name='#{fieldName}']").remove()
-
- if !value?
- field.remove()
-
- # Toggle active class for the tick mark
- el.addClass ACTIVE_CLASS
-
- # Toggle the dropdown label
- if @options.toggleLabel
- @updateLabel(selectedObject, el, @)
- if value?
- if !field.length and fieldName
- @addInput(fieldName, value)
- else
- field
- .val value
- .trigger 'change'
-
- return selectedObject
-
- addInput: (fieldName, value)->
- # Create hidden input for form
- $input = $('<input>').attr('type', 'hidden')
- .attr('name', fieldName)
- .val(value)
-
- if @options.inputId?
- $input.attr('id', @options.inputId)
-
- @dropdown.before $input
-
- selectRowAtIndex: (e, index) ->
- selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(#{index}) a"
-
- if @dropdown.find(".dropdown-toggle-page").length
- selector = ".dropdown-page-one #{selector}"
-
- # simulate a click on the first link
- $el = $(selector, @dropdown)
-
- if $el.length
- e.preventDefault()
- e.stopImmediatePropagation()
- $el.first().trigger('click')
-
- addArrowKeyEvent: ->
- ARROW_KEY_CODES = [38, 40]
- $input = @dropdown.find(".dropdown-input-field")
-
- selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator)'
- if @dropdown.find(".dropdown-toggle-page").length
- selector = ".dropdown-page-one #{selector}"
-
- $('body').on 'keydown', (e) =>
- currentKeyCode = e.which
-
- if ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0
- e.preventDefault()
- e.stopImmediatePropagation()
-
- PREV_INDEX = currentIndex
- $listItems = $(selector, @dropdown)
-
- # if @options.filterable
- # $input.blur()
-
- if currentKeyCode is 40
- # Move down
- currentIndex += 1 if currentIndex < ($listItems.length - 1)
- else if currentKeyCode is 38
- # Move up
- currentIndex -= 1 if currentIndex > 0
-
- @highlightRowAtIndex($listItems, currentIndex) if currentIndex isnt PREV_INDEX
-
- return false
-
- if currentKeyCode is 13 and currentIndex isnt -1
- @selectRowAtIndex e, currentIndex
-
- removeArrayKeyEvent: ->
- $('body').off 'keydown'
-
- highlightRowAtIndex: ($listItems, index) ->
- # Remove the class for the previously focused row
- $('.is-focused', @dropdown).removeClass 'is-focused'
-
- # Update the class for the row at the specific index
- $listItem = $listItems.eq(index)
- $listItem.find('a:first-child').addClass "is-focused"
-
- # Dropdown content scroll area
- $dropdownContent = $listItem.closest('.dropdown-content')
- dropdownScrollTop = $dropdownContent.scrollTop()
- dropdownContentHeight = $dropdownContent.outerHeight()
- dropdownContentTop = $dropdownContent.prop('offsetTop')
- dropdownContentBottom = dropdownContentTop + dropdownContentHeight
-
- # Get the offset bottom of the list item
- listItemHeight = $listItem.outerHeight()
- listItemTop = $listItem.prop('offsetTop')
- listItemBottom = listItemTop + listItemHeight
-
- if listItemBottom > dropdownContentBottom + dropdownScrollTop
- # Scroll the dropdown content down
- $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom)
- else if listItemTop < dropdownContentTop + dropdownScrollTop
- # Scroll the dropdown content up
- $dropdownContent.scrollTop(listItemTop - dropdownContentTop)
-
- updateLabel: (selected = null, el = null, instance = null) =>
- $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance)
-
-$.fn.glDropdown = (opts) ->
- return @.each ->
- if (!$.data @, 'glDropdown')
- $.data(@, 'glDropdown', new GitLabDropdown @, opts)
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
new file mode 100644
index 00000000000..941ca1309fc
--- /dev/null
+++ b/app/assets/javascripts/gl_form.js
@@ -0,0 +1,50 @@
+this.GLForm = (function() {
+ function GLForm(form) {
+ this.form = form;
+ this.textarea = this.form.find('textarea.js-gfm-input');
+ this.destroy();
+ this.setupForm();
+ this.form.data('gl-form', this);
+ }
+
+ GLForm.prototype.destroy = function() {
+ this.clearEventListeners();
+ return this.form.data('gl-form', null);
+ };
+
+ GLForm.prototype.setupForm = function() {
+ var isNewForm;
+ isNewForm = this.form.is(':not(.gfm-form)');
+ this.form.removeClass('js-new-note-form');
+ if (isNewForm) {
+ this.form.find('.div-dropzone').remove();
+ this.form.addClass('gfm-form');
+ disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
+ GitLab.GfmAutoComplete.setup();
+ new DropzoneInput(this.form);
+ autosize(this.textarea);
+ this.addEventListeners();
+ gl.text.init(this.form);
+ }
+ this.form.find('.js-note-discard').hide();
+ return this.form.show();
+ };
+
+ GLForm.prototype.clearEventListeners = function() {
+ this.textarea.off('focus');
+ this.textarea.off('blur');
+ return gl.text.removeListeners(this.form);
+ };
+
+ GLForm.prototype.addEventListeners = function() {
+ this.textarea.on('focus', function() {
+ return $(this).closest('.md-area').addClass('is-focused');
+ });
+ return this.textarea.on('blur', function() {
+ return $(this).closest('.md-area').removeClass('is-focused');
+ });
+ };
+
+ return GLForm;
+
+})();
diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee
deleted file mode 100644
index 77512d187c9..00000000000
--- a/app/assets/javascripts/gl_form.js.coffee
+++ /dev/null
@@ -1,54 +0,0 @@
-class @GLForm
- constructor: (@form) ->
- @textarea = @form.find('textarea.js-gfm-input')
-
- # Before we start, we should clean up any previous data for this form
- @destroy()
-
- # Setup the form
- @setupForm()
-
- @form.data 'gl-form', @
-
- destroy: ->
- # Clean form listeners
- @clearEventListeners()
- @form.data 'gl-form', null
-
- setupForm: ->
- isNewForm = @form.is(':not(.gfm-form)')
-
- @form.removeClass 'js-new-note-form'
-
- if isNewForm
- @form.find('.div-dropzone').remove()
- @form.addClass('gfm-form')
- disableButtonIfEmptyField @form.find('.js-note-text'), @form.find('.js-comment-button')
-
- # remove notify commit author checkbox for non-commit notes
- GitLab.GfmAutoComplete.setup()
- new DropzoneInput(@form)
-
- autosize(@textarea)
-
- # form and textarea event listeners
- @addEventListeners()
-
- gl.text.init(@form)
-
- # hide discard button
- @form.find('.js-note-discard').hide()
-
- @form.show()
-
- clearEventListeners: ->
- @textarea.off 'focus'
- @textarea.off 'blur'
- gl.text.removeListeners(@form)
-
- addEventListeners: ->
- @textarea.on 'focus', ->
- $(@).closest('.md-area').addClass 'is-focused'
-
- @textarea.on 'blur', ->
- $(@).closest('.md-area').removeClass 'is-focused'
diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js
new file mode 100644
index 00000000000..334007d6b38
--- /dev/null
+++ b/app/assets/javascripts/graphs/graphs_bundle.js
@@ -0,0 +1,3 @@
+
+/*= require_tree . */
+
diff --git a/app/assets/javascripts/graphs/graphs_bundle.js.coffee b/app/assets/javascripts/graphs/graphs_bundle.js.coffee
deleted file mode 100644
index e0f681acf0b..00000000000
--- a/app/assets/javascripts/graphs/graphs_bundle.js.coffee
+++ /dev/null
@@ -1,7 +0,0 @@
-# This is a manifest file that'll be compiled into including all the files listed below.
-# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
-# be included in the compiled file accessible from http://example.com/assets/application.js
-# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-# the compiled file.
-#
-#= require_tree .
diff --git a/app/assets/javascripts/graphs/stat_graph.js b/app/assets/javascripts/graphs/stat_graph.js
new file mode 100644
index 00000000000..5805118d689
--- /dev/null
+++ b/app/assets/javascripts/graphs/stat_graph.js
@@ -0,0 +1,16 @@
+this.StatGraph = (function() {
+ function StatGraph() {}
+
+ StatGraph.log = {};
+
+ StatGraph.get_log = function() {
+ return this.log;
+ };
+
+ StatGraph.set_log = function(data) {
+ return this.log = data;
+ };
+
+ return StatGraph;
+
+})();
diff --git a/app/assets/javascripts/graphs/stat_graph.js.coffee b/app/assets/javascripts/graphs/stat_graph.js.coffee
deleted file mode 100644
index f36c71fd25e..00000000000
--- a/app/assets/javascripts/graphs/stat_graph.js.coffee
+++ /dev/null
@@ -1,6 +0,0 @@
-class @StatGraph
- @log: {}
- @get_log: ->
- @log
- @set_log: (data) ->
- @log = data
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js
new file mode 100644
index 00000000000..865cf85496a
--- /dev/null
+++ b/app/assets/javascripts/graphs/stat_graph_contributors.js
@@ -0,0 +1,108 @@
+
+/*= require d3 */
+this.ContributorsStatGraph = (function() {
+ function ContributorsStatGraph() {}
+
+ ContributorsStatGraph.prototype.init = function(log) {
+ var author_commits, total_commits;
+ this.parsed_log = ContributorsStatGraphUtil.parse_log(log);
+ this.set_current_field("commits");
+ total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
+ author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field);
+ this.add_master_graph(total_commits);
+ this.add_authors_graph(author_commits);
+ return this.change_date_header();
+ };
+
+ ContributorsStatGraph.prototype.add_master_graph = function(total_data) {
+ this.master_graph = new ContributorsMasterGraph(total_data);
+ return this.master_graph.draw();
+ };
+
+ ContributorsStatGraph.prototype.add_authors_graph = function(author_data) {
+ var limited_author_data;
+ this.authors = [];
+ limited_author_data = author_data.slice(0, 100);
+ return _.each(limited_author_data, (function(_this) {
+ return function(d) {
+ var author_graph, author_header;
+ author_header = _this.create_author_header(d);
+ $(".contributors-list").append(author_header);
+ _this.authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates);
+ return author_graph.draw();
+ };
+ })(this));
+ };
+
+ ContributorsStatGraph.prototype.format_author_commit_info = function(author) {
+ var commits;
+ commits = $('<span/>', {
+ "class": 'graph-author-commits-count'
+ });
+ commits.text(author.commits + " commits");
+ return $('<span/>').append(commits);
+ };
+
+ ContributorsStatGraph.prototype.create_author_header = function(author) {
+ var author_commit_info, author_commit_info_span, author_email, author_name, list_item;
+ list_item = $('<li/>', {
+ "class": 'person',
+ style: 'display: block;'
+ });
+ author_name = $('<h4>' + author.author_name + '</h4>');
+ author_email = $('<p class="graph-author-email">' + author.author_email + '</p>');
+ author_commit_info_span = $('<span/>', {
+ "class": 'commits'
+ });
+ author_commit_info = this.format_author_commit_info(author);
+ author_commit_info_span.html(author_commit_info);
+ list_item.append(author_name);
+ list_item.append(author_email);
+ list_item.append(author_commit_info_span);
+ return list_item;
+ };
+
+ ContributorsStatGraph.prototype.redraw_master = function() {
+ var total_data;
+ total_data = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
+ this.master_graph.set_data(total_data);
+ return this.master_graph.redraw();
+ };
+
+ ContributorsStatGraph.prototype.redraw_authors = function() {
+ var author_commits, x_domain;
+ $("ol").html("");
+ x_domain = ContributorsGraph.prototype.x_domain;
+ author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field, x_domain);
+ return _.each(author_commits, (function(_this) {
+ return function(d) {
+ _this.redraw_author_commit_info(d);
+ $(_this.authors[d.author_name].list_item).appendTo("ol");
+ _this.authors[d.author_name].set_data(d.dates);
+ return _this.authors[d.author_name].redraw();
+ };
+ })(this));
+ };
+
+ ContributorsStatGraph.prototype.set_current_field = function(field) {
+ return this.field = field;
+ };
+
+ ContributorsStatGraph.prototype.change_date_header = function() {
+ var print, print_date_format, x_domain;
+ x_domain = ContributorsGraph.prototype.x_domain;
+ print_date_format = d3.time.format("%B %e %Y");
+ print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
+ return $("#date_header").text(print);
+ };
+
+ ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {
+ var author_commit_info, author_list_item;
+ author_list_item = $(this.authors[author.author_name].list_item);
+ author_commit_info = this.format_author_commit_info(author);
+ return author_list_item.find("span").html(author_commit_info);
+ };
+
+ return ContributorsStatGraph;
+
+})();
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js.coffee b/app/assets/javascripts/graphs/stat_graph_contributors.js.coffee
deleted file mode 100644
index 1d9fae7cf79..00000000000
--- a/app/assets/javascripts/graphs/stat_graph_contributors.js.coffee
+++ /dev/null
@@ -1,71 +0,0 @@
-#= require d3
-
-class @ContributorsStatGraph
- init: (log) ->
- @parsed_log = ContributorsStatGraphUtil.parse_log(log)
- @set_current_field("commits")
- total_commits = ContributorsStatGraphUtil.get_total_data(@parsed_log, @field)
- author_commits = ContributorsStatGraphUtil.get_author_data(@parsed_log, @field)
- @add_master_graph(total_commits)
- @add_authors_graph(author_commits)
- @change_date_header()
- add_master_graph: (total_data) ->
- @master_graph = new ContributorsMasterGraph(total_data)
- @master_graph.draw()
- add_authors_graph: (author_data) ->
- @authors = []
- limited_author_data = author_data.slice(0, 100)
- _.each(limited_author_data, (d) =>
- author_header = @create_author_header(d)
- $(".contributors-list").append(author_header)
- @authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates)
- author_graph.draw()
- )
- format_author_commit_info: (author) ->
- commits = $('<span/>', {
- class: 'graph-author-commits-count'
- })
- commits.text(author.commits + " commits")
- $('<span/>').append(commits)
-
- create_author_header: (author) ->
- list_item = $('<li/>', {
- class: 'person'
- style: 'display: block;'
- })
- author_name = $('<h4>' + author.author_name + '</h4>')
- author_email = $('<p class="graph-author-email">' + author.author_email + '</p>')
- author_commit_info_span = $('<span/>', {
- class: 'commits'
- })
- author_commit_info = @format_author_commit_info(author)
- author_commit_info_span.html(author_commit_info)
- list_item.append(author_name)
- list_item.append(author_email)
- list_item.append(author_commit_info_span)
- list_item
- redraw_master: ->
- total_data = ContributorsStatGraphUtil.get_total_data(@parsed_log, @field)
- @master_graph.set_data(total_data)
- @master_graph.redraw()
- redraw_authors: ->
- $("ol").html("")
- x_domain = ContributorsGraph.prototype.x_domain
- author_commits = ContributorsStatGraphUtil.get_author_data(@parsed_log, @field, x_domain)
- _.each(author_commits, (d) =>
- @redraw_author_commit_info(d)
- $(@authors[d.author_name].list_item).appendTo("ol")
- @authors[d.author_name].set_data(d.dates)
- @authors[d.author_name].redraw()
- )
- set_current_field: (field) ->
- @field = field
- change_date_header: ->
- x_domain = ContributorsGraph.prototype.x_domain
- print_date_format = d3.time.format("%B %e %Y")
- print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1])
- $("#date_header").text(print)
- redraw_author_commit_info: (author) ->
- author_list_item = $(@authors[author.author_name].list_item)
- author_commit_info = @format_author_commit_info(author)
- author_list_item.find("span").html(author_commit_info)
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
new file mode 100644
index 00000000000..a227dcb5907
--- /dev/null
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
@@ -0,0 +1,275 @@
+
+/*= require d3 */
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+this.ContributorsGraph = (function() {
+ function ContributorsGraph() {}
+
+ ContributorsGraph.prototype.MARGIN = {
+ top: 20,
+ right: 20,
+ bottom: 30,
+ left: 50
+ };
+
+ ContributorsGraph.prototype.x_domain = null;
+
+ ContributorsGraph.prototype.y_domain = null;
+
+ ContributorsGraph.prototype.dates = [];
+
+ ContributorsGraph.set_x_domain = function(data) {
+ return ContributorsGraph.prototype.x_domain = data;
+ };
+
+ ContributorsGraph.set_y_domain = function(data) {
+ return ContributorsGraph.prototype.y_domain = [
+ 0, d3.max(data, function(d) {
+ var ref, ref1;
+ return d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions;
+ })
+ ];
+ };
+
+ ContributorsGraph.init_x_domain = function(data) {
+ return ContributorsGraph.prototype.x_domain = d3.extent(data, function(d) {
+ return d.date;
+ });
+ };
+
+ ContributorsGraph.init_y_domain = function(data) {
+ return ContributorsGraph.prototype.y_domain = [
+ 0, d3.max(data, function(d) {
+ var ref, ref1;
+ return d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions;
+ })
+ ];
+ };
+
+ ContributorsGraph.init_domain = function(data) {
+ ContributorsGraph.init_x_domain(data);
+ return ContributorsGraph.init_y_domain(data);
+ };
+
+ ContributorsGraph.set_dates = function(data) {
+ return ContributorsGraph.prototype.dates = data;
+ };
+
+ ContributorsGraph.prototype.set_x_domain = function() {
+ return this.x.domain(this.x_domain);
+ };
+
+ ContributorsGraph.prototype.set_y_domain = function() {
+ return this.y.domain(this.y_domain);
+ };
+
+ ContributorsGraph.prototype.set_domain = function() {
+ this.set_x_domain();
+ return this.set_y_domain();
+ };
+
+ ContributorsGraph.prototype.create_scale = function(width, height) {
+ this.x = d3.time.scale().range([0, width]).clamp(true);
+ return this.y = d3.scale.linear().range([height, 0]).nice();
+ };
+
+ ContributorsGraph.prototype.draw_x_axis = function() {
+ return this.svg.append("g").attr("class", "x axis").attr("transform", "translate(0, " + this.height + ")").call(this.x_axis);
+ };
+
+ ContributorsGraph.prototype.draw_y_axis = function() {
+ return this.svg.append("g").attr("class", "y axis").call(this.y_axis);
+ };
+
+ ContributorsGraph.prototype.set_data = function(data) {
+ return this.data = data;
+ };
+
+ return ContributorsGraph;
+
+})();
+
+this.ContributorsMasterGraph = (function(superClass) {
+ extend(ContributorsMasterGraph, superClass);
+
+ function ContributorsMasterGraph(data1) {
+ this.data = data1;
+ this.update_content = bind(this.update_content, this);
+ this.width = $('.content').width() - 70;
+ this.height = 200;
+ this.x = null;
+ this.y = null;
+ this.x_axis = null;
+ this.y_axis = null;
+ this.area = null;
+ this.svg = null;
+ this.brush = null;
+ this.x_max_domain = null;
+ }
+
+ ContributorsMasterGraph.prototype.process_dates = function(data) {
+ var dates;
+ dates = this.get_dates(data);
+ this.parse_dates(data);
+ return ContributorsGraph.set_dates(dates);
+ };
+
+ ContributorsMasterGraph.prototype.get_dates = function(data) {
+ return _.pluck(data, 'date');
+ };
+
+ ContributorsMasterGraph.prototype.parse_dates = function(data) {
+ var parseDate;
+ parseDate = d3.time.format("%Y-%m-%d").parse;
+ return data.forEach(function(d) {
+ return d.date = parseDate(d.date);
+ });
+ };
+
+ ContributorsMasterGraph.prototype.create_scale = function() {
+ return ContributorsMasterGraph.__super__.create_scale.call(this, this.width, this.height);
+ };
+
+ ContributorsMasterGraph.prototype.create_axes = function() {
+ this.x_axis = d3.svg.axis().scale(this.x).orient("bottom");
+ return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
+ };
+
+ ContributorsMasterGraph.prototype.create_svg = function() {
+ return this.svg = d3.select("#contributors-master").append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "tint-box").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
+ };
+
+ ContributorsMasterGraph.prototype.create_area = function(x, y) {
+ return this.area = d3.svg.area().x(function(d) {
+ return x(d.date);
+ }).y0(this.height).y1(function(d) {
+ var ref, ref1, xa;
+ xa = d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions;
+ return y(xa);
+ }).interpolate("basis");
+ };
+
+ ContributorsMasterGraph.prototype.create_brush = function() {
+ return this.brush = d3.svg.brush().x(this.x).on("brushend", this.update_content);
+ };
+
+ ContributorsMasterGraph.prototype.draw_path = function(data) {
+ return this.svg.append("path").datum(data).attr("class", "area").attr("d", this.area);
+ };
+
+ ContributorsMasterGraph.prototype.add_brush = function() {
+ return this.svg.append("g").attr("class", "selection").call(this.brush).selectAll("rect").attr("height", this.height);
+ };
+
+ ContributorsMasterGraph.prototype.update_content = function() {
+ ContributorsGraph.set_x_domain(this.brush.empty() ? this.x_max_domain : this.brush.extent());
+ return $("#brush_change").trigger('change');
+ };
+
+ ContributorsMasterGraph.prototype.draw = function() {
+ this.process_dates(this.data);
+ this.create_scale();
+ this.create_axes();
+ ContributorsGraph.init_domain(this.data);
+ this.x_max_domain = this.x_domain;
+ this.set_domain();
+ this.create_area(this.x, this.y);
+ this.create_svg();
+ this.create_brush();
+ this.draw_path(this.data);
+ this.draw_x_axis();
+ this.draw_y_axis();
+ return this.add_brush();
+ };
+
+ ContributorsMasterGraph.prototype.redraw = function() {
+ this.process_dates(this.data);
+ ContributorsGraph.set_y_domain(this.data);
+ this.set_y_domain();
+ this.svg.select("path").datum(this.data);
+ this.svg.select("path").attr("d", this.area);
+ return this.svg.select(".y.axis").call(this.y_axis);
+ };
+
+ return ContributorsMasterGraph;
+
+})(ContributorsGraph);
+
+this.ContributorsAuthorGraph = (function(superClass) {
+ extend(ContributorsAuthorGraph, superClass);
+
+ function ContributorsAuthorGraph(data1) {
+ this.data = data1;
+ if ($(window).width() < 768) {
+ this.width = $('.content').width() - 80;
+ } else {
+ this.width = ($('.content').width() / 2) - 100;
+ }
+ this.height = 200;
+ this.x = null;
+ this.y = null;
+ this.x_axis = null;
+ this.y_axis = null;
+ this.area = null;
+ this.svg = null;
+ this.list_item = null;
+ }
+
+ ContributorsAuthorGraph.prototype.create_scale = function() {
+ return ContributorsAuthorGraph.__super__.create_scale.call(this, this.width, this.height);
+ };
+
+ ContributorsAuthorGraph.prototype.create_axes = function() {
+ this.x_axis = d3.svg.axis().scale(this.x).orient("bottom").ticks(8);
+ return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
+ };
+
+ ContributorsAuthorGraph.prototype.create_area = function(x, y) {
+ return this.area = d3.svg.area().x(function(d) {
+ var parseDate;
+ parseDate = d3.time.format("%Y-%m-%d").parse;
+ return x(parseDate(d));
+ }).y0(this.height).y1((function(_this) {
+ return function(d) {
+ if (_this.data[d] != null) {
+ return y(_this.data[d]);
+ } else {
+ return y(0);
+ }
+ };
+ })(this)).interpolate("basis");
+ };
+
+ ContributorsAuthorGraph.prototype.create_svg = function() {
+ this.list_item = d3.selectAll(".person")[0].pop();
+ return this.svg = d3.select(this.list_item).append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "spark").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
+ };
+
+ ContributorsAuthorGraph.prototype.draw_path = function(data) {
+ return this.svg.append("path").datum(data).attr("class", "area-contributor").attr("d", this.area);
+ };
+
+ ContributorsAuthorGraph.prototype.draw = function() {
+ this.create_scale();
+ this.create_axes();
+ this.set_domain();
+ this.create_area(this.x, this.y);
+ this.create_svg();
+ this.draw_path(this.dates);
+ this.draw_x_axis();
+ return this.draw_y_axis();
+ };
+
+ ContributorsAuthorGraph.prototype.redraw = function() {
+ this.set_domain();
+ this.svg.select("path").datum(this.dates);
+ this.svg.select("path").attr("d", this.area);
+ this.svg.select(".x.axis").call(this.x_axis);
+ return this.svg.select(".y.axis").call(this.y_axis);
+ };
+
+ return ContributorsAuthorGraph;
+
+})(ContributorsGraph);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee
deleted file mode 100644
index 834a81af459..00000000000
--- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee
+++ /dev/null
@@ -1,173 +0,0 @@
-#= require d3
-
-class @ContributorsGraph
- MARGIN:
- top: 20
- right: 20
- bottom: 30
- left: 50
- x_domain: null
- y_domain: null
- dates: []
- @set_x_domain: (data) =>
- @prototype.x_domain = data
- @set_y_domain: (data) =>
- @prototype.y_domain = [0, d3.max(data, (d) ->
- d.commits = d.commits ? d.additions ? d.deletions
- )]
- @init_x_domain: (data) =>
- @prototype.x_domain = d3.extent(data, (d) ->
- d.date
- )
- @init_y_domain: (data) =>
- @prototype.y_domain = [0, d3.max(data, (d) ->
- d.commits = d.commits ? d.additions ? d.deletions
- )]
- @init_domain: (data) =>
- @init_x_domain(data)
- @init_y_domain(data)
- @set_dates: (data) =>
- @prototype.dates = data
- set_x_domain: ->
- @x.domain(@x_domain)
- set_y_domain: ->
- @y.domain(@y_domain)
- set_domain: ->
- @set_x_domain()
- @set_y_domain()
- create_scale: (width, height) ->
- @x = d3.time.scale().range([0, width]).clamp(true)
- @y = d3.scale.linear().range([height, 0]).nice()
- draw_x_axis: ->
- @svg.append("g").attr("class", "x axis").attr("transform", "translate(0, #{@height})")
- .call(@x_axis)
- draw_y_axis: ->
- @svg.append("g").attr("class", "y axis").call(@y_axis)
- set_data: (data) ->
- @data = data
-
-class @ContributorsMasterGraph extends ContributorsGraph
- constructor: (@data) ->
- @width = $('.content').width() - 70
- @height = 200
- @x = null
- @y = null
- @x_axis = null
- @y_axis = null
- @area = null
- @svg = null
- @brush = null
- @x_max_domain = null
- process_dates: (data) ->
- dates = @get_dates(data)
- @parse_dates(data)
- ContributorsGraph.set_dates(dates)
- get_dates: (data) ->
- _.pluck(data, 'date')
- parse_dates: (data) ->
- parseDate = d3.time.format("%Y-%m-%d").parse
- data.forEach((d) ->
- d.date = parseDate(d.date)
- )
- create_scale: ->
- super @width, @height
- create_axes: ->
- @x_axis = d3.svg.axis().scale(@x).orient("bottom")
- @y_axis = d3.svg.axis().scale(@y).orient("left").ticks(5)
- create_svg: ->
- @svg = d3.select("#contributors-master").append("svg")
- .attr("width", @width + @MARGIN.left + @MARGIN.right)
- .attr("height", @height + @MARGIN.top + @MARGIN.bottom)
- .attr("class", "tint-box")
- .append("g")
- .attr("transform", "translate(" + @MARGIN.left + "," + @MARGIN.top + ")")
- create_area: (x, y) ->
- @area = d3.svg.area().x((d) ->
- x(d.date)
- ).y0(@height).y1((d) ->
- xa = d.commits = d.commits ? d.additions ? d.deletions
- y(xa)
- ).interpolate("basis")
- create_brush: ->
- @brush = d3.svg.brush().x(@x).on("brushend", @update_content)
- draw_path: (data) ->
- @svg.append("path").datum(data).attr("class", "area").attr("d", @area)
- add_brush: ->
- @svg.append("g").attr("class", "selection").call(@brush).selectAll("rect").attr("height", @height)
- update_content: =>
- ContributorsGraph.set_x_domain(if @brush.empty() then @x_max_domain else @brush.extent())
- $("#brush_change").trigger('change')
- draw: ->
- @process_dates(@data)
- @create_scale()
- @create_axes()
- ContributorsGraph.init_domain(@data)
- @x_max_domain = @x_domain
- @set_domain()
- @create_area(@x, @y)
- @create_svg()
- @create_brush()
- @draw_path(@data)
- @draw_x_axis()
- @draw_y_axis()
- @add_brush()
- redraw: ->
- @process_dates(@data)
- ContributorsGraph.set_y_domain(@data)
- @set_y_domain()
- @svg.select("path").datum(@data)
- @svg.select("path").attr("d", @area)
- @svg.select(".y.axis").call(@y_axis)
-
-class @ContributorsAuthorGraph extends ContributorsGraph
- constructor: (@data) ->
- # Don't split graph size in half for mobile devices.
- if $(window).width() < 768
- @width = $('.content').width() - 80
- else
- @width = ($('.content').width() / 2) - 100
- @height = 200
- @x = null
- @y = null
- @x_axis = null
- @y_axis = null
- @area = null
- @svg = null
- @list_item = null
- create_scale: ->
- super @width, @height
- create_axes: ->
- @x_axis = d3.svg.axis().scale(@x).orient("bottom").ticks(8)
- @y_axis = d3.svg.axis().scale(@y).orient("left").ticks(5)
- create_area: (x, y) ->
- @area = d3.svg.area().x((d) ->
- parseDate = d3.time.format("%Y-%m-%d").parse
- x(parseDate(d))
- ).y0(@height).y1((d) =>
- if @data[d]? then y(@data[d]) else y(0)
- ).interpolate("basis")
- create_svg: ->
- @list_item = d3.selectAll(".person")[0].pop()
- @svg = d3.select(@list_item).append("svg")
- .attr("width", @width + @MARGIN.left + @MARGIN.right)
- .attr("height", @height + @MARGIN.top + @MARGIN.bottom)
- .attr("class", "spark")
- .append("g")
- .attr("transform", "translate(" + @MARGIN.left + "," + @MARGIN.top + ")")
- draw_path: (data) ->
- @svg.append("path").datum(data).attr("class", "area-contributor").attr("d", @area)
- draw: ->
- @create_scale()
- @create_axes()
- @set_domain()
- @create_area(@x, @y)
- @create_svg()
- @draw_path(@dates)
- @draw_x_axis()
- @draw_y_axis()
- redraw: ->
- @set_domain()
- @svg.select("path").datum(@dates)
- @svg.select("path").attr("d", @area)
- @svg.select(".x.axis").call(@x_axis)
- @svg.select(".y.axis").call(@y_axis)
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
new file mode 100644
index 00000000000..26189f5d913
--- /dev/null
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
@@ -0,0 +1,132 @@
+window.ContributorsStatGraphUtil = {
+ parse_log: function(log) {
+ var by_author, by_email, data, entry, i, len, total;
+ total = {};
+ by_author = {};
+ by_email = {};
+ for (i = 0, len = log.length; i < len; i++) {
+ entry = log[i];
+ if (total[entry.date] == null) {
+ this.add_date(entry.date, total);
+ }
+ data = by_author[entry.author_name] || by_email[entry.author_email];
+ if (data == null) {
+ data = this.add_author(entry, by_author, by_email);
+ }
+ if (!data[entry.date]) {
+ this.add_date(entry.date, data);
+ }
+ this.store_data(entry, total[entry.date], data[entry.date]);
+ }
+ total = _.toArray(total);
+ by_author = _.toArray(by_author);
+ return {
+ total: total,
+ by_author: by_author
+ };
+ },
+ add_date: function(date, collection) {
+ collection[date] = {};
+ return collection[date].date = date;
+ },
+ add_author: function(author, by_author, by_email) {
+ var data;
+ data = {};
+ data.author_name = author.author_name;
+ data.author_email = author.author_email;
+ by_author[author.author_name] = data;
+ return by_email[author.author_email] = data;
+ },
+ store_data: function(entry, total, by_author) {
+ this.store_commits(total, by_author);
+ this.store_additions(entry, total, by_author);
+ return this.store_deletions(entry, total, by_author);
+ },
+ store_commits: function(total, by_author) {
+ this.add(total, "commits", 1);
+ return this.add(by_author, "commits", 1);
+ },
+ add: function(collection, field, value) {
+ if (collection[field] == null) {
+ collection[field] = 0;
+ }
+ return collection[field] += value;
+ },
+ store_additions: function(entry, total, by_author) {
+ if (entry.additions == null) {
+ entry.additions = 0;
+ }
+ this.add(total, "additions", entry.additions);
+ return this.add(by_author, "additions", entry.additions);
+ },
+ store_deletions: function(entry, total, by_author) {
+ if (entry.deletions == null) {
+ entry.deletions = 0;
+ }
+ this.add(total, "deletions", entry.deletions);
+ return this.add(by_author, "deletions", entry.deletions);
+ },
+ get_total_data: function(parsed_log, field) {
+ var log, total_data;
+ log = parsed_log.total;
+ total_data = this.pick_field(log, field);
+ return _.sortBy(total_data, function(d) {
+ return d.date;
+ });
+ },
+ pick_field: function(log, field) {
+ var total_data;
+ total_data = [];
+ _.each(log, function(d) {
+ return total_data.push(_.pick(d, [field, 'date']));
+ });
+ return total_data;
+ },
+ get_author_data: function(parsed_log, field, date_range) {
+ var author_data, log;
+ if (date_range == null) {
+ date_range = null;
+ }
+ log = parsed_log.by_author;
+ author_data = [];
+ _.each(log, (function(_this) {
+ return function(log_entry) {
+ var parsed_log_entry;
+ parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range);
+ if (!_.isEmpty(parsed_log_entry.dates)) {
+ return author_data.push(parsed_log_entry);
+ }
+ };
+ })(this));
+ return _.sortBy(author_data, function(d) {
+ return d[field];
+ }).reverse();
+ },
+ parse_log_entry: function(log_entry, field, date_range) {
+ var parsed_entry;
+ parsed_entry = {};
+ parsed_entry.author_name = log_entry.author_name;
+ parsed_entry.author_email = log_entry.author_email;
+ parsed_entry.dates = {};
+ parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0;
+ _.each(_.omit(log_entry, 'author_name', 'author_email'), (function(_this) {
+ return function(value, key) {
+ if (_this.in_range(value.date, date_range)) {
+ parsed_entry.dates[value.date] = value[field];
+ parsed_entry.commits += value.commits;
+ parsed_entry.additions += value.additions;
+ return parsed_entry.deletions += value.deletions;
+ }
+ };
+ })(this));
+ return parsed_entry;
+ },
+ in_range: function(date, date_range) {
+ var ref;
+ if (date_range === null || (date_range[0] <= (ref = new Date(date)) && ref <= date_range[1])) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+};
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js.coffee b/app/assets/javascripts/graphs/stat_graph_contributors_util.js.coffee
deleted file mode 100644
index 31617c88b4a..00000000000
--- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js.coffee
+++ /dev/null
@@ -1,98 +0,0 @@
-window.ContributorsStatGraphUtil =
- parse_log: (log) ->
- total = {}
- by_author = {}
- by_email = {}
- for entry in log
- @add_date(entry.date, total) unless total[entry.date]?
-
- data = by_author[entry.author_name] || by_email[entry.author_email]
- data ?= @add_author(entry, by_author, by_email)
-
- @add_date(entry.date, data) unless data[entry.date]
- @store_data(entry, total[entry.date], data[entry.date])
- total = _.toArray(total)
- by_author = _.toArray(by_author)
- total: total, by_author: by_author
-
- add_date: (date, collection) ->
- collection[date] = {}
- collection[date].date = date
-
- add_author: (author, by_author, by_email) ->
- data = {}
- data.author_name = author.author_name
- data.author_email = author.author_email
- by_author[author.author_name] = data
- by_email[author.author_email] = data
-
- store_data: (entry, total, by_author) ->
- @store_commits(total, by_author)
- @store_additions(entry, total, by_author)
- @store_deletions(entry, total, by_author)
-
- store_commits: (total, by_author) ->
- @add(total, "commits", 1)
- @add(by_author, "commits", 1)
-
- add: (collection, field, value) ->
- collection[field] ?= 0
- collection[field] += value
-
- store_additions: (entry, total, by_author) ->
- entry.additions ?= 0
- @add(total, "additions", entry.additions)
- @add(by_author, "additions", entry.additions)
-
- store_deletions: (entry, total, by_author) ->
- entry.deletions ?= 0
- @add(total, "deletions", entry.deletions)
- @add(by_author, "deletions", entry.deletions)
-
- get_total_data: (parsed_log, field) ->
- log = parsed_log.total
- total_data = @pick_field(log, field)
- _.sortBy(total_data, (d) ->
- d.date
- )
- pick_field: (log, field) ->
- total_data = []
- _.each(log, (d) ->
- total_data.push(_.pick(d, [field, 'date']))
- )
- total_data
-
- get_author_data: (parsed_log, field, date_range = null) ->
- log = parsed_log.by_author
- author_data = []
-
- _.each(log, (log_entry) =>
- parsed_log_entry = @parse_log_entry(log_entry, field, date_range)
- if not _.isEmpty(parsed_log_entry.dates)
- author_data.push(parsed_log_entry)
- )
-
- _.sortBy(author_data, (d) ->
- d[field]
- ).reverse()
-
- parse_log_entry: (log_entry, field, date_range) ->
- parsed_entry = {}
- parsed_entry.author_name = log_entry.author_name
- parsed_entry.author_email = log_entry.author_email
- parsed_entry.dates = {}
- parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0
- _.each(_.omit(log_entry, 'author_name', 'author_email'), (value, key) =>
- if @in_range(value.date, date_range)
- parsed_entry.dates[value.date] = value[field]
- parsed_entry.commits += value.commits
- parsed_entry.additions += value.additions
- parsed_entry.deletions += value.deletions
- )
- return parsed_entry
-
- in_range: (date, date_range) ->
- if date_range is null || date_range[0] <= new Date(date) <= date_range[1]
- true
- else
- false
diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js
new file mode 100644
index 00000000000..3691827ee4b
--- /dev/null
+++ b/app/assets/javascripts/group_avatar.js
@@ -0,0 +1,18 @@
+this.GroupAvatar = (function() {
+ function GroupAvatar() {
+ $('.js-choose-group-avatar-button').bind("click", function() {
+ var form;
+ form = $(this).closest("form");
+ return form.find(".js-group-avatar-input").click();
+ });
+ $('.js-group-avatar-input').bind("change", function() {
+ var filename, form;
+ form = $(this).closest("form");
+ filename = $(this).val().replace(/^.*[\\\/]/, '');
+ return form.find(".js-avatar-filename").text(filename);
+ });
+ }
+
+ return GroupAvatar;
+
+})();
diff --git a/app/assets/javascripts/group_avatar.js.coffee b/app/assets/javascripts/group_avatar.js.coffee
deleted file mode 100644
index 0825fd3ce52..00000000000
--- a/app/assets/javascripts/group_avatar.js.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-class @GroupAvatar
- constructor: ->
- $('.js-choose-group-avatar-button').bind "click", ->
- form = $(this).closest("form")
- form.find(".js-group-avatar-input").click()
- $('.js-group-avatar-input').bind "change", ->
- form = $(this).closest("form")
- filename = $(this).val().replace(/^.*[\\\/]/, '')
- form.find(".js-avatar-filename").text(filename)
diff --git a/app/assets/javascripts/groups.js b/app/assets/javascripts/groups.js
new file mode 100644
index 00000000000..d5c6b51973a
--- /dev/null
+++ b/app/assets/javascripts/groups.js
@@ -0,0 +1,10 @@
+this.GroupMembers = (function() {
+ function GroupMembers() {
+ $('li.group_member').bind('ajax:success', function() {
+ return $(this).fadeOut();
+ });
+ }
+
+ return GroupMembers;
+
+})();
diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee
deleted file mode 100644
index cc905e91ea2..00000000000
--- a/app/assets/javascripts/groups.js.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-class @GroupMembers
- constructor: ->
- $('li.group_member').bind 'ajax:success', ->
- $(this).fadeOut()
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
new file mode 100644
index 00000000000..1a5bf964e01
--- /dev/null
+++ b/app/assets/javascripts/groups_select.js
@@ -0,0 +1,64 @@
+var slice = [].slice;
+
+this.GroupsSelect = (function() {
+ function GroupsSelect() {
+ $('.ajax-groups-select').each((function(_this) {
+ return function(i, select) {
+ var skip_ldap;
+ skip_ldap = $(select).hasClass('skip_ldap');
+ return $(select).select2({
+ placeholder: "Search for a group",
+ multiple: $(select).hasClass('multiselect'),
+ minimumInputLength: 0,
+ query: function(query) {
+ return Api.groups(query.term, skip_ldap, function(groups) {
+ var data;
+ data = {
+ results: groups
+ };
+ return query.callback(data);
+ });
+ },
+ initSelection: function(element, callback) {
+ var id;
+ id = $(element).val();
+ if (id !== "") {
+ return Api.group(id, callback);
+ }
+ },
+ formatResult: function() {
+ var args;
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ return _this.formatResult.apply(_this, args);
+ },
+ formatSelection: function() {
+ var args;
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ return _this.formatSelection.apply(_this, args);
+ },
+ dropdownCssClass: "ajax-groups-dropdown",
+ escapeMarkup: function(m) {
+ return m;
+ }
+ });
+ };
+ })(this));
+ }
+
+ GroupsSelect.prototype.formatResult = function(group) {
+ var avatar;
+ if (group.avatar_url) {
+ avatar = group.avatar_url;
+ } else {
+ avatar = gon.default_avatar_url;
+ }
+ return "<div class='group-result'> <div class='group-name'>" + group.name + "</div> <div class='group-path'>" + group.path + "</div> </div>";
+ };
+
+ GroupsSelect.prototype.formatSelection = function(group) {
+ return group.name;
+ };
+
+ return GroupsSelect;
+
+})();
diff --git a/app/assets/javascripts/groups_select.js.coffee b/app/assets/javascripts/groups_select.js.coffee
deleted file mode 100644
index 1084e2a17d1..00000000000
--- a/app/assets/javascripts/groups_select.js.coffee
+++ /dev/null
@@ -1,41 +0,0 @@
-class @GroupsSelect
- constructor: ->
- $('.ajax-groups-select').each (i, select) =>
- skip_ldap = $(select).hasClass('skip_ldap')
-
- $(select).select2
- placeholder: "Search for a group"
- multiple: $(select).hasClass('multiselect')
- minimumInputLength: 0
- query: (query) ->
- Api.groups query.term, skip_ldap, (groups) ->
- data = { results: groups }
- query.callback(data)
-
- initSelection: (element, callback) ->
- id = $(element).val()
- if id isnt ""
- Api.group(id, callback)
-
-
- formatResult: (args...) =>
- @formatResult(args...)
- formatSelection: (args...) =>
- @formatSelection(args...)
- dropdownCssClass: "ajax-groups-dropdown"
- escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
- m
-
- formatResult: (group) ->
- if group.avatar_url
- avatar = group.avatar_url
- else
- avatar = gon.default_avatar_url
-
- "<div class='group-result'>
- <div class='group-name'>#{group.name}</div>
- <div class='group-path'>#{group.path}</div>
- </div>"
-
- formatSelection: (group) ->
- group.name
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
new file mode 100644
index 00000000000..cd3aeed8695
--- /dev/null
+++ b/app/assets/javascripts/importer_status.js
@@ -0,0 +1,66 @@
+this.ImporterStatus = (function() {
+ function ImporterStatus(jobs_url, import_url) {
+ this.jobs_url = jobs_url;
+ this.import_url = import_url;
+ this.initStatusPage();
+ this.setAutoUpdate();
+ }
+
+ ImporterStatus.prototype.initStatusPage = function() {
+ $('.js-add-to-import').off('click').on('click', (function(_this) {
+ return function(e) {
+ var $btn, $namespace_input, $target_field, $tr, id, new_namespace;
+ $btn = $(e.currentTarget);
+ $tr = $btn.closest('tr');
+ $target_field = $tr.find('.import-target');
+ $namespace_input = $target_field.find('input');
+ id = $tr.attr('id').replace('repo_', '');
+ new_namespace = null;
+ if ($namespace_input.length > 0) {
+ new_namespace = $namespace_input.prop('value');
+ $target_field.empty().append(new_namespace + "/" + ($target_field.data('project_name')));
+ }
+ $btn.disable().addClass('is-loading');
+ return $.post(_this.import_url, {
+ repo_id: id,
+ new_namespace: new_namespace
+ }, {
+ dataType: 'script'
+ });
+ };
+ })(this));
+ return $('.js-import-all').off('click').on('click', function(e) {
+ var $btn;
+ $btn = $(this);
+ $btn.disable().addClass('is-loading');
+ return $('.js-add-to-import').each(function() {
+ return $(this).trigger('click');
+ });
+ });
+ };
+
+ ImporterStatus.prototype.setAutoUpdate = function() {
+ return setInterval(((function(_this) {
+ return function() {
+ return $.get(_this.jobs_url, function(data) {
+ return $.each(data, function(i, job) {
+ var job_item, status_field;
+ job_item = $("#project_" + job.id);
+ status_field = job_item.find(".job-status");
+ if (job.import_status === 'finished') {
+ job_item.removeClass("active").addClass("success");
+ return status_field.html('<span><i class="fa fa-check"></i> done</span>');
+ } else if (job.import_status === 'started') {
+ return status_field.html("<i class='fa fa-spinner fa-spin'></i> started");
+ } else {
+ return status_field.html(job.import_status);
+ }
+ });
+ });
+ };
+ })(this)), 4000);
+ };
+
+ return ImporterStatus;
+
+})();
diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee
deleted file mode 100644
index eb046eb2eff..00000000000
--- a/app/assets/javascripts/importer_status.js.coffee
+++ /dev/null
@@ -1,53 +0,0 @@
-class @ImporterStatus
- constructor: (@jobs_url, @import_url) ->
- this.initStatusPage()
- this.setAutoUpdate()
-
- initStatusPage: ->
- $('.js-add-to-import')
- .off 'click'
- .on 'click', (e) =>
- $btn = $(e.currentTarget)
- $tr = $btn.closest('tr')
- $target_field = $tr.find('.import-target')
- $namespace_input = $target_field.find('input')
- id = $tr.attr('id').replace('repo_', '')
- new_namespace = null
-
- if $namespace_input.length > 0
- new_namespace = $namespace_input.prop('value')
- $target_field.empty().append("#{new_namespace}/#{$target_field.data('project_name')}")
-
- $btn
- .disable()
- .addClass 'is-loading'
-
- $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
-
- $('.js-import-all')
- .off 'click'
- .on 'click', (e) ->
- $btn = $(@)
- $btn
- .disable()
- .addClass 'is-loading'
-
- $('.js-add-to-import').each ->
- $(this).trigger('click')
-
- setAutoUpdate: ->
- setInterval (=>
- $.get @jobs_url, (data) =>
- $.each data, (i, job) =>
- job_item = $("#project_" + job.id)
- status_field = job_item.find(".job-status")
-
- if job.import_status == 'finished'
- job_item.removeClass("active").addClass("success")
- status_field.html('<span><i class="fa fa-check"></i> done</span>')
- else if job.import_status == 'started'
- status_field.html("<i class='fa fa-spinner fa-spin'></i> started")
- else
- status_field.html(job.import_status)
-
- ), 4000
diff --git a/app/assets/javascripts/issuable.js b/app/assets/javascripts/issuable.js
new file mode 100644
index 00000000000..a5151a3bc2e
--- /dev/null
+++ b/app/assets/javascripts/issuable.js
@@ -0,0 +1,86 @@
+var issuable_created;
+
+issuable_created = false;
+
+this.Issuable = {
+ init: function() {
+ if (!issuable_created) {
+ issuable_created = true;
+ Issuable.initTemplates();
+ Issuable.initSearch();
+ Issuable.initChecks();
+ return Issuable.initLabelFilterRemove();
+ }
+ },
+ initTemplates: function() {
+ return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>');
+ },
+ initSearch: function() {
+ this.timer = null;
+ return $('#issue_search').off('keyup').on('keyup', function() {
+ clearTimeout(this.timer);
+ return this.timer = setTimeout(function() {
+ var $form, $input, $search;
+ $search = $('#issue_search');
+ $form = $('.js-filter-form');
+ $input = $("input[name='" + ($search.attr('name')) + "']", $form);
+ if ($input.length === 0) {
+ $form.append("<input type='hidden' name='" + ($search.attr('name')) + "' value='" + (_.escape($search.val())) + "'/>");
+ } else {
+ $input.val($search.val());
+ }
+ if ($search.val() !== '') {
+ return Issuable.filterResults($form);
+ }
+ }, 500);
+ });
+ },
+ initLabelFilterRemove: function() {
+ return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) {
+ var $button;
+ $button = $(this);
+ $('input[name="label_name[]"]').filter(function() {
+ return this.value === $button.data('label');
+ }).remove();
+ Issuable.filterResults($('.filter-form'));
+ return $('.js-label-select').trigger('update.label');
+ });
+ },
+ filterResults: (function(_this) {
+ return function(form) {
+ var formAction, formData, issuesUrl;
+ formData = form.serialize();
+ formAction = form.attr('action');
+ issuesUrl = formAction;
+ issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&');
+ issuesUrl += formData;
+ return Turbolinks.visit(issuesUrl);
+ };
+ })(this),
+ initChecks: function() {
+ this.issuableBulkActions = $('.bulk-update').data('bulkActions');
+ $('.check_all_issues').off('click').on('click', function() {
+ $('.selected_issue').prop('checked', this.checked);
+ return Issuable.checkChanged();
+ });
+ return $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(this));
+ },
+ checkChanged: function() {
+ var checked_issues, ids;
+ checked_issues = $('.selected_issue:checked');
+ if (checked_issues.length > 0) {
+ ids = $.map(checked_issues, function(value) {
+ return $(value).data('id');
+ });
+ $('#update_issues_ids').val(ids);
+ $('.issues-other-filters').hide();
+ $('.issues_bulk_update').show();
+ } else {
+ $('#update_issues_ids').val([]);
+ $('.issues_bulk_update').hide();
+ $('.issues-other-filters').show();
+ this.issuableBulkActions.willUpdateLabels = false;
+ }
+ return true;
+ }
+};
diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee
deleted file mode 100644
index 7f795f8096b..00000000000
--- a/app/assets/javascripts/issuable.js.coffee
+++ /dev/null
@@ -1,93 +0,0 @@
-issuable_created = false
-@Issuable =
- init: ->
- unless issuable_created
- issuable_created = true
- Issuable.initTemplates()
- Issuable.initSearch()
- Issuable.initChecks()
- Issuable.initLabelFilterRemove()
-
- initTemplates: ->
- Issuable.labelRow = _.template(
- '<% _.each(labels, function(label){ %>
- <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;">
- <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body">
- <%- label.title %>
- </a>
- <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>">
- <i class="fa fa-times"></i>
- </button>
- </span>
- <% }); %>'
- )
-
- initSearch: ->
- @timer = null
- $('#issue_search')
- .off 'keyup'
- .on 'keyup', ->
- clearTimeout(@timer)
- @timer = setTimeout( ->
- $search = $('#issue_search')
- $form = $('.js-filter-form')
- $input = $("input[name='#{$search.attr('name')}']", $form)
- if $input.length is 0
- $form.append "<input type='hidden' name='#{$search.attr('name')}' value='#{_.escape($search.val())}'/>"
- else
- $input.val $search.val()
- Issuable.filterResults $form if $search.val() isnt ''
- , 500)
-
- initLabelFilterRemove: ->
- $(document)
- .off 'click', '.js-label-filter-remove'
- .on 'click', '.js-label-filter-remove', (e) ->
- $button = $(@)
-
- # Remove the label input box
- $('input[name="label_name[]"]')
- .filter -> @value is $button.data('label')
- .remove()
-
- # Submit the form to get new data
- Issuable.filterResults $('.filter-form')
- $('.js-label-select').trigger('update.label')
-
- filterResults: (form) =>
- formData = form.serialize()
-
- formAction = form.attr('action')
- issuesUrl = formAction
- issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}")
- issuesUrl += formData
-
- Turbolinks.visit(issuesUrl)
-
- initChecks: ->
- @issuableBulkActions = $('.bulk-update').data('bulkActions')
-
- $('.check_all_issues').off('click').on('click', ->
- $('.selected_issue').prop('checked', @checked)
- Issuable.checkChanged()
- )
-
- $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(@))
-
-
- checkChanged: ->
- checked_issues = $('.selected_issue:checked')
- if checked_issues.length > 0
- ids = $.map checked_issues, (value) ->
- $(value).data('id')
-
- $('#update_issues_ids').val ids
- $('.issues-other-filters').hide()
- $('.issues_bulk_update').show()
- else
- $('#update_issues_ids').val []
- $('.issues_bulk_update').hide()
- $('.issues-other-filters').show()
- @issuableBulkActions.willUpdateLabels = false
-
- return true
diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js
new file mode 100644
index 00000000000..b7c1c40f7d3
--- /dev/null
+++ b/app/assets/javascripts/issuable_context.js
@@ -0,0 +1,66 @@
+this.IssuableContext = (function() {
+ function IssuableContext(currentUser) {
+ this.initParticipants();
+ new UsersSelect(currentUser);
+ $('select.select2').select2({
+ width: 'resolve',
+ dropdownAutoWidth: true
+ });
+ $(".issuable-sidebar .inline-update").on("change", "select", function() {
+ return $(this).submit();
+ });
+ $(".issuable-sidebar .inline-update").on("change", ".js-assignee", function() {
+ return $(this).submit();
+ });
+ $(document).off('click', '.issuable-sidebar .dropdown-content a').on('click', '.issuable-sidebar .dropdown-content a', function(e) {
+ return e.preventDefault();
+ });
+ $(document).off('click', '.edit-link').on('click', '.edit-link', function(e) {
+ var $block, $selectbox;
+ e.preventDefault();
+ $block = $(this).parents('.block');
+ $selectbox = $block.find('.selectbox');
+ if ($selectbox.is(':visible')) {
+ $selectbox.hide();
+ $block.find('.value').show();
+ } else {
+ $selectbox.show();
+ $block.find('.value').hide();
+ }
+ if ($selectbox.is(':visible')) {
+ return setTimeout(function() {
+ return $block.find('.dropdown-menu-toggle').trigger('click');
+ }, 0);
+ }
+ });
+ $(".right-sidebar").niceScroll();
+ }
+
+ IssuableContext.prototype.initParticipants = function() {
+ var _this;
+ _this = this;
+ $(document).on("click", ".js-participants-more", this.toggleHiddenParticipants);
+ return $(".js-participants-author").each(function(i) {
+ if (i >= _this.PARTICIPANTS_ROW_COUNT) {
+ return $(this).addClass("js-participants-hidden").hide();
+ }
+ });
+ };
+
+ IssuableContext.prototype.toggleHiddenParticipants = function(e) {
+ var currentText, lessText, originalText;
+ e.preventDefault();
+ currentText = $(this).text().trim();
+ lessText = $(this).data("less-text");
+ originalText = $(this).data("original-text");
+ if (currentText === originalText) {
+ $(this).text(lessText);
+ } else {
+ $(this).text(originalText);
+ }
+ return $(".js-participants-hidden").toggle();
+ };
+
+ return IssuableContext;
+
+})();
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
deleted file mode 100644
index 3c491ebfc4c..00000000000
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ /dev/null
@@ -1,60 +0,0 @@
-class @IssuableContext
- constructor: (currentUser) ->
- @initParticipants()
- new UsersSelect(currentUser)
- $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
-
- $(".issuable-sidebar .inline-update").on "change", "select", ->
- $(this).submit()
- $(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
- $(this).submit()
-
- $(document)
- .off 'click', '.issuable-sidebar .dropdown-content a'
- .on 'click', '.issuable-sidebar .dropdown-content a', (e) ->
- e.preventDefault()
-
- $(document)
- .off 'click', '.edit-link'
- .on 'click', '.edit-link', (e) ->
- e.preventDefault()
-
- $block = $(@).parents('.block')
- $selectbox = $block.find('.selectbox')
- if $selectbox.is(':visible')
- $selectbox.hide()
- $block.find('.value').show()
- else
- $selectbox.show()
- $block.find('.value').hide()
-
- if $selectbox.is(':visible')
- setTimeout ->
- $block.find('.dropdown-menu-toggle').trigger 'click'
- , 0
-
- $(".right-sidebar").niceScroll()
-
- initParticipants: ->
- _this = @
- $(document).on "click", ".js-participants-more", @toggleHiddenParticipants
-
- $(".js-participants-author").each (i) ->
- if i >= _this.PARTICIPANTS_ROW_COUNT
- $(@)
- .addClass "js-participants-hidden"
- .hide()
-
- toggleHiddenParticipants: (e) ->
- e.preventDefault()
-
- currentText = $(this).text().trim()
- lessText = $(this).data("less-text")
- originalText = $(this).data("original-text")
-
- if currentText is originalText
- $(this).text(lessText)
- else
- $(this).text(originalText)
-
- $(".js-participants-hidden").toggle()
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
new file mode 100644
index 00000000000..cd0b72c5fc2
--- /dev/null
+++ b/app/assets/javascripts/issuable_form.js
@@ -0,0 +1,133 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.IssuableForm = (function() {
+ IssuableForm.prototype.issueMoveConfirmMsg = 'Are you sure you want to move this issue to another project?';
+
+ IssuableForm.prototype.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i;
+
+ function IssuableForm(form) {
+ var $issuableDueDate;
+ this.form = form;
+ this.toggleWip = bind(this.toggleWip, this);
+ this.renderWipExplanation = bind(this.renderWipExplanation, this);
+ this.resetAutosave = bind(this.resetAutosave, this);
+ this.handleSubmit = bind(this.handleSubmit, this);
+ GitLab.GfmAutoComplete.setup();
+ new UsersSelect();
+ new ZenMode();
+ this.titleField = this.form.find("input[name*='[title]']");
+ this.descriptionField = this.form.find("textarea[name*='[description]']");
+ this.issueMoveField = this.form.find("#move_to_project_id");
+ if (!(this.titleField.length && this.descriptionField.length)) {
+ return;
+ }
+ this.initAutosave();
+ this.form.on("submit", this.handleSubmit);
+ this.form.on("click", ".btn-cancel", this.resetAutosave);
+ this.initWip();
+ this.initMoveDropdown();
+ $issuableDueDate = $('#issuable-due-date');
+ if ($issuableDueDate.length) {
+ $('.datepicker').datepicker({
+ dateFormat: 'yy-mm-dd',
+ onSelect: function(dateText, inst) {
+ return $issuableDueDate.val(dateText);
+ }
+ }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $issuableDueDate.val()));
+ }
+ }
+
+ IssuableForm.prototype.initAutosave = function() {
+ new Autosave(this.titleField, [document.location.pathname, document.location.search, "title"]);
+ return new Autosave(this.descriptionField, [document.location.pathname, document.location.search, "description"]);
+ };
+
+ IssuableForm.prototype.handleSubmit = function() {
+ var ref, ref1;
+ if (((ref = parseInt((ref1 = this.issueMoveField) != null ? ref1.val() : void 0)) != null ? ref : 0) > 0) {
+ if (!confirm(this.issueMoveConfirmMsg)) {
+ return false;
+ }
+ }
+ return this.resetAutosave();
+ };
+
+ IssuableForm.prototype.resetAutosave = function() {
+ this.titleField.data("autosave").reset();
+ return this.descriptionField.data("autosave").reset();
+ };
+
+ IssuableForm.prototype.initWip = function() {
+ this.$wipExplanation = this.form.find(".js-wip-explanation");
+ this.$noWipExplanation = this.form.find(".js-no-wip-explanation");
+ if (!(this.$wipExplanation.length && this.$noWipExplanation.length)) {
+ return;
+ }
+ this.form.on("click", ".js-toggle-wip", this.toggleWip);
+ this.titleField.on("keyup blur", this.renderWipExplanation);
+ return this.renderWipExplanation();
+ };
+
+ IssuableForm.prototype.workInProgress = function() {
+ return this.wipRegex.test(this.titleField.val());
+ };
+
+ IssuableForm.prototype.renderWipExplanation = function() {
+ if (this.workInProgress()) {
+ this.$wipExplanation.show();
+ return this.$noWipExplanation.hide();
+ } else {
+ this.$wipExplanation.hide();
+ return this.$noWipExplanation.show();
+ }
+ };
+
+ IssuableForm.prototype.toggleWip = function(event) {
+ event.preventDefault();
+ if (this.workInProgress()) {
+ this.removeWip();
+ } else {
+ this.addWip();
+ }
+ return this.renderWipExplanation();
+ };
+
+ IssuableForm.prototype.removeWip = function() {
+ return this.titleField.val(this.titleField.val().replace(this.wipRegex, ""));
+ };
+
+ IssuableForm.prototype.addWip = function() {
+ return this.titleField.val("WIP: " + (this.titleField.val()));
+ };
+
+ IssuableForm.prototype.initMoveDropdown = function() {
+ var $moveDropdown;
+ $moveDropdown = $('.js-move-dropdown');
+ if ($moveDropdown.length) {
+ return $('.js-move-dropdown').select2({
+ ajax: {
+ url: $moveDropdown.data('projects-url'),
+ results: function(data) {
+ return {
+ results: data
+ };
+ },
+ data: function(query) {
+ return {
+ search: query
+ };
+ }
+ },
+ formatResult: function(project) {
+ return project.name_with_namespace;
+ },
+ formatSelection: function(project) {
+ return project.name_with_namespace;
+ }
+ });
+ }
+ };
+
+ return IssuableForm;
+
+})();
diff --git a/app/assets/javascripts/issuable_form.js.coffee b/app/assets/javascripts/issuable_form.js.coffee
deleted file mode 100644
index 5b7a4831dfc..00000000000
--- a/app/assets/javascripts/issuable_form.js.coffee
+++ /dev/null
@@ -1,112 +0,0 @@
-class @IssuableForm
- issueMoveConfirmMsg: 'Are you sure you want to move this issue to another project?'
- wipRegex: /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i
-
- constructor: (@form) ->
- GitLab.GfmAutoComplete.setup()
- new UsersSelect()
- new ZenMode()
-
- @titleField = @form.find("input[name*='[title]']")
- @descriptionField = @form.find("textarea[name*='[description]']")
- @issueMoveField = @form.find("#move_to_project_id")
-
- return unless @titleField.length && @descriptionField.length
-
- @initAutosave()
-
- @form.on "submit", @handleSubmit
- @form.on "click", ".btn-cancel", @resetAutosave
-
- @initWip()
- @initMoveDropdown()
-
- $issuableDueDate = $('#issuable-due-date')
-
- if $issuableDueDate.length
- $('.datepicker').datepicker(
- dateFormat: 'yy-mm-dd',
- onSelect: (dateText, inst) ->
- $issuableDueDate.val dateText
- ).datepicker 'setDate', $.datepicker.parseDate('yy-mm-dd', $issuableDueDate.val())
-
- initAutosave: ->
- new Autosave @titleField, [
- document.location.pathname,
- document.location.search,
- "title"
- ]
-
- new Autosave @descriptionField, [
- document.location.pathname,
- document.location.search,
- "description"
- ]
-
- handleSubmit: =>
- if (parseInt(@issueMoveField?.val()) ? 0) > 0
- return false unless confirm(@issueMoveConfirmMsg)
-
- @resetAutosave()
-
- resetAutosave: =>
- @titleField.data("autosave").reset()
- @descriptionField.data("autosave").reset()
-
- initWip: ->
- @$wipExplanation = @form.find(".js-wip-explanation")
- @$noWipExplanation = @form.find(".js-no-wip-explanation")
- return unless @$wipExplanation.length and @$noWipExplanation.length
-
- @form.on "click", ".js-toggle-wip", @toggleWip
-
- @titleField.on "keyup blur", @renderWipExplanation
-
- @renderWipExplanation()
-
- workInProgress: ->
- @wipRegex.test @titleField.val()
-
- renderWipExplanation: =>
- if @workInProgress()
- @$wipExplanation.show()
- @$noWipExplanation.hide()
- else
- @$wipExplanation.hide()
- @$noWipExplanation.show()
-
- toggleWip: (event) =>
- event.preventDefault()
-
- if @workInProgress()
- @removeWip()
- else
- @addWip()
-
- @renderWipExplanation()
-
- removeWip: ->
- @titleField.val @titleField.val().replace(@wipRegex, "")
-
- addWip: ->
- @titleField.val "WIP: #{@titleField.val()}"
-
- initMoveDropdown: ->
- $moveDropdown = $('.js-move-dropdown')
-
- if $moveDropdown.length
- $('.js-move-dropdown').select2
- ajax:
- url: $moveDropdown.data('projects-url')
- results: (data) ->
- return {
- results: data
- }
- data: (query) ->
- {
- search: query
- }
- formatResult: (project) ->
- project.name_with_namespace
- formatSelection: (project) ->
- project.name_with_namespace
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
new file mode 100644
index 00000000000..c9587da4c9f
--- /dev/null
+++ b/app/assets/javascripts/issue.js
@@ -0,0 +1,148 @@
+
+/*= require flash */
+
+/*= require jquery.waitforimages */
+
+/*= require task_list */
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.Issue = (function() {
+ function Issue() {
+ this.submitNoteForm = bind(this.submitNoteForm, this);
+ this.disableTaskList();
+ if ($('a.btn-close').length) {
+ this.initTaskList();
+ this.initIssueBtnEventListeners();
+ }
+ this.initMergeRequests();
+ this.initRelatedBranches();
+ this.initCanCreateBranch();
+ }
+
+ Issue.prototype.initTaskList = function() {
+ $('.detail-page-description .js-task-list-container').taskList('enable');
+ return $(document).on('tasklist:changed', '.detail-page-description .js-task-list-container', this.updateTaskList);
+ };
+
+ Issue.prototype.initIssueBtnEventListeners = function() {
+ var _this, issueFailMessage;
+ _this = this;
+ issueFailMessage = 'Unable to update this issue at this time.';
+ return $('a.btn-close, a.btn-reopen').on('click', function(e) {
+ var $this, isClose, shouldSubmit, url;
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ $this = $(this);
+ isClose = $this.hasClass('btn-close');
+ shouldSubmit = $this.hasClass('btn-comment');
+ if (shouldSubmit) {
+ _this.submitNoteForm($this.closest('form'));
+ }
+ $this.prop('disabled', true);
+ url = $this.attr('href');
+ return $.ajax({
+ type: 'PUT',
+ url: url,
+ error: function(jqXHR, textStatus, errorThrown) {
+ var issueStatus;
+ issueStatus = isClose ? 'close' : 'open';
+ return new Flash(issueFailMessage, 'alert');
+ },
+ success: function(data, textStatus, jqXHR) {
+ if ('id' in data) {
+ $(document).trigger('issuable:change');
+ if (isClose) {
+ $('a.btn-close').addClass('hidden');
+ $('a.btn-reopen').removeClass('hidden');
+ $('div.status-box-closed').removeClass('hidden');
+ $('div.status-box-open').addClass('hidden');
+ } else {
+ $('a.btn-reopen').addClass('hidden');
+ $('a.btn-close').removeClass('hidden');
+ $('div.status-box-closed').addClass('hidden');
+ $('div.status-box-open').removeClass('hidden');
+ }
+ } else {
+ new Flash(issueFailMessage, 'alert');
+ }
+ return $this.prop('disabled', false);
+ }
+ });
+ });
+ };
+
+ Issue.prototype.submitNoteForm = function(form) {
+ var noteText;
+ noteText = form.find("textarea.js-note-text").val();
+ if (noteText.trim().length > 0) {
+ return form.submit();
+ }
+ };
+
+ Issue.prototype.disableTaskList = function() {
+ $('.detail-page-description .js-task-list-container').taskList('disable');
+ return $(document).off('tasklist:changed', '.detail-page-description .js-task-list-container');
+ };
+
+ Issue.prototype.updateTaskList = function() {
+ var patchData;
+ patchData = {};
+ patchData['issue'] = {
+ 'description': $('.js-task-list-field', this).val()
+ };
+ return $.ajax({
+ type: 'PATCH',
+ url: $('form.js-issuable-update').attr('action'),
+ data: patchData
+ });
+ };
+
+ Issue.prototype.initMergeRequests = function() {
+ var $container;
+ $container = $('#merge-requests');
+ return $.getJSON($container.data('url')).error(function() {
+ return new Flash('Failed to load referenced merge requests', 'alert');
+ }).success(function(data) {
+ if ('html' in data) {
+ return $container.html(data.html);
+ }
+ });
+ };
+
+ Issue.prototype.initRelatedBranches = function() {
+ var $container;
+ $container = $('#related-branches');
+ return $.getJSON($container.data('url')).error(function() {
+ return new Flash('Failed to load related branches', 'alert');
+ }).success(function(data) {
+ if ('html' in data) {
+ return $container.html(data.html);
+ }
+ });
+ };
+
+ Issue.prototype.initCanCreateBranch = function() {
+ var $container;
+ $container = $('div#new-branch');
+ if ($container.length === 0) {
+ return;
+ }
+ return $.getJSON($container.data('path')).error(function() {
+ $container.find('.checking').hide();
+ $container.find('.unavailable').show();
+ return new Flash('Failed to check if a new branch can be created.', 'alert');
+ }).success(function(data) {
+ if (data.can_create_branch) {
+ $container.find('.checking').hide();
+ $container.find('.available').show();
+ return $container.find('a').attr('disabled', false);
+ } else {
+ $container.find('.checking').hide();
+ return $container.find('.unavailable').show();
+ }
+ });
+ };
+
+ return Issue;
+
+})();
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
deleted file mode 100644
index f446aa49cde..00000000000
--- a/app/assets/javascripts/issue.js.coffee
+++ /dev/null
@@ -1,117 +0,0 @@
-#= require flash
-#= require jquery.waitforimages
-#= require task_list
-
-class @Issue
- constructor: ->
- # Prevent duplicate event bindings
- @disableTaskList()
- if $('a.btn-close').length
- @initTaskList()
- @initIssueBtnEventListeners()
-
- @initMergeRequests()
- @initRelatedBranches()
- @initCanCreateBranch()
-
- initTaskList: ->
- $('.detail-page-description .js-task-list-container').taskList('enable')
- $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
-
- initIssueBtnEventListeners: ->
- _this = @
- issueFailMessage = 'Unable to update this issue at this time.'
- $('a.btn-close, a.btn-reopen').on 'click', (e) ->
- e.preventDefault()
- e.stopImmediatePropagation()
- $this = $(this)
- isClose = $this.hasClass('btn-close')
- shouldSubmit = $this.hasClass('btn-comment')
- if shouldSubmit
- _this.submitNoteForm($this.closest('form'))
- $this.prop('disabled', true)
- url = $this.attr('href')
- $.ajax
- type: 'PUT'
- url: url,
- error: (jqXHR, textStatus, errorThrown) ->
- issueStatus = if isClose then 'close' else 'open'
- new Flash(issueFailMessage, 'alert')
- success: (data, textStatus, jqXHR) ->
- if 'id' of data
- $(document).trigger('issuable:change');
- if isClose
- $('a.btn-close').addClass('hidden')
- $('a.btn-reopen').removeClass('hidden')
- $('div.status-box-closed').removeClass('hidden')
- $('div.status-box-open').addClass('hidden')
- else
- $('a.btn-reopen').addClass('hidden')
- $('a.btn-close').removeClass('hidden')
- $('div.status-box-closed').addClass('hidden')
- $('div.status-box-open').removeClass('hidden')
- else
- new Flash(issueFailMessage, 'alert')
- $this.prop('disabled', false)
-
- submitNoteForm: (form) =>
- noteText = form.find("textarea.js-note-text").val()
- if noteText.trim().length > 0
- form.submit()
-
- disableTaskList: ->
- $('.detail-page-description .js-task-list-container').taskList('disable')
- $(document).off 'tasklist:changed', '.detail-page-description .js-task-list-container'
-
- # TODO (rspeicher): Make the issue description inline-editable like a note so
- # that we can re-use its form here
- updateTaskList: ->
- patchData = {}
- patchData['issue'] = {'description': $('.js-task-list-field', this).val()}
-
- $.ajax
- type: 'PATCH'
- url: $('form.js-issuable-update').attr('action')
- data: patchData
-
- initMergeRequests: ->
- $container = $('#merge-requests')
-
- $.getJSON($container.data('url'))
- .error ->
- new Flash('Failed to load referenced merge requests', 'alert')
- .success (data) ->
- if 'html' of data
- $container.html(data.html)
-
- initRelatedBranches: ->
- $container = $('#related-branches')
-
- $.getJSON($container.data('url'))
- .error ->
- new Flash('Failed to load related branches', 'alert')
- .success (data) ->
- if 'html' of data
- $container.html(data.html)
-
- initCanCreateBranch: ->
- $container = $('div#new-branch')
-
- # If the user doesn't have the required permissions the container isn't
- # rendered at all.
- return if $container.length is 0
-
- $.getJSON($container.data('path'))
- .error ->
- $container.find('.checking').hide()
- $container.find('.unavailable').show()
-
- new Flash('Failed to check if a new branch can be created.', 'alert')
- .success (data) ->
- if data.can_create_branch
- $container.find('.checking').hide()
- $container.find('.available').show()
- $container.find('a').attr('disabled', false)
- else
- $container.find('.checking').hide()
- $container.find('.unavailable').show()
diff --git a/app/assets/javascripts/issue_status_select.js b/app/assets/javascripts/issue_status_select.js
new file mode 100644
index 00000000000..16bc009d5c5
--- /dev/null
+++ b/app/assets/javascripts/issue_status_select.js
@@ -0,0 +1,32 @@
+this.IssueStatusSelect = (function() {
+ function IssueStatusSelect() {
+ $('.js-issue-status').each(function(i, el) {
+ var fieldName;
+ fieldName = $(el).data("field-name");
+ return $(el).glDropdown({
+ selectable: true,
+ fieldName: fieldName,
+ toggleLabel: (function(_this) {
+ return function(selected, el, instance) {
+ var $item, label;
+ label = 'Author';
+ $item = instance.dropdown.find('.is-active');
+ if ($item.length) {
+ label = $item.text();
+ }
+ return label;
+ };
+ })(this),
+ clicked: function(item, $el, e) {
+ return e.preventDefault();
+ },
+ id: function(obj, el) {
+ return $(el).data("id");
+ }
+ });
+ });
+ }
+
+ return IssueStatusSelect;
+
+})();
diff --git a/app/assets/javascripts/issue_status_select.js.coffee b/app/assets/javascripts/issue_status_select.js.coffee
deleted file mode 100644
index ed50e2e698f..00000000000
--- a/app/assets/javascripts/issue_status_select.js.coffee
+++ /dev/null
@@ -1,18 +0,0 @@
-class @IssueStatusSelect
- constructor: ->
- $('.js-issue-status').each (i, el) ->
- fieldName = $(el).data("field-name")
-
- $(el).glDropdown(
- selectable: true
- fieldName: fieldName
- toggleLabel: (selected, el, instance) =>
- label = 'Author'
- $item = instance.dropdown.find('.is-active')
- label = $item.text() if $item.length
- label
- clicked: (item, $el, e)->
- e.preventDefault()
- id: (obj, el) ->
- $(el).data("id")
- )
diff --git a/app/assets/javascripts/issues-bulk-assignment.js b/app/assets/javascripts/issues-bulk-assignment.js
new file mode 100644
index 00000000000..d26e66300e8
--- /dev/null
+++ b/app/assets/javascripts/issues-bulk-assignment.js
@@ -0,0 +1,158 @@
+this.IssuableBulkActions = (function() {
+ function IssuableBulkActions(opts) {
+ var ref, ref1, ref2;
+ if (opts == null) {
+ opts = {};
+ }
+ this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issues-list .issue');
+ this.form.data('bulkActions', this);
+ this.willUpdateLabels = false;
+ this.bindEvents();
+ Issuable.initChecks();
+ }
+
+ IssuableBulkActions.prototype.getElement = function(selector) {
+ return this.container.find(selector);
+ };
+
+ IssuableBulkActions.prototype.bindEvents = function() {
+ return this.form.off('submit').on('submit', this.onFormSubmit.bind(this));
+ };
+
+ IssuableBulkActions.prototype.onFormSubmit = function(e) {
+ e.preventDefault();
+ return this.submit();
+ };
+
+ IssuableBulkActions.prototype.submit = function() {
+ var _this, xhr;
+ _this = this;
+ xhr = $.ajax({
+ url: this.form.attr('action'),
+ method: this.form.attr('method'),
+ dataType: 'JSON',
+ data: this.getFormDataAsObject()
+ });
+ xhr.done(function(response, status, xhr) {
+ return location.reload();
+ });
+ xhr.fail(function() {
+ return new Flash("Issue update failed");
+ });
+ return xhr.always(this.onFormSubmitAlways.bind(this));
+ };
+
+ IssuableBulkActions.prototype.onFormSubmitAlways = function() {
+ return this.form.find('[type="submit"]').enable();
+ };
+
+ IssuableBulkActions.prototype.getSelectedIssues = function() {
+ return this.issues.has('.selected_issue:checked');
+ };
+
+ IssuableBulkActions.prototype.getLabelsFromSelection = function() {
+ var labels;
+ labels = [];
+ this.getSelectedIssues().map(function() {
+ var _labels;
+ _labels = $(this).data('labels');
+ if (_labels) {
+ return _labels.map(function(labelId) {
+ if (labels.indexOf(labelId) === -1) {
+ return labels.push(labelId);
+ }
+ });
+ }
+ });
+ return labels;
+ };
+
+
+ /**
+ * Will return only labels that were marked previously and the user has unmarked
+ * @return {Array} Label IDs
+ */
+
+ IssuableBulkActions.prototype.getUnmarkedIndeterminedLabels = function() {
+ var el, i, id, j, labelsToKeep, len, len1, ref, ref1, result;
+ result = [];
+ labelsToKeep = [];
+ ref = this.getElement('.labels-filter .is-indeterminate');
+ for (i = 0, len = ref.length; i < len; i++) {
+ el = ref[i];
+ labelsToKeep.push($(el).data('labelId'));
+ }
+ ref1 = this.getLabelsFromSelection();
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
+ id = ref1[j];
+ if (labelsToKeep.indexOf(id) === -1) {
+ result.push(id);
+ }
+ }
+ return result;
+ };
+
+
+ /**
+ * Simple form serialization, it will return just what we need
+ * Returns key/value pairs from form data
+ */
+
+ IssuableBulkActions.prototype.getFormDataAsObject = function() {
+ var formData;
+ formData = {
+ update: {
+ state_event: this.form.find('input[name="update[state_event]"]').val(),
+ assignee_id: this.form.find('input[name="update[assignee_id]"]').val(),
+ milestone_id: this.form.find('input[name="update[milestone_id]"]').val(),
+ issues_ids: this.form.find('input[name="update[issues_ids]"]').val(),
+ subscription_event: this.form.find('input[name="update[subscription_event]"]').val(),
+ add_label_ids: [],
+ remove_label_ids: []
+ }
+ };
+ if (this.willUpdateLabels) {
+ this.getLabelsToApply().map(function(id) {
+ return formData.update.add_label_ids.push(id);
+ });
+ this.getLabelsToRemove().map(function(id) {
+ return formData.update.remove_label_ids.push(id);
+ });
+ }
+ return formData;
+ };
+
+ IssuableBulkActions.prototype.getLabelsToApply = function() {
+ var $labels, labelIds;
+ labelIds = [];
+ $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]');
+ $labels.each(function(k, label) {
+ if (label) {
+ return labelIds.push(parseInt($(label).val()));
+ }
+ });
+ return labelIds;
+ };
+
+
+ /**
+ * Returns Label IDs that will be removed from issue selection
+ * @return {Array} Array of labels IDs
+ */
+
+ IssuableBulkActions.prototype.getLabelsToRemove = function() {
+ var indeterminatedLabels, labelsToApply, result;
+ result = [];
+ indeterminatedLabels = this.getUnmarkedIndeterminedLabels();
+ labelsToApply = this.getLabelsToApply();
+ indeterminatedLabels.map(function(id) {
+ if (labelsToApply.indexOf(id) === -1) {
+ return result.push(id);
+ }
+ });
+ return result;
+ };
+
+ return IssuableBulkActions;
+
+})();
diff --git a/app/assets/javascripts/issues-bulk-assignment.js.coffee b/app/assets/javascripts/issues-bulk-assignment.js.coffee
deleted file mode 100644
index 3d09ea08e3b..00000000000
--- a/app/assets/javascripts/issues-bulk-assignment.js.coffee
+++ /dev/null
@@ -1,128 +0,0 @@
-class @IssuableBulkActions
- constructor: (opts = {}) ->
- # Set defaults
- {
- @container = $('.content')
- @form = @getElement('.bulk-update')
- @issues = @getElement('.issues-list .issue')
- } = opts
-
- # Save instance
- @form.data 'bulkActions', @
-
- @willUpdateLabels = false
-
- @bindEvents()
-
- # Fixes bulk-assign not working when navigating through pages
- Issuable.initChecks();
-
- getElement: (selector) ->
- @container.find selector
-
- bindEvents: ->
- @form.off('submit').on('submit', @onFormSubmit.bind(@))
-
- onFormSubmit: (e) ->
- e.preventDefault()
- @submit()
-
- submit: ->
- _this = @
-
- xhr = $.ajax
- url: @form.attr 'action'
- method: @form.attr 'method'
- dataType: 'JSON',
- data: @getFormDataAsObject()
-
- xhr.done (response, status, xhr) ->
- location.reload()
-
- xhr.fail ->
- new Flash("Issue update failed")
-
- xhr.always @onFormSubmitAlways.bind(@)
-
- onFormSubmitAlways: ->
- @form.find('[type="submit"]').enable()
-
- getSelectedIssues: ->
- @issues.has('.selected_issue:checked')
-
- getLabelsFromSelection: ->
- labels = []
-
- @getSelectedIssues().map ->
- _labels = $(@).data('labels')
- if _labels
- _labels.map (labelId) ->
- labels.push(labelId) if labels.indexOf(labelId) is -1
-
- labels
-
- ###*
- * Will return only labels that were marked previously and the user has unmarked
- * @return {Array} Label IDs
- ###
- getUnmarkedIndeterminedLabels: ->
- result = []
- labelsToKeep = []
-
- for el in @getElement('.labels-filter .is-indeterminate')
- labelsToKeep.push $(el).data('labelId')
-
- for id in @getLabelsFromSelection()
- # Only the ones that we are not going to keep
- result.push(id) if labelsToKeep.indexOf(id) is -1
-
- result
-
- ###*
- * Simple form serialization, it will return just what we need
- * Returns key/value pairs from form data
- ###
- getFormDataAsObject: ->
- formData =
- update:
- state_event : @form.find('input[name="update[state_event]"]').val()
- assignee_id : @form.find('input[name="update[assignee_id]"]').val()
- milestone_id : @form.find('input[name="update[milestone_id]"]').val()
- issues_ids : @form.find('input[name="update[issues_ids]"]').val()
- subscription_event : @form.find('input[name="update[subscription_event]"]').val()
- add_label_ids : []
- remove_label_ids : []
-
- if @willUpdateLabels
- @getLabelsToApply().map (id) ->
- formData.update.add_label_ids.push id
-
- @getLabelsToRemove().map (id) ->
- formData.update.remove_label_ids.push id
-
- formData
-
- getLabelsToApply: ->
- labelIds = []
- $labels = @form.find('.labels-filter input[name="update[label_ids][]"]')
-
- $labels.each (k, label) ->
- labelIds.push parseInt($(label).val()) if label
-
- labelIds
-
- ###*
- * Returns Label IDs that will be removed from issue selection
- * @return {Array} Array of labels IDs
- ###
- getLabelsToRemove: ->
- result = []
- indeterminatedLabels = @getUnmarkedIndeterminedLabels()
- labelsToApply = @getLabelsToApply()
-
- indeterminatedLabels.map (id) ->
- # We need to exclude label IDs that will be applied
- # By not doing this will cause issues from selection to not add labels at all
- result.push(id) if labelsToApply.indexOf(id) is -1
-
- result
diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js
new file mode 100644
index 00000000000..91d3b3b27e8
--- /dev/null
+++ b/app/assets/javascripts/labels.js
@@ -0,0 +1,41 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.Labels = (function() {
+ function Labels() {
+ this.setSuggestedColor = bind(this.setSuggestedColor, this);
+ this.updateColorPreview = bind(this.updateColorPreview, this);
+ var form;
+ form = $('.label-form');
+ this.cleanBinding();
+ this.addBinding();
+ this.updateColorPreview();
+ }
+
+ Labels.prototype.addBinding = function() {
+ $(document).on('click', '.suggest-colors a', this.setSuggestedColor);
+ return $(document).on('input', 'input#label_color', this.updateColorPreview);
+ };
+
+ Labels.prototype.cleanBinding = function() {
+ $(document).off('click', '.suggest-colors a');
+ return $(document).off('input', 'input#label_color');
+ };
+
+ Labels.prototype.updateColorPreview = function() {
+ var previewColor;
+ previewColor = $('input#label_color').val();
+ return $('div.label-color-preview').css('background-color', previewColor);
+ };
+
+ Labels.prototype.setSuggestedColor = function(e) {
+ var color;
+ color = $(e.currentTarget).data('color');
+ $('input#label_color').val(color);
+ this.updateColorPreview();
+ $('.label-form').trigger('keyup');
+ return e.preventDefault();
+ };
+
+ return Labels;
+
+})();
diff --git a/app/assets/javascripts/labels.js.coffee b/app/assets/javascripts/labels.js.coffee
deleted file mode 100644
index d05bacd7494..00000000000
--- a/app/assets/javascripts/labels.js.coffee
+++ /dev/null
@@ -1,28 +0,0 @@
-class @Labels
- constructor: ->
- form = $('.label-form')
- @cleanBinding()
- @addBinding()
- @updateColorPreview()
-
- addBinding: ->
- $(document).on 'click', '.suggest-colors a', @setSuggestedColor
- $(document).on 'input', 'input#label_color', @updateColorPreview
-
- cleanBinding: ->
- $(document).off 'click', '.suggest-colors a'
- $(document).off 'input', 'input#label_color'
-
- # Updates the the preview color with the hex-color input
- updateColorPreview: =>
- previewColor = $('input#label_color').val()
- $('div.label-color-preview').css('background-color', previewColor)
-
- # Updates the preview color with a click on a suggested color
- setSuggestedColor: (e) =>
- color = $(e.currentTarget).data('color')
- $('input#label_color').val(color)
- @updateColorPreview()
- # Notify the form, that color has changed
- $('.label-form').trigger('keyup')
- e.preventDefault()
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
new file mode 100644
index 00000000000..dad615f37ce
--- /dev/null
+++ b/app/assets/javascripts/labels_select.js
@@ -0,0 +1,374 @@
+this.LabelsSelect = (function() {
+ function LabelsSelect() {
+ var _this;
+ _this = this;
+ $('.js-label-select').each(function(i, dropdown) {
+ var $block, $colorPreview, $dropdown, $form, $loading, $newLabelCreateButton, $newLabelError, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, newColorField, newLabelField, projectId, resetForm, saveLabel, saveLabelData, selectedLabel, showAny, showNo;
+ $dropdown = $(dropdown);
+ projectId = $dropdown.data('project-id');
+ labelUrl = $dropdown.data('labels');
+ issueUpdateURL = $dropdown.data('issueUpdate');
+ selectedLabel = $dropdown.data('selected');
+ if ((selectedLabel != null) && !$dropdown.hasClass('js-multiselect')) {
+ selectedLabel = selectedLabel.split(',');
+ }
+ newLabelField = $('#new_label_name');
+ newColorField = $('#new_label_color');
+ showNo = $dropdown.data('show-no');
+ showAny = $dropdown.data('show-any');
+ defaultLabel = $dropdown.data('default-label');
+ abilityName = $dropdown.data('ability-name');
+ $selectbox = $dropdown.closest('.selectbox');
+ $block = $selectbox.closest('.block');
+ $form = $dropdown.closest('form');
+ $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span');
+ $value = $block.find('.value');
+ $newLabelError = $('.js-label-error');
+ $colorPreview = $('.js-dropdown-label-color-preview');
+ $newLabelCreateButton = $('.js-new-label-btn');
+ $newLabelError.hide();
+ $loading = $block.find('.block-loading').fadeOut();
+ if (issueUpdateURL != null) {
+ issueURLSplit = issueUpdateURL.split('/');
+ }
+ if (issueUpdateURL) {
+ labelHTMLTemplate = _.template('<% _.each(labels, function(label){ %> <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>"> <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;"> <%- label.title %> </span> </a> <% }); %>');
+ labelNoneHTMLTemplate = '<span class="no-value">None</span>';
+ }
+ if (newLabelField.length) {
+ $('.suggest-colors-dropdown a').on("click", function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ newColorField.val($(this).data('color')).trigger('change');
+ return $colorPreview.css('background-color', $(this).data('color')).parent().addClass('is-active');
+ });
+ resetForm = function() {
+ newLabelField.val('').trigger('change');
+ newColorField.val('').trigger('change');
+ return $colorPreview.css('background-color', '').parent().removeClass('is-active');
+ };
+ $('.dropdown-menu-back').on('click', function() {
+ return resetForm();
+ });
+ $('.js-cancel-label-btn').on('click', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ resetForm();
+ return $('.dropdown-menu-back', $dropdown.parent()).trigger('click');
+ });
+ enableLabelCreateButton = function() {
+ if (newLabelField.val() !== '' && newColorField.val() !== '') {
+ $newLabelError.hide();
+ return $newLabelCreateButton.enable();
+ } else {
+ return $newLabelCreateButton.disable();
+ }
+ };
+ saveLabel = function() {
+ return Api.newLabel(projectId, {
+ name: newLabelField.val(),
+ color: newColorField.val()
+ }, function(label) {
+ var errors;
+ $newLabelCreateButton.enable();
+ if (label.message != null) {
+ errors = _.map(label.message, function(value, key) {
+ return key + " " + value[0];
+ });
+ return $newLabelError.html(errors.join("<br/>")).show();
+ } else {
+ return $('.dropdown-menu-back', $dropdown.parent()).trigger('click');
+ }
+ });
+ };
+ newLabelField.on('keyup change', enableLabelCreateButton);
+ newColorField.on('keyup change', enableLabelCreateButton);
+ $newLabelCreateButton.disable().on('click', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ return saveLabel();
+ });
+ }
+ saveLabelData = function() {
+ var data, selected;
+ selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").map(function() {
+ return this.value;
+ }).get();
+ data = {};
+ data[abilityName] = {};
+ data[abilityName].label_ids = selected;
+ if (!selected.length) {
+ data[abilityName].label_ids = [''];
+ }
+ $loading.fadeIn();
+ $dropdown.trigger('loading.gl.dropdown');
+ return $.ajax({
+ type: 'PUT',
+ url: issueUpdateURL,
+ dataType: 'JSON',
+ data: data
+ }).done(function(data) {
+ var labelCount, template;
+ $loading.fadeOut();
+ $dropdown.trigger('loaded.gl.dropdown');
+ $selectbox.hide();
+ data.issueURLSplit = issueURLSplit;
+ labelCount = 0;
+ if (data.labels.length) {
+ template = labelHTMLTemplate(data);
+ labelCount = data.labels.length;
+ } else {
+ template = labelNoneHTMLTemplate;
+ }
+ $value.removeAttr('style').html(template);
+ $sidebarCollapsedValue.text(labelCount);
+ $('.has-tooltip', $value).tooltip({
+ container: 'body'
+ });
+ return $value.find('a').each(function(i) {
+ return setTimeout((function(_this) {
+ return function() {
+ return gl.animate.animate($(_this), 'pulse');
+ };
+ })(this), 200 * i);
+ });
+ });
+ };
+ return $dropdown.glDropdown({
+ data: function(term, callback) {
+ return $.ajax({
+ url: labelUrl
+ }).done(function(data) {
+ data = _.chain(data).groupBy(function(label) {
+ return label.title;
+ }).map(function(label) {
+ var color;
+ color = _.map(label, function(dup) {
+ return dup.color;
+ });
+ return {
+ id: label[0].id,
+ title: label[0].title,
+ color: color,
+ duplicate: color.length > 1
+ };
+ }).value();
+ if ($dropdown.hasClass('js-extra-options')) {
+ if (showNo) {
+ data.unshift({
+ id: 0,
+ title: 'No Label'
+ });
+ }
+ if (showAny) {
+ data.unshift({
+ isAny: true,
+ title: 'Any Label'
+ });
+ }
+ if (data.length > 2) {
+ data.splice(2, 0, 'divider');
+ }
+ }
+ return callback(data);
+ });
+ },
+ renderRow: function(label, instance) {
+ var $a, $li, active, color, colorEl, indeterminate, removesAll, selectedClass, spacing;
+ $li = $('<li>');
+ $a = $('<a href="#">');
+ selectedClass = [];
+ removesAll = label.id === 0 || (label.id == null);
+ if ($dropdown.hasClass('js-filter-bulk-update')) {
+ indeterminate = instance.indeterminateIds;
+ active = instance.activeIds;
+ if (indeterminate.indexOf(label.id) !== -1) {
+ selectedClass.push('is-indeterminate');
+ }
+ if (active.indexOf(label.id) !== -1) {
+ i = selectedClass.indexOf('is-indeterminate');
+ if (i !== -1) {
+ selectedClass.splice(i, 1);
+ }
+ selectedClass.push('is-active');
+ instance.addInput(this.fieldName, label.id);
+ }
+ }
+ if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + (this.id(label)) + "']").length) {
+ selectedClass.push('is-active');
+ }
+ if ($dropdown.hasClass('js-multiselect') && removesAll) {
+ selectedClass.push('dropdown-clear-active');
+ }
+ if (label.duplicate) {
+ spacing = 100 / label.color.length;
+ label.color = label.color.filter(function(color, i) {
+ return i < 4;
+ });
+ color = _.map(label.color, function(color, i) {
+ var percentFirst, percentSecond;
+ percentFirst = Math.floor(spacing * i);
+ percentSecond = Math.floor(spacing * (i + 1));
+ return color + " " + percentFirst + "%," + color + " " + percentSecond + "% ";
+ }).join(',');
+ color = "linear-gradient(" + color + ")";
+ } else {
+ if (label.color != null) {
+ color = label.color[0];
+ }
+ }
+ if (color) {
+ colorEl = "<span class='dropdown-label-box' style='background: " + color + "'></span>";
+ } else {
+ colorEl = '';
+ }
+ if (label.id) {
+ selectedClass.push('label-item');
+ $a.attr('data-label-id', label.id);
+ }
+ $a.addClass(selectedClass.join(' ')).html(colorEl + " " + label.title);
+ return $li.html($a).prop('outerHTML');
+ },
+ persistWhenHide: $dropdown.data('persistWhenHide'),
+ search: {
+ fields: ['title']
+ },
+ selectable: true,
+ filterable: true,
+ toggleLabel: function(selected, el) {
+ var selected_labels;
+ selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active');
+ if (selected && (selected.title != null)) {
+ if (selected_labels.length > 1) {
+ return selected.title + " +" + (selected_labels.length - 1) + " more";
+ } else {
+ return selected.title;
+ }
+ } else if (!selected && selected_labels.length !== 0) {
+ if (selected_labels.length > 1) {
+ return ($(selected_labels[0]).text()) + " +" + (selected_labels.length - 1) + " more";
+ } else if (selected_labels.length === 1) {
+ return $(selected_labels).text();
+ }
+ } else {
+ return defaultLabel;
+ }
+ },
+ fieldName: $dropdown.data('field-name'),
+ id: function(label) {
+ if ($dropdown.hasClass("js-filter-submit") && (label.isAny == null)) {
+ return label.title;
+ } else {
+ return label.id;
+ }
+ },
+ hidden: function() {
+ var isIssueIndex, isMRIndex, page, selectedLabels;
+ page = $('body').data('page');
+ isIssueIndex = page === 'projects:issues:index';
+ isMRIndex = page === 'projects:merge_requests:index';
+ $selectbox.hide();
+ $value.removeAttr('style');
+ if ($dropdown.hasClass('js-multiselect')) {
+ if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+ selectedLabels = $dropdown.closest('form').find("input:hidden[name='" + ($dropdown.data('fieldName')) + "']");
+ Issuable.filterResults($dropdown.closest('form'));
+ } else if ($dropdown.hasClass('js-filter-submit')) {
+ $dropdown.closest('form').submit();
+ } else {
+ if (!$dropdown.hasClass('js-filter-bulk-update')) {
+ saveLabelData();
+ }
+ }
+ }
+ if ($dropdown.hasClass('js-filter-bulk-update')) {
+ if (!this.options.persistWhenHide) {
+ return $dropdown.parent().find('.is-active, .is-indeterminate').removeClass();
+ }
+ }
+ },
+ multiSelect: $dropdown.hasClass('js-multiselect'),
+ clicked: function(label) {
+ var isIssueIndex, isMRIndex, page;
+ _this.enableBulkLabelDropdown();
+ if ($dropdown.hasClass('js-filter-bulk-update')) {
+ return;
+ }
+ page = $('body').data('page');
+ isIssueIndex = page === 'projects:issues:index';
+ isMRIndex = page === 'projects:merge_requests:index';
+ if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+ if (!$dropdown.hasClass('js-multiselect')) {
+ selectedLabel = label.title;
+ return Issuable.filterResults($dropdown.closest('form'));
+ }
+ } else if ($dropdown.hasClass('js-filter-submit')) {
+ return $dropdown.closest('form').submit();
+ } else {
+ if ($dropdown.hasClass('js-multiselect')) {
+
+ } else {
+ return saveLabelData();
+ }
+ }
+ },
+ setIndeterminateIds: function() {
+ if (this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
+ return this.indeterminateIds = _this.getIndeterminateIds();
+ }
+ },
+ setActiveIds: function() {
+ if (this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
+ return this.activeIds = _this.getActiveIds();
+ }
+ }
+ });
+ });
+ this.bindEvents();
+ }
+
+ LabelsSelect.prototype.bindEvents = function() {
+ return $('body').on('change', '.selected_issue', this.onSelectCheckboxIssue);
+ };
+
+ LabelsSelect.prototype.onSelectCheckboxIssue = function() {
+ if ($('.selected_issue:checked').length) {
+ return;
+ }
+ $('.issues_bulk_update .labels-filter input[type="hidden"]').remove();
+ return $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label');
+ };
+
+ LabelsSelect.prototype.getIndeterminateIds = function() {
+ var label_ids;
+ label_ids = [];
+ $('.selected_issue:checked').each(function(i, el) {
+ var issue_id;
+ issue_id = $(el).data('id');
+ return label_ids.push($("#issue_" + issue_id).data('labels'));
+ });
+ return _.flatten(label_ids);
+ };
+
+ LabelsSelect.prototype.getActiveIds = function() {
+ var label_ids;
+ label_ids = [];
+ $('.selected_issue:checked').each(function(i, el) {
+ var issue_id;
+ issue_id = $(el).data('id');
+ return label_ids.push($("#issue_" + issue_id).data('labels'));
+ });
+ return _.intersection.apply(_, label_ids);
+ };
+
+ LabelsSelect.prototype.enableBulkLabelDropdown = function() {
+ var issuableBulkActions;
+ if ($('.selected_issue:checked').length) {
+ issuableBulkActions = $('.bulk-update').data('bulkActions');
+ return issuableBulkActions.willUpdateLabels = true;
+ }
+ };
+
+ return LabelsSelect;
+
+})();
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
deleted file mode 100644
index 7688609b301..00000000000
--- a/app/assets/javascripts/labels_select.js.coffee
+++ /dev/null
@@ -1,386 +0,0 @@
-class @LabelsSelect
- constructor: ->
- _this = @
-
- $('.js-label-select').each (i, dropdown) ->
- $dropdown = $(dropdown)
- projectId = $dropdown.data('project-id')
- labelUrl = $dropdown.data('labels')
- issueUpdateURL = $dropdown.data('issueUpdate')
- selectedLabel = $dropdown.data('selected')
- if selectedLabel? and not $dropdown.hasClass 'js-multiselect'
- selectedLabel = selectedLabel.split(',')
- newLabelField = $('#new_label_name')
- newColorField = $('#new_label_color')
- showNo = $dropdown.data('show-no')
- showAny = $dropdown.data('show-any')
- defaultLabel = $dropdown.data('default-label')
- abilityName = $dropdown.data('ability-name')
- $selectbox = $dropdown.closest('.selectbox')
- $block = $selectbox.closest('.block')
- $form = $dropdown.closest('form')
- $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
- $value = $block.find('.value')
- $newLabelError = $('.js-label-error')
- $colorPreview = $('.js-dropdown-label-color-preview')
- $newLabelCreateButton = $('.js-new-label-btn')
-
- $newLabelError.hide()
- $loading = $block.find('.block-loading').fadeOut()
-
- issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
- if issueUpdateURL
- labelHTMLTemplate = _.template(
- '<% _.each(labels, function(label){ %>
- <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>">
- <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;">
- <%- label.title %>
- </span>
- </a>
- <% }); %>'
- )
- labelNoneHTMLTemplate = '<span class="no-value">None</span>'
-
- if newLabelField.length
-
- # Suggested colors in the dropdown to chose from pre-chosen colors
- $('.suggest-colors-dropdown a').on "click", (e) ->
- e.preventDefault()
- e.stopPropagation()
- newColorField
- .val($(this).data('color'))
- .trigger('change')
- $colorPreview
- .css 'background-color', $(this).data('color')
- .parent()
- .addClass 'is-active'
-
- # Cancel button takes back to first page
- resetForm = ->
- newLabelField
- .val ''
- .trigger 'change'
- newColorField
- .val ''
- .trigger 'change'
- $colorPreview
- .css 'background-color', ''
- .parent()
- .removeClass 'is-active'
-
- $('.dropdown-menu-back').on 'click', ->
- resetForm()
-
- $('.js-cancel-label-btn').on 'click', (e) ->
- e.preventDefault()
- e.stopPropagation()
- resetForm()
- $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
-
- # Listen for change and keyup events on label and color field
- # This allows us to enable the button when ready
- enableLabelCreateButton = ->
- if newLabelField.val() isnt '' and newColorField.val() isnt ''
- $newLabelError.hide()
- $newLabelCreateButton.enable()
- else
- $newLabelCreateButton.disable()
-
- saveLabel = ->
- # Create new label with API
- Api.newLabel projectId, {
- name: newLabelField.val()
- color: newColorField.val()
- }, (label) ->
- $newLabelCreateButton.enable()
-
- if label.message?
- errors = _.map label.message, (value, key) ->
- "#{key} #{value[0]}"
-
- $newLabelError
- .html errors.join("<br/>")
- .show()
- else
- $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
-
- newLabelField.on 'keyup change', enableLabelCreateButton
-
- newColorField.on 'keyup change', enableLabelCreateButton
-
- # Send the API call to create the label
- $newLabelCreateButton
- .disable()
- .on 'click', (e) ->
- e.preventDefault()
- e.stopPropagation()
- saveLabel()
-
- saveLabelData = ->
- selected = $dropdown
- .closest('.selectbox')
- .find("input[name='#{$dropdown.data('field-name')}']")
- .map(->
- @value
- ).get()
- data = {}
- data[abilityName] = {}
- data[abilityName].label_ids = selected
- if not selected.length
- data[abilityName].label_ids = ['']
- $loading.fadeIn()
- $dropdown.trigger('loading.gl.dropdown')
- $.ajax(
- type: 'PUT'
- url: issueUpdateURL
- dataType: 'JSON'
- data: data
- ).done (data) ->
- $loading.fadeOut()
- $dropdown.trigger('loaded.gl.dropdown')
- $selectbox.hide()
- data.issueURLSplit = issueURLSplit
- labelCount = 0
- if data.labels.length
- template = labelHTMLTemplate(data)
- labelCount = data.labels.length
- else
- template = labelNoneHTMLTemplate
- $value
- .removeAttr('style')
- .html(template)
- $sidebarCollapsedValue.text(labelCount)
-
- $('.has-tooltip', $value).tooltip(container: 'body')
-
- $value
- .find('a')
- .each((i) ->
- setTimeout(=>
- gl.animate.animate($(@), 'pulse')
- ,200 * i
- )
- )
-
-
- $dropdown.glDropdown(
- data: (term, callback) ->
- $.ajax(
- url: labelUrl
- ).done (data) ->
- data = _.chain data
- .groupBy (label) ->
- label.title
- .map (label) ->
- color = _.map label, (dup) ->
- dup.color
-
- return {
- id: label[0].id
- title: label[0].title
- color: color
- duplicate: color.length > 1
- }
- .value()
-
- if $dropdown.hasClass 'js-extra-options'
- if showNo
- data.unshift(
- id: 0
- title: 'No Label'
- )
-
- if showAny
- data.unshift(
- isAny: true
- title: 'Any Label'
- )
-
- if data.length > 2
- data.splice 2, 0, 'divider'
-
- callback data
-
- renderRow: (label, instance) ->
- $li = $('<li>')
- $a = $('<a href="#">')
-
- selectedClass = []
- removesAll = label.id is 0 or not label.id?
-
- if $dropdown.hasClass('js-filter-bulk-update')
- indeterminate = instance.indeterminateIds
- active = instance.activeIds
-
- if indeterminate.indexOf(label.id) isnt -1
- selectedClass.push 'is-indeterminate'
-
- if active.indexOf(label.id) isnt -1
- # Remove is-indeterminate class if the item will be marked as active
- i = selectedClass.indexOf 'is-indeterminate'
- selectedClass.splice i, 1 unless i is -1
-
- selectedClass.push 'is-active'
-
- # Add input manually
- instance.addInput @fieldName, label.id
-
- if $form.find("input[type='hidden']\
- [name='#{$dropdown.data('fieldName')}']\
- [value='#{this.id(label)}']").length
- selectedClass.push 'is-active'
-
- if $dropdown.hasClass('js-multiselect') and removesAll
- selectedClass.push 'dropdown-clear-active'
-
- if label.duplicate
- spacing = 100 / label.color.length
-
- # Reduce the colors to 4
- label.color = label.color.filter (color, i) ->
- i < 4
-
- color = _.map(label.color, (color, i) ->
- percentFirst = Math.floor(spacing * i)
- percentSecond = Math.floor(spacing * (i + 1))
- "#{color} #{percentFirst}%,#{color} #{percentSecond}% "
- ).join(',')
- color = "linear-gradient(#{color})"
- else
- if label.color?
- color = label.color[0]
-
- if color
- colorEl = "<span class='dropdown-label-box' style='background: #{color}'></span>"
- else
- colorEl = ''
-
- # We need to identify which items are actually labels
- if label.id
- selectedClass.push('label-item')
- $a.attr('data-label-id', label.id)
-
- $a.addClass(selectedClass.join(' '))
- .html("#{colorEl} #{label.title}")
-
- # Return generated html
- $li.html($a).prop('outerHTML')
- persistWhenHide: $dropdown.data('persistWhenHide')
- search:
- fields: ['title']
- selectable: true
- filterable: true
- toggleLabel: (selected, el) ->
- selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active')
-
- if selected and selected.title?
- if selected_labels.length > 1
- "#{selected.title} +#{selected_labels.length - 1} more"
- else
- selected.title
- else if not selected and selected_labels.length isnt 0
- if selected_labels.length > 1
- "#{$(selected_labels[0]).text()} +#{selected_labels.length - 1} more"
- else if selected_labels.length is 1
- $(selected_labels).text()
- else
- defaultLabel
- fieldName: $dropdown.data('field-name')
- id: (label) ->
- if $dropdown.hasClass("js-filter-submit") and not label.isAny?
- label.title
- else
- label.id
-
- hidden: ->
- page = $('body').data 'page'
- isIssueIndex = page is 'projects:issues:index'
- isMRIndex = page is 'projects:merge_requests:index'
-
- $selectbox.hide()
- # display:block overrides the hide-collapse rule
- $value.removeAttr('style')
- if $dropdown.hasClass 'js-multiselect'
- if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
- selectedLabels = $dropdown
- .closest('form')
- .find("input:hidden[name='#{$dropdown.data('fieldName')}']")
- Issuable.filterResults $dropdown.closest('form')
- else if $dropdown.hasClass('js-filter-submit')
- $dropdown.closest('form').submit()
- else
- if not $dropdown.hasClass 'js-filter-bulk-update'
- saveLabelData()
-
- if $dropdown.hasClass('js-filter-bulk-update')
- # If we are persisting state we need the classes
- if not @options.persistWhenHide
- $dropdown.parent().find('.is-active, .is-indeterminate').removeClass()
-
- multiSelect: $dropdown.hasClass 'js-multiselect'
- clicked: (label) ->
- _this.enableBulkLabelDropdown()
-
- if $dropdown.hasClass('js-filter-bulk-update')
- return
-
- page = $('body').data 'page'
- isIssueIndex = page is 'projects:issues:index'
- isMRIndex = page is 'projects:merge_requests:index'
- if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
- if not $dropdown.hasClass 'js-multiselect'
- selectedLabel = label.title
- Issuable.filterResults $dropdown.closest('form')
- else if $dropdown.hasClass 'js-filter-submit'
- $dropdown.closest('form').submit()
- else
- if $dropdown.hasClass 'js-multiselect'
- return
- else
- saveLabelData()
-
- setIndeterminateIds: ->
- if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
- @indeterminateIds = _this.getIndeterminateIds()
-
- setActiveIds: ->
- if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
- @activeIds = _this.getActiveIds()
- )
-
- @bindEvents()
-
- bindEvents: ->
- $('body').on 'change', '.selected_issue', @onSelectCheckboxIssue
-
- onSelectCheckboxIssue: ->
- return if $('.selected_issue:checked').length
-
- # Remove inputs
- $('.issues_bulk_update .labels-filter input[type="hidden"]').remove()
-
- # Also restore button text
- $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label')
-
- getIndeterminateIds: ->
- label_ids = []
-
- $('.selected_issue:checked').each (i, el) ->
- issue_id = $(el).data('id')
- label_ids.push $("#issue_#{issue_id}").data('labels')
-
- _.flatten(label_ids)
-
- getActiveIds: ->
- label_ids = []
-
- $('.selected_issue:checked').each (i, el) ->
- issue_id = $(el).data('id')
- label_ids.push $("#issue_#{issue_id}").data('labels')
-
- _.intersection.apply _, label_ids
-
- enableBulkLabelDropdown: ->
- if $('.selected_issue:checked').length
- issuableBulkActions = $('.bulk-update').data('bulkActions')
- issuableBulkActions.willUpdateLabels = true
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
new file mode 100644
index 00000000000..650b9a8673a
--- /dev/null
+++ b/app/assets/javascripts/layout_nav.js
@@ -0,0 +1,24 @@
+var hideEndFade;
+
+hideEndFade = function($scrollingTabs) {
+ return $scrollingTabs.each(function() {
+ var $this;
+ $this = $(this);
+ return $this.siblings('.fade-right').toggleClass('scrolling', $this.width() < $this.prop('scrollWidth'));
+ });
+};
+
+$(function() {
+ hideEndFade($('.scrolling-tabs'));
+ $(window).off('resize.nav').on('resize.nav', function() {
+ return hideEndFade($('.scrolling-tabs'));
+ });
+ return $('.scrolling-tabs').on('scroll', function(event) {
+ var $this, currentPosition, maxPosition;
+ $this = $(this);
+ currentPosition = $this.scrollLeft();
+ maxPosition = $this.prop('scrollWidth') - $this.outerWidth();
+ $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
+ return $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
+ });
+});
diff --git a/app/assets/javascripts/layout_nav.js.coffee b/app/assets/javascripts/layout_nav.js.coffee
deleted file mode 100644
index f639f7f5892..00000000000
--- a/app/assets/javascripts/layout_nav.js.coffee
+++ /dev/null
@@ -1,24 +0,0 @@
-hideEndFade = ($scrollingTabs) ->
- $scrollingTabs.each ->
- $this = $(@)
-
- $this
- .siblings('.fade-right')
- .toggleClass('scrolling', $this.width() < $this.prop('scrollWidth'))
-
-$ ->
-
- hideEndFade($('.scrolling-tabs'))
-
- $(window)
- .off 'resize.nav'
- .on 'resize.nav', ->
- hideEndFade($('.scrolling-tabs'))
-
- $('.scrolling-tabs').on 'scroll', (event) ->
- $this = $(this)
- currentPosition = $this.scrollLeft()
- maxPosition = $this.prop('scrollWidth') - $this.outerWidth()
-
- $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0)
- $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1)
diff --git a/app/assets/javascripts/lib/chart.js b/app/assets/javascripts/lib/chart.js
new file mode 100644
index 00000000000..f3f76866506
--- /dev/null
+++ b/app/assets/javascripts/lib/chart.js
@@ -0,0 +1,3 @@
+
+/*= require Chart */
+
diff --git a/app/assets/javascripts/lib/chart.js.coffee b/app/assets/javascripts/lib/chart.js.coffee
deleted file mode 100644
index 82217fc5107..00000000000
--- a/app/assets/javascripts/lib/chart.js.coffee
+++ /dev/null
@@ -1 +0,0 @@
-#= require Chart
diff --git a/app/assets/javascripts/lib/cropper.js b/app/assets/javascripts/lib/cropper.js
new file mode 100644
index 00000000000..975b8ec2ac3
--- /dev/null
+++ b/app/assets/javascripts/lib/cropper.js
@@ -0,0 +1,3 @@
+
+/*= require cropper */
+
diff --git a/app/assets/javascripts/lib/cropper.js.coffee b/app/assets/javascripts/lib/cropper.js.coffee
deleted file mode 100644
index 32536d23fe3..00000000000
--- a/app/assets/javascripts/lib/cropper.js.coffee
+++ /dev/null
@@ -1 +0,0 @@
-#= require cropper
diff --git a/app/assets/javascripts/lib/d3.js b/app/assets/javascripts/lib/d3.js
new file mode 100644
index 00000000000..73e8b14a712
--- /dev/null
+++ b/app/assets/javascripts/lib/d3.js
@@ -0,0 +1,3 @@
+
+/*= require d3 */
+
diff --git a/app/assets/javascripts/lib/d3.js.coffee b/app/assets/javascripts/lib/d3.js.coffee
deleted file mode 100644
index 74f0a0bb06a..00000000000
--- a/app/assets/javascripts/lib/d3.js.coffee
+++ /dev/null
@@ -1 +0,0 @@
-#= require d3
diff --git a/app/assets/javascripts/lib/raphael.js b/app/assets/javascripts/lib/raphael.js
new file mode 100644
index 00000000000..57118b30aed
--- /dev/null
+++ b/app/assets/javascripts/lib/raphael.js
@@ -0,0 +1,7 @@
+
+/*= require raphael */
+
+/*= require g.raphael */
+
+/*= require g.bar */
+
diff --git a/app/assets/javascripts/lib/raphael.js.coffee b/app/assets/javascripts/lib/raphael.js.coffee
deleted file mode 100644
index ab8e5979b87..00000000000
--- a/app/assets/javascripts/lib/raphael.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-#= require raphael
-#= require g.raphael
-#= require g.bar
diff --git a/app/assets/javascripts/lib/utils/animate.js b/app/assets/javascripts/lib/utils/animate.js
new file mode 100644
index 00000000000..3ccaf05f7e9
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/animate.js
@@ -0,0 +1,46 @@
+(function(w) {
+ if (w.gl == null) {
+ w.gl = {};
+ }
+ if (gl.animate == null) {
+ gl.animate = {};
+ }
+ gl.animate.animate = function($el, animation, options, done) {
+ if ((options != null ? options.cssStart : void 0) != null) {
+ $el.css(options.cssStart);
+ }
+ $el.removeClass(animation + ' animated').addClass(animation + ' animated').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
+ $(this).removeClass(animation + ' animated');
+ if (done != null) {
+ done();
+ }
+ if ((options != null ? options.cssEnd : void 0) != null) {
+ $el.css(options.cssEnd);
+ }
+ });
+ };
+ gl.animate.animateEach = function($els, animation, time, options, done) {
+ var dfd;
+ dfd = $.Deferred();
+ if (!$els.length) {
+ dfd.resolve();
+ }
+ $els.each(function(i) {
+ setTimeout((function(_this) {
+ return function() {
+ var $this;
+ $this = $(_this);
+ return gl.animate.animate($this, animation, options, function() {
+ if (i === $els.length - 1) {
+ dfd.resolve();
+ if (done != null) {
+ return done();
+ }
+ }
+ });
+ };
+ })(this), time * i);
+ });
+ return dfd.promise();
+ };
+})(window);
diff --git a/app/assets/javascripts/lib/utils/animate.js.coffee b/app/assets/javascripts/lib/utils/animate.js.coffee
deleted file mode 100644
index ec3b44d6126..00000000000
--- a/app/assets/javascripts/lib/utils/animate.js.coffee
+++ /dev/null
@@ -1,39 +0,0 @@
-((w) ->
- if not w.gl? then w.gl = {}
- if not gl.animate? then gl.animate = {}
-
- gl.animate.animate = ($el, animation, options, done) ->
- if options?.cssStart?
- $el.css(options.cssStart)
- $el
- .removeClass(animation + ' animated')
- .addClass(animation + ' animated')
- .one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', ->
- $(this).removeClass(animation + ' animated')
- if done?
- done()
- if options?.cssEnd?
- $el.css(options.cssEnd)
- return
- return
-
- gl.animate.animateEach = ($els, animation, time, options, done) ->
- dfd = $.Deferred()
- if not $els.length
- dfd.resolve()
- $els.each((i) ->
- setTimeout(=>
- $this = $(@)
- gl.animate.animate($this, animation, options, =>
- if i is $els.length - 1
- dfd.resolve()
- if done?
- done()
- )
- ,time * i
- )
- return
- )
- return dfd.promise()
- return
-) window \ No newline at end of file
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
new file mode 100644
index 00000000000..c829bc0b378
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -0,0 +1,57 @@
+(function(w) {
+ var base;
+ w.gl || (w.gl = {});
+ (base = w.gl).utils || (base.utils = {});
+ w.gl.utils.isInGroupsPage = function() {
+ return gl.utils.getPagePath() === 'groups';
+ };
+ w.gl.utils.isInProjectPage = function() {
+ return gl.utils.getPagePath() === 'projects';
+ };
+ w.gl.utils.getProjectSlug = function() {
+ if (this.isInProjectPage()) {
+ return $('body').data('project');
+ } else {
+ return null;
+ }
+ };
+ w.gl.utils.getGroupSlug = function() {
+ if (this.isInGroupsPage()) {
+ return $('body').data('group');
+ } else {
+ return null;
+ }
+ };
+ gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
+ return $tooltipEl.tooltip('destroy').attr('title', newTitle).tooltip('fixTitle');
+ };
+ gl.utils.preventDisabledButtons = function() {
+ return $('.btn').click(function(e) {
+ if ($(this).hasClass('disabled')) {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ return false;
+ }
+ });
+ };
+ gl.utils.getPagePath = function() {
+ return $('body').data('page').split(':')[0];
+ };
+ return jQuery.timefor = function(time, suffix, expiredLabel) {
+ var suffixFromNow, timefor;
+ if (!time) {
+ return '';
+ }
+ suffix || (suffix = 'remaining');
+ expiredLabel || (expiredLabel = 'Past due');
+ jQuery.timeago.settings.allowFuture = true;
+ suffixFromNow = jQuery.timeago.settings.strings.suffixFromNow;
+ jQuery.timeago.settings.strings.suffixFromNow = suffix;
+ timefor = $.timeago(time);
+ if (timefor.indexOf('ago') > -1) {
+ timefor = expiredLabel;
+ }
+ jQuery.timeago.settings.strings.suffixFromNow = suffixFromNow;
+ return timefor;
+ };
+})(window);
diff --git a/app/assets/javascripts/lib/utils/common_utils.js.coffee b/app/assets/javascripts/lib/utils/common_utils.js.coffee
deleted file mode 100644
index d4dd3dc329a..00000000000
--- a/app/assets/javascripts/lib/utils/common_utils.js.coffee
+++ /dev/null
@@ -1,68 +0,0 @@
-((w) ->
-
- w.gl or= {}
- w.gl.utils or= {}
-
- w.gl.utils.isInGroupsPage = ->
-
- return gl.utils.getPagePath() is 'groups'
-
-
- w.gl.utils.isInProjectPage = ->
-
- return gl.utils.getPagePath() is 'projects'
-
-
- w.gl.utils.getProjectSlug = ->
-
- return if @isInProjectPage() then $('body').data 'project' else null
-
-
- w.gl.utils.getGroupSlug = ->
-
- return if @isInGroupsPage() then $('body').data 'group' else null
-
-
-
- gl.utils.updateTooltipTitle = ($tooltipEl, newTitle) ->
-
- $tooltipEl
- .tooltip 'destroy'
- .attr 'title', newTitle
- .tooltip 'fixTitle'
-
-
- gl.utils.preventDisabledButtons = ->
-
- $('.btn').click (e) ->
- if $(this).hasClass 'disabled'
- e.preventDefault()
- e.stopImmediatePropagation()
- return false
-
- gl.utils.getPagePath = ->
- return $('body').data('page').split(':')[0]
-
-
- jQuery.timefor = (time, suffix, expiredLabel) ->
-
- return '' unless time
-
- suffix or= 'remaining'
- expiredLabel or= 'Past due'
-
- jQuery.timeago.settings.allowFuture = yes
-
- { suffixFromNow } = jQuery.timeago.settings.strings
- jQuery.timeago.settings.strings.suffixFromNow = suffix
-
- timefor = $.timeago time
-
- if timefor.indexOf('ago') > -1
- timefor = expiredLabel
-
- jQuery.timeago.settings.strings.suffixFromNow = suffixFromNow
-
- return timefor
-
-) window
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
new file mode 100644
index 00000000000..c5ff617f00d
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -0,0 +1,33 @@
+(function(w) {
+ var base;
+ if (w.gl == null) {
+ w.gl = {};
+ }
+ if ((base = w.gl).utils == null) {
+ base.utils = {};
+ }
+ w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+ w.gl.utils.formatDate = function(datetime) {
+ return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
+ };
+ w.gl.utils.getDayName = function(date) {
+ return this.days[date.getDay()];
+ };
+ return w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago) {
+ if (setTimeago == null) {
+ setTimeago = true;
+ }
+ $timeagoEls.each(function() {
+ var $el;
+ $el = $(this);
+ return $el.attr('title', gl.utils.formatDate($el.attr('datetime')));
+ });
+ if (setTimeago) {
+ $timeagoEls.timeago();
+ $timeagoEls.tooltip('destroy');
+ return $timeagoEls.tooltip({
+ template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+ });
+ }
+ };
+})(window);
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js.coffee b/app/assets/javascripts/lib/utils/datetime_utility.js.coffee
deleted file mode 100644
index 2371e913844..00000000000
--- a/app/assets/javascripts/lib/utils/datetime_utility.js.coffee
+++ /dev/null
@@ -1,28 +0,0 @@
-((w) ->
-
- w.gl ?= {}
- w.gl.utils ?= {}
- w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
-
- w.gl.utils.formatDate = (datetime) ->
- dateFormat(datetime, 'mmm d, yyyy h:MMtt Z')
-
- w.gl.utils.getDayName = (date) ->
- this.days[date.getDay()]
-
- w.gl.utils.localTimeAgo = ($timeagoEls, setTimeago = true) ->
- $timeagoEls.each( ->
- $el = $(@)
- $el.attr('title', gl.utils.formatDate($el.attr('datetime')))
- )
-
- if setTimeago
- $timeagoEls.timeago()
- $timeagoEls.tooltip('destroy')
-
- # Recreate with custom template
- $timeagoEls.tooltip(
- template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
- )
-
-) window
diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js
new file mode 100644
index 00000000000..45b3df4260e
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/notify.js
@@ -0,0 +1,38 @@
+(function(w) {
+ var notificationGranted, notifyMe, notifyPermissions;
+ notificationGranted = function(message, opts, onclick) {
+ var notification;
+ notification = new Notification(message, opts);
+ setTimeout(function() {
+ return notification.close();
+ }, 8000);
+ if (onclick) {
+ return notification.onclick = onclick;
+ }
+ };
+ notifyPermissions = function() {
+ if ('Notification' in window) {
+ return Notification.requestPermission();
+ }
+ };
+ notifyMe = function(message, body, icon, onclick) {
+ var opts;
+ opts = {
+ body: body,
+ icon: icon
+ };
+ if (!('Notification' in window)) {
+
+ } else if (Notification.permission === 'granted') {
+ return notificationGranted(message, opts, onclick);
+ } else if (Notification.permission !== 'denied') {
+ return Notification.requestPermission(function(permission) {
+ if (permission === 'granted') {
+ return notificationGranted(message, opts, onclick);
+ }
+ });
+ }
+ };
+ w.notify = notifyMe;
+ return w.notifyPermissions = notifyPermissions;
+})(window);
diff --git a/app/assets/javascripts/lib/utils/notify.js.coffee b/app/assets/javascripts/lib/utils/notify.js.coffee
deleted file mode 100644
index 9e28353ac34..00000000000
--- a/app/assets/javascripts/lib/utils/notify.js.coffee
+++ /dev/null
@@ -1,35 +0,0 @@
-((w) ->
- notificationGranted = (message, opts, onclick) ->
- notification = new Notification(message, opts)
-
- # Hide the notification after X amount of seconds
- setTimeout ->
- notification.close()
- , 8000
-
- if onclick
- notification.onclick = onclick
-
- notifyPermissions = ->
- if 'Notification' of window
- Notification.requestPermission()
-
- notifyMe = (message, body, icon, onclick) ->
- opts =
- body: body
- icon: icon
- # Let's check if the browser supports notifications
- if !('Notification' of window)
- # do nothing
- else if Notification.permission == 'granted'
- # If it's okay let's create a notification
- notificationGranted message, opts, onclick
- else if Notification.permission != 'denied'
- Notification.requestPermission (permission) ->
- # If the user accepts, let's create a notification
- if permission == 'granted'
- notificationGranted message, opts, onclick
-
- w.notify = notifyMe
- w.notifyPermissions = notifyPermissions
-) window
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
new file mode 100644
index 00000000000..db711121c85
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -0,0 +1,109 @@
+(function(w) {
+ var base;
+ if (w.gl == null) {
+ w.gl = {};
+ }
+ if ((base = w.gl).text == null) {
+ base.text = {};
+ }
+ gl.text.randomString = function() {
+ return Math.random().toString(36).substring(7);
+ };
+ gl.text.replaceRange = function(s, start, end, substitute) {
+ return s.substring(0, start) + substitute + s.substring(end);
+ };
+ gl.text.selectedText = function(text, textarea) {
+ return text.substring(textarea.selectionStart, textarea.selectionEnd);
+ };
+ gl.text.lineBefore = function(text, textarea) {
+ var split;
+ split = text.substring(0, textarea.selectionStart).trim().split('\n');
+ return split[split.length - 1];
+ };
+ gl.text.lineAfter = function(text, textarea) {
+ return text.substring(textarea.selectionEnd).trim().split('\n')[0];
+ };
+ gl.text.blockTagText = function(text, textArea, blockTag, selected) {
+ var lineAfter, lineBefore;
+ lineBefore = this.lineBefore(text, textArea);
+ lineAfter = this.lineAfter(text, textArea);
+ if (lineBefore === blockTag && lineAfter === blockTag) {
+ if (blockTag != null) {
+ textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
+ textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
+ }
+ return selected;
+ } else {
+ return blockTag + "\n" + selected + "\n" + blockTag;
+ }
+ };
+ gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
+ var insertText, inserted, selectedSplit, startChar;
+ selectedSplit = selected.split('\n');
+ startChar = !wrap && textArea.selectionStart > 0 ? '\n' : '';
+ if (selectedSplit.length > 1 && (!wrap || (blockTag != null))) {
+ if (blockTag != null) {
+ insertText = this.blockTagText(text, textArea, blockTag, selected);
+ } else {
+ insertText = selectedSplit.map(function(val) {
+ if (val.indexOf(tag) === 0) {
+ return "" + (val.replace(tag, ''));
+ } else {
+ return "" + tag + val;
+ }
+ }).join('\n');
+ }
+ } else {
+ insertText = "" + startChar + tag + selected + (wrap ? tag : ' ');
+ }
+ if (document.queryCommandSupported('insertText')) {
+ inserted = document.execCommand('insertText', false, insertText);
+ }
+ if (!inserted) {
+ try {
+ document.execCommand("ms-beginUndoUnit");
+ } catch (undefined) {}
+ textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
+ try {
+ document.execCommand("ms-endUndoUnit");
+ } catch (undefined) {}
+ }
+ return this.moveCursor(textArea, tag, wrap);
+ };
+ gl.text.moveCursor = function(textArea, tag, wrapped) {
+ var pos;
+ if (!textArea.setSelectionRange) {
+ return;
+ }
+ if (textArea.selectionStart === textArea.selectionEnd) {
+ if (wrapped) {
+ pos = textArea.selectionStart - tag.length;
+ } else {
+ pos = textArea.selectionStart;
+ }
+ return textArea.setSelectionRange(pos, pos);
+ }
+ };
+ gl.text.updateText = function(textArea, tag, blockTag, wrap) {
+ var $textArea, oldVal, selected, text;
+ $textArea = $(textArea);
+ oldVal = $textArea.val();
+ textArea = $textArea.get(0);
+ text = $textArea.val();
+ selected = this.selectedText(text, textArea);
+ $textArea.focus();
+ return this.insertText(textArea, text, tag, blockTag, selected, wrap);
+ };
+ gl.text.init = function(form) {
+ var self;
+ self = this;
+ return $('.js-md', form).off('click').on('click', function() {
+ var $this;
+ $this = $(this);
+ return self.updateText($this.closest('.md-area').find('textarea'), $this.data('md-tag'), $this.data('md-block'), !$this.data('md-prepend'));
+ });
+ };
+ return gl.text.removeListeners = function(form) {
+ return $('.js-md', form).off();
+ };
+})(window);
diff --git a/app/assets/javascripts/lib/utils/text_utility.js.coffee b/app/assets/javascripts/lib/utils/text_utility.js.coffee
deleted file mode 100644
index 2e1407f8738..00000000000
--- a/app/assets/javascripts/lib/utils/text_utility.js.coffee
+++ /dev/null
@@ -1,105 +0,0 @@
-((w) ->
- w.gl ?= {}
- w.gl.text ?= {}
-
- gl.text.randomString = -> Math.random().toString(36).substring(7)
-
- gl.text.replaceRange = (s, start, end, substitute) ->
- s.substring(0, start) + substitute + s.substring(end);
-
- gl.text.selectedText = (text, textarea) ->
- text.substring(textarea.selectionStart, textarea.selectionEnd)
-
- gl.text.lineBefore = (text, textarea) ->
- split = text.substring(0, textarea.selectionStart).trim().split('\n')
- split[split.length - 1]
-
- gl.text.lineAfter = (text, textarea) ->
- text.substring(textarea.selectionEnd).trim().split('\n')[0]
-
- gl.text.blockTagText = (text, textArea, blockTag, selected) ->
- lineBefore = @lineBefore(text, textArea)
- lineAfter = @lineAfter(text, textArea)
-
- if lineBefore is blockTag and lineAfter is blockTag
- # To remove the block tag we have to select the line before & after
- if blockTag?
- textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1)
- textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1)
-
- selected
- else
- "#{blockTag}\n#{selected}\n#{blockTag}"
-
- gl.text.insertText = (textArea, text, tag, blockTag, selected, wrap) ->
- selectedSplit = selected.split('\n')
- startChar = if not wrap and textArea.selectionStart > 0 then '\n' else ''
-
- if selectedSplit.length > 1 and (not wrap or blockTag?)
- if blockTag?
- insertText = @blockTagText(text, textArea, blockTag, selected)
- else
- insertText = selectedSplit.map((val) ->
- if val.indexOf(tag) is 0
- "#{val.replace(tag, '')}"
- else
- "#{tag}#{val}"
- ).join('\n')
- else
- insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}"
-
- if document.queryCommandSupported('insertText')
- inserted = document.execCommand 'insertText', false, insertText
-
- unless inserted
- try
- document.execCommand("ms-beginUndoUnit")
-
- textArea.value = @replaceRange(
- text,
- textArea.selectionStart,
- textArea.selectionEnd,
- insertText)
- try
- document.execCommand("ms-endUndoUnit")
-
- @moveCursor(textArea, tag, wrap)
-
- gl.text.moveCursor = (textArea, tag, wrapped) ->
- return unless textArea.setSelectionRange
-
- if textArea.selectionStart is textArea.selectionEnd
- if wrapped
- pos = textArea.selectionStart - tag.length
- else
- pos = textArea.selectionStart
-
- textArea.setSelectionRange pos, pos
-
- gl.text.updateText = (textArea, tag, blockTag, wrap) ->
- $textArea = $(textArea)
- oldVal = $textArea.val()
- textArea = $textArea.get(0)
- text = $textArea.val()
- selected = @selectedText(text, textArea)
- $textArea.focus()
-
- @insertText(textArea, text, tag, blockTag, selected, wrap)
-
- gl.text.init = (form) ->
- self = @
- $('.js-md', form)
- .off 'click'
- .on 'click', ->
- $this = $(@)
- self.updateText(
- $this.closest('.md-area').find('textarea'),
- $this.data('md-tag'),
- $this.data('md-block'),
- not $this.data('md-prepend')
- )
-
- gl.text.removeListeners = (form) ->
- $('.js-md', form).off()
-
-) window
diff --git a/app/assets/javascripts/lib/utils/type_utility.js b/app/assets/javascripts/lib/utils/type_utility.js
new file mode 100644
index 00000000000..3a5e5f31ae9
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/type_utility.js
@@ -0,0 +1,12 @@
+(function(w) {
+ var base;
+ if (w.gl == null) {
+ w.gl = {};
+ }
+ if ((base = w.gl).utils == null) {
+ base.utils = {};
+ }
+ return w.gl.utils.isObject = function(obj) {
+ return (obj != null) && (obj.constructor === Object);
+ };
+})(window);
diff --git a/app/assets/javascripts/lib/utils/type_utility.js.coffee b/app/assets/javascripts/lib/utils/type_utility.js.coffee
deleted file mode 100644
index 957f0d86b36..00000000000
--- a/app/assets/javascripts/lib/utils/type_utility.js.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-((w) ->
-
- w.gl ?= {}
- w.gl.utils ?= {}
-
- w.gl.utils.isObject = (obj) ->
- obj? and (obj.constructor is Object)
-
-) window
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
new file mode 100644
index 00000000000..e9ccd820481
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -0,0 +1,61 @@
+(function(w) {
+ var base;
+ if (w.gl == null) {
+ w.gl = {};
+ }
+ if ((base = w.gl).utils == null) {
+ base.utils = {};
+ }
+ w.gl.utils.getParameterValues = function(sParam) {
+ var i, sPageURL, sParameterName, sURLVariables, values;
+ sPageURL = decodeURIComponent(window.location.search.substring(1));
+ sURLVariables = sPageURL.split('&');
+ sParameterName = void 0;
+ values = [];
+ i = 0;
+ while (i < sURLVariables.length) {
+ sParameterName = sURLVariables[i].split('=');
+ if (sParameterName[0] === sParam) {
+ values.push(sParameterName[1]);
+ }
+ i++;
+ }
+ return values;
+ };
+ w.gl.utils.mergeUrlParams = function(params, url) {
+ var lastChar, newUrl, paramName, paramValue, pattern;
+ newUrl = decodeURIComponent(url);
+ for (paramName in params) {
+ paramValue = params[paramName];
+ pattern = new RegExp("\\b(" + paramName + "=).*?(&|$)");
+ if (paramValue == null) {
+ newUrl = newUrl.replace(pattern, '');
+ } else if (url.search(pattern) !== -1) {
+ newUrl = newUrl.replace(pattern, "$1" + paramValue + "$2");
+ } else {
+ newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
+ }
+ }
+ lastChar = newUrl[newUrl.length - 1];
+ if (lastChar === '&') {
+ newUrl = newUrl.slice(0, -1);
+ }
+ return newUrl;
+ };
+ return w.gl.utils.removeParamQueryString = function(url, param) {
+ var urlVariables, variables;
+ url = decodeURIComponent(url);
+ urlVariables = url.split('&');
+ return ((function() {
+ var j, len, results;
+ results = [];
+ for (j = 0, len = urlVariables.length; j < len; j++) {
+ variables = urlVariables[j];
+ if (variables.indexOf(param) === -1) {
+ results.push(variables);
+ }
+ }
+ return results;
+ })()).join('&');
+ };
+})(window);
diff --git a/app/assets/javascripts/lib/utils/url_utility.js.coffee b/app/assets/javascripts/lib/utils/url_utility.js.coffee
deleted file mode 100644
index e8085e1c2e4..00000000000
--- a/app/assets/javascripts/lib/utils/url_utility.js.coffee
+++ /dev/null
@@ -1,52 +0,0 @@
-((w) ->
-
- w.gl ?= {}
- w.gl.utils ?= {}
-
- # Returns an array containing the value(s) of the
- # of the key passed as an argument
- w.gl.utils.getParameterValues = (sParam) ->
- sPageURL = decodeURIComponent(window.location.search.substring(1))
- sURLVariables = sPageURL.split('&')
- sParameterName = undefined
- values = []
- i = 0
- while i < sURLVariables.length
- sParameterName = sURLVariables[i].split('=')
- if sParameterName[0] is sParam
- values.push(sParameterName[1])
- i++
- values
-
- # #
- # @param {Object} params - url keys and value to merge
- # @param {String} url
- # #
- w.gl.utils.mergeUrlParams = (params, url) ->
- newUrl = decodeURIComponent(url)
- for paramName, paramValue of params
- pattern = new RegExp "\\b(#{paramName}=).*?(&|$)"
- if not paramValue?
- newUrl = newUrl.replace pattern, ''
- else if url.search(pattern) isnt -1
- newUrl = newUrl.replace pattern, "$1#{paramValue}$2"
- else
- newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}"
-
- # Remove a trailing ampersand
- lastChar = newUrl[newUrl.length - 1]
-
- if lastChar is '&'
- newUrl = newUrl.slice 0, -1
-
- newUrl
-
- # removes parameter query string from url. returns the modified url
- w.gl.utils.removeParamQueryString = (url, param) ->
- url = decodeURIComponent(url)
- urlVariables = url.split('&')
- (
- variables for variables in urlVariables when variables.indexOf(param) is -1
- ).join('&')
-
-) window
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
new file mode 100644
index 00000000000..2d37ebcb801
--- /dev/null
+++ b/app/assets/javascripts/line_highlighter.js
@@ -0,0 +1,111 @@
+
+/*= require jquery.scrollTo */
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.LineHighlighter = (function() {
+ LineHighlighter.prototype.highlightClass = 'hll';
+
+ LineHighlighter.prototype._hash = '';
+
+ function LineHighlighter(hash) {
+ var range;
+ if (hash == null) {
+ hash = location.hash;
+ }
+ this.setHash = bind(this.setHash, this);
+ this.highlightLine = bind(this.highlightLine, this);
+ this.clickHandler = bind(this.clickHandler, this);
+ this._hash = hash;
+ this.bindEvents();
+ if (hash !== '') {
+ range = this.hashToRange(hash);
+ if (range[0]) {
+ this.highlightRange(range);
+ $.scrollTo("#L" + range[0], {
+ offset: -150
+ });
+ }
+ }
+ }
+
+ LineHighlighter.prototype.bindEvents = function() {
+ $('#blob-content-holder').on('mousedown', 'a[data-line-number]', this.clickHandler);
+ return $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
+ return event.preventDefault();
+ });
+ };
+
+ LineHighlighter.prototype.clickHandler = function(event) {
+ var current, lineNumber, range;
+ event.preventDefault();
+ this.clearHighlight();
+ lineNumber = $(event.target).closest('a').data('line-number');
+ current = this.hashToRange(this._hash);
+ if (!(current[0] && event.shiftKey)) {
+ this.setHash(lineNumber);
+ return this.highlightLine(lineNumber);
+ } else if (event.shiftKey) {
+ if (lineNumber < current[0]) {
+ range = [lineNumber, current[0]];
+ } else {
+ range = [current[0], lineNumber];
+ }
+ this.setHash(range[0], range[1]);
+ return this.highlightRange(range);
+ }
+ };
+
+ LineHighlighter.prototype.clearHighlight = function() {
+ return $("." + this.highlightClass).removeClass(this.highlightClass);
+ };
+
+ LineHighlighter.prototype.hashToRange = function(hash) {
+ var first, last, matches;
+ matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/);
+ if (matches && matches.length) {
+ first = parseInt(matches[1]);
+ last = matches[2] ? parseInt(matches[2]) : null;
+ return [first, last];
+ } else {
+ return [null, null];
+ }
+ };
+
+ LineHighlighter.prototype.highlightLine = function(lineNumber) {
+ return $("#LC" + lineNumber).addClass(this.highlightClass);
+ };
+
+ LineHighlighter.prototype.highlightRange = function(range) {
+ var i, lineNumber, ref, ref1, results;
+ if (range[1]) {
+ results = [];
+ for (lineNumber = i = ref = range[0], ref1 = range[1]; ref <= ref1 ? i <= ref1 : i >= ref1; lineNumber = ref <= ref1 ? ++i : --i) {
+ results.push(this.highlightLine(lineNumber));
+ }
+ return results;
+ } else {
+ return this.highlightLine(range[0]);
+ }
+ };
+
+ LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
+ var hash;
+ if (lastLineNumber) {
+ hash = "#L" + firstLineNumber + "-" + lastLineNumber;
+ } else {
+ hash = "#L" + firstLineNumber;
+ }
+ this._hash = hash;
+ return this.__setLocationHash__(hash);
+ };
+
+ LineHighlighter.prototype.__setLocationHash__ = function(value) {
+ return history.pushState({
+ turbolinks: false,
+ url: value
+ }, document.title, value);
+ };
+
+ return LineHighlighter;
+
+})();
diff --git a/app/assets/javascripts/line_highlighter.js.coffee b/app/assets/javascripts/line_highlighter.js.coffee
deleted file mode 100644
index 2254a3f91ae..00000000000
--- a/app/assets/javascripts/line_highlighter.js.coffee
+++ /dev/null
@@ -1,148 +0,0 @@
-# LineHighlighter
-#
-# Handles single- and multi-line selection and highlight for blob views.
-#
-#= require jquery.scrollTo
-#
-# ### Example Markup
-#
-# <div id="blob-content-holder">
-# <div class="file-content">
-# <div class="line-numbers">
-# <a href="#L1" id="L1" data-line-number="1">1</a>
-# <a href="#L2" id="L2" data-line-number="2">2</a>
-# <a href="#L3" id="L3" data-line-number="3">3</a>
-# <a href="#L4" id="L4" data-line-number="4">4</a>
-# <a href="#L5" id="L5" data-line-number="5">5</a>
-# </div>
-# <pre class="code highlight">
-# <code>
-# <span id="LC1" class="line">...</span>
-# <span id="LC2" class="line">...</span>
-# <span id="LC3" class="line">...</span>
-# <span id="LC4" class="line">...</span>
-# <span id="LC5" class="line">...</span>
-# </code>
-# </pre>
-# </div>
-# </div>
-#
-class @LineHighlighter
- # CSS class applied to highlighted lines
- highlightClass: 'hll'
-
- # Internal copy of location.hash so we're not dependent on `location` in tests
- _hash: ''
-
- # Initialize a LineHighlighter object
- #
- # hash - String URL hash for dependency injection in tests
- constructor: (hash = location.hash) ->
- @_hash = hash
-
- @bindEvents()
-
- unless hash == ''
- range = @hashToRange(hash)
-
- if range[0]
- @highlightRange(range)
-
- # Scroll to the first highlighted line on initial load
- # Offset -50 for the sticky top bar, and another -100 for some context
- $.scrollTo("#L#{range[0]}", offset: -150)
-
- bindEvents: ->
- $('#blob-content-holder').on 'mousedown', 'a[data-line-number]', @clickHandler
-
- # While it may seem odd to bind to the mousedown event and then throw away
- # the click event, there is a method to our madness.
- #
- # If not done this way, the line number anchor will sometimes keep its
- # active state even when the event is cancelled, resulting in an ugly border
- # around the link and/or a persisted underline text decoration.
-
- $('#blob-content-holder').on 'click', 'a[data-line-number]', (event) ->
- event.preventDefault()
-
- clickHandler: (event) =>
- event.preventDefault()
-
- @clearHighlight()
-
- lineNumber = $(event.target).closest('a').data('line-number')
- current = @hashToRange(@_hash)
-
- unless current[0] && event.shiftKey
- # If there's no current selection, or there is but Shift wasn't held,
- # treat this like a single-line selection.
- @setHash(lineNumber)
- @highlightLine(lineNumber)
- else if event.shiftKey
- if lineNumber < current[0]
- range = [lineNumber, current[0]]
- else
- range = [current[0], lineNumber]
-
- @setHash(range[0], range[1])
- @highlightRange(range)
-
- # Unhighlight previously highlighted lines
- clearHighlight: ->
- $(".#{@highlightClass}").removeClass(@highlightClass)
-
- # Convert a URL hash String into line numbers
- #
- # hash - Hash String
- #
- # Examples:
- #
- # hashToRange('#L5') # => [5, null]
- # hashToRange('#L5-15') # => [5, 15]
- # hashToRange('#foo') # => [null, null]
- #
- # Returns an Array
- hashToRange: (hash) ->
- matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/)
-
- if matches && matches.length
- first = parseInt(matches[1])
- last = if matches[2] then parseInt(matches[2]) else null
-
- [first, last]
- else
- [null, null]
-
- # Highlight a single line
- #
- # lineNumber - Line number to highlight
- highlightLine: (lineNumber) =>
- $("#LC#{lineNumber}").addClass(@highlightClass)
-
- # Highlight all lines within a range
- #
- # range - Array containing the starting and ending line numbers
- highlightRange: (range) ->
- if range[1]
- for lineNumber in [range[0]..range[1]]
- @highlightLine(lineNumber)
- else
- @highlightLine(range[0])
-
- # Set the URL hash string
- setHash: (firstLineNumber, lastLineNumber) =>
- if lastLineNumber
- hash = "#L#{firstLineNumber}-#{lastLineNumber}"
- else
- hash = "#L#{firstLineNumber}"
-
- @_hash = hash
- @__setLocationHash__(hash)
-
- # Make the actual hash change in the browser
- #
- # This method is stubbed in tests.
- __setLocationHash__: (value) ->
- # We're using pushState instead of assigning location.hash directly to
- # prevent the page from scrolling on the hashchange event
- history.pushState({turbolinks: false, url: value}, document.title, value)
diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js
new file mode 100644
index 00000000000..6f2d0d3b37c
--- /dev/null
+++ b/app/assets/javascripts/logo.js
@@ -0,0 +1,51 @@
+var clearHighlights, currentTimer, defaultClass, delay, firstPiece, pieceIndex, pieces, start, stop, work;
+
+Turbolinks.enableProgressBar();
+
+defaultClass = 'tanuki-shape';
+
+pieces = ['path#tanuki-right-cheek', 'path#tanuki-right-eye, path#tanuki-right-ear', 'path#tanuki-nose', 'path#tanuki-left-eye, path#tanuki-left-ear', 'path#tanuki-left-cheek'];
+
+pieceIndex = 0;
+
+firstPiece = pieces[0];
+
+currentTimer = null;
+
+delay = 150;
+
+clearHighlights = function() {
+ return $("." + defaultClass + ".highlight").attr('class', defaultClass);
+};
+
+start = function() {
+ clearHighlights();
+ pieceIndex = 0;
+ if (pieces[0] !== firstPiece) {
+ pieces.reverse();
+ }
+ if (currentTimer) {
+ clearInterval(currentTimer);
+ }
+ return currentTimer = setInterval(work, delay);
+};
+
+stop = function() {
+ clearInterval(currentTimer);
+ return clearHighlights();
+};
+
+work = function() {
+ clearHighlights();
+ $(pieces[pieceIndex]).attr('class', defaultClass + " highlight");
+ if (pieceIndex === pieces.length - 1) {
+ pieceIndex = 0;
+ return pieces.reverse();
+ } else {
+ return pieceIndex++;
+ }
+};
+
+$(document).on('page:fetch', start);
+
+$(document).on('page:change', stop);
diff --git a/app/assets/javascripts/logo.js.coffee b/app/assets/javascripts/logo.js.coffee
deleted file mode 100644
index dc2590a0355..00000000000
--- a/app/assets/javascripts/logo.js.coffee
+++ /dev/null
@@ -1,44 +0,0 @@
-Turbolinks.enableProgressBar();
-
-defaultClass = 'tanuki-shape'
-pieces = [
- 'path#tanuki-right-cheek',
- 'path#tanuki-right-eye, path#tanuki-right-ear',
- 'path#tanuki-nose',
- 'path#tanuki-left-eye, path#tanuki-left-ear',
- 'path#tanuki-left-cheek',
-]
-pieceIndex = 0
-firstPiece = pieces[0]
-
-currentTimer = null
-delay = 150
-
-clearHighlights = ->
- $(".#{defaultClass}.highlight").attr('class', defaultClass)
-
-start = ->
- clearHighlights()
- pieceIndex = 0
- pieces.reverse() unless pieces[0] == firstPiece
- clearInterval(currentTimer) if currentTimer
- currentTimer = setInterval(work, delay)
-
-stop = ->
- clearInterval(currentTimer)
- clearHighlights()
-
-work = ->
- clearHighlights()
- $(pieces[pieceIndex]).attr('class', "#{defaultClass} highlight")
-
- # If we hit the last piece, reset the index and then reverse the array to
- # get a nice back-and-forth sweeping look
- if pieceIndex == pieces.length - 1
- pieceIndex = 0
- pieces.reverse()
- else
- pieceIndex++
-
-$(document).on('page:fetch', start)
-$(document).on('page:change', stop)
diff --git a/app/assets/javascripts/markdown_preview.js b/app/assets/javascripts/markdown_preview.js
new file mode 100644
index 00000000000..099f979a469
--- /dev/null
+++ b/app/assets/javascripts/markdown_preview.js
@@ -0,0 +1,147 @@
+var lastTextareaPreviewed, markdownPreview, previewButtonSelector, writeButtonSelector;
+
+this.MarkdownPreview = (function() {
+ function MarkdownPreview() {}
+
+ MarkdownPreview.prototype.referenceThreshold = 10;
+
+ MarkdownPreview.prototype.ajaxCache = {};
+
+ MarkdownPreview.prototype.showPreview = function(form) {
+ var mdText, preview;
+ preview = form.find('.js-md-preview');
+ mdText = form.find('textarea.markdown-area').val();
+ if (mdText.trim().length === 0) {
+ preview.text('Nothing to preview.');
+ return this.hideReferencedUsers(form);
+ } else {
+ preview.text('Loading...');
+ return this.renderMarkdown(mdText, (function(_this) {
+ return function(response) {
+ preview.html(response.body);
+ preview.syntaxHighlight();
+ return _this.renderReferencedUsers(response.references.users, form);
+ };
+ })(this));
+ }
+ };
+
+ MarkdownPreview.prototype.renderMarkdown = function(text, success) {
+ if (!window.markdown_preview_path) {
+ return;
+ }
+ if (text === this.ajaxCache.text) {
+ return success(this.ajaxCache.response);
+ }
+ return $.ajax({
+ type: 'POST',
+ url: window.markdown_preview_path,
+ data: {
+ text: text
+ },
+ dataType: 'json',
+ success: (function(_this) {
+ return function(response) {
+ _this.ajaxCache = {
+ text: text,
+ response: response
+ };
+ return success(response);
+ };
+ })(this)
+ });
+ };
+
+ MarkdownPreview.prototype.hideReferencedUsers = function(form) {
+ var referencedUsers;
+ referencedUsers = form.find('.referenced-users');
+ return referencedUsers.hide();
+ };
+
+ MarkdownPreview.prototype.renderReferencedUsers = function(users, form) {
+ var referencedUsers;
+ referencedUsers = form.find('.referenced-users');
+ if (referencedUsers.length) {
+ if (users.length >= this.referenceThreshold) {
+ referencedUsers.show();
+ return referencedUsers.find('.js-referenced-users-count').text(users.length);
+ } else {
+ return referencedUsers.hide();
+ }
+ }
+ };
+
+ return MarkdownPreview;
+
+})();
+
+markdownPreview = new MarkdownPreview();
+
+previewButtonSelector = '.js-md-preview-button';
+
+writeButtonSelector = '.js-md-write-button';
+
+lastTextareaPreviewed = null;
+
+$.fn.setupMarkdownPreview = function() {
+ var $form, form_textarea;
+ $form = $(this);
+ form_textarea = $form.find('textarea.markdown-area');
+ form_textarea.on('input', function() {
+ return markdownPreview.hideReferencedUsers($form);
+ });
+ return form_textarea.on('blur', function() {
+ return markdownPreview.showPreview($form);
+ });
+};
+
+$(document).on('markdown-preview:show', function(e, $form) {
+ if (!$form) {
+ return;
+ }
+ lastTextareaPreviewed = $form.find('textarea.markdown-area');
+ $form.find(writeButtonSelector).parent().removeClass('active');
+ $form.find(previewButtonSelector).parent().addClass('active');
+ $form.find('.md-write-holder').hide();
+ $form.find('.md-preview-holder').show();
+ return markdownPreview.showPreview($form);
+});
+
+$(document).on('markdown-preview:hide', function(e, $form) {
+ if (!$form) {
+ return;
+ }
+ lastTextareaPreviewed = null;
+ $form.find(writeButtonSelector).parent().addClass('active');
+ $form.find(previewButtonSelector).parent().removeClass('active');
+ $form.find('.md-write-holder').show();
+ $form.find('textarea.markdown-area').focus();
+ return $form.find('.md-preview-holder').hide();
+});
+
+$(document).on('markdown-preview:toggle', function(e, keyboardEvent) {
+ var $target;
+ $target = $(keyboardEvent.target);
+ if ($target.is('textarea.markdown-area')) {
+ $(document).triggerHandler('markdown-preview:show', [$target.closest('form')]);
+ return keyboardEvent.preventDefault();
+ } else if (lastTextareaPreviewed) {
+ $target = lastTextareaPreviewed;
+ $(document).triggerHandler('markdown-preview:hide', [$target.closest('form')]);
+ return keyboardEvent.preventDefault();
+ }
+});
+
+$(document).on('click', previewButtonSelector, function(e) {
+ var $form;
+ e.preventDefault();
+ $form = $(this).closest('form');
+ return $(document).triggerHandler('markdown-preview:show', [$form]);
+});
+
+$(document).on('click', writeButtonSelector, function(e) {
+ var $form;
+ e.preventDefault();
+ $form = $(this).closest('form');
+ return $(document).triggerHandler('markdown-preview:hide', [$form]);
+});
diff --git a/app/assets/javascripts/markdown_preview.js.coffee b/app/assets/javascripts/markdown_preview.js.coffee
deleted file mode 100644
index 2a0b9479445..00000000000
--- a/app/assets/javascripts/markdown_preview.js.coffee
+++ /dev/null
@@ -1,119 +0,0 @@
-# MarkdownPreview
-#
-# Handles toggling the "Write" and "Preview" tab clicks, rendering the preview,
-# and showing a warning when more than `x` users are referenced.
-#
-class @MarkdownPreview
- # Minimum number of users referenced before triggering a warning
- referenceThreshold: 10
- ajaxCache: {}
-
- showPreview: (form) ->
- preview = form.find('.js-md-preview')
- mdText = form.find('textarea.markdown-area').val()
-
- if mdText.trim().length == 0
- preview.text('Nothing to preview.')
- @hideReferencedUsers(form)
- else
- preview.text('Loading...')
- @renderMarkdown mdText, (response) =>
- preview.html(response.body)
- preview.syntaxHighlight()
- @renderReferencedUsers(response.references.users, form)
-
- renderMarkdown: (text, success) ->
- return unless window.markdown_preview_path
-
- return success(@ajaxCache.response) if text == @ajaxCache.text
-
- $.ajax
- type: 'POST'
- url: window.markdown_preview_path
- data: { text: text }
- dataType: 'json'
- success: (response) =>
- @ajaxCache = text: text, response: response
- success(response)
-
- hideReferencedUsers: (form) ->
- referencedUsers = form.find('.referenced-users')
- referencedUsers.hide()
-
- renderReferencedUsers: (users, form) ->
- referencedUsers = form.find('.referenced-users')
-
- if referencedUsers.length
- if users.length >= @referenceThreshold
- referencedUsers.show()
- referencedUsers.find('.js-referenced-users-count').text(users.length)
- else
- referencedUsers.hide()
-
-markdownPreview = new MarkdownPreview()
-
-previewButtonSelector = '.js-md-preview-button'
-writeButtonSelector = '.js-md-write-button'
-lastTextareaPreviewed = null
-
-$.fn.setupMarkdownPreview = ->
- $form = $(this)
-
- form_textarea = $form.find('textarea.markdown-area')
-
- form_textarea.on 'input', -> markdownPreview.hideReferencedUsers($form)
- form_textarea.on 'blur', -> markdownPreview.showPreview($form)
-
-$(document).on 'markdown-preview:show', (e, $form) ->
- return unless $form
-
- lastTextareaPreviewed = $form.find('textarea.markdown-area')
-
- # toggle tabs
- $form.find(writeButtonSelector).parent().removeClass('active')
- $form.find(previewButtonSelector).parent().addClass('active')
-
- # toggle content
- $form.find('.md-write-holder').hide()
- $form.find('.md-preview-holder').show()
-
- markdownPreview.showPreview($form)
-
-$(document).on 'markdown-preview:hide', (e, $form) ->
- return unless $form
-
- lastTextareaPreviewed = null
-
- # toggle tabs
- $form.find(writeButtonSelector).parent().addClass('active')
- $form.find(previewButtonSelector).parent().removeClass('active')
-
- # toggle content
- $form.find('.md-write-holder').show()
- $form.find('textarea.markdown-area').focus()
- $form.find('.md-preview-holder').hide()
-
-$(document).on 'markdown-preview:toggle', (e, keyboardEvent) ->
- $target = $(keyboardEvent.target)
-
- if $target.is('textarea.markdown-area')
- $(document).triggerHandler('markdown-preview:show', [$target.closest('form')])
- keyboardEvent.preventDefault()
- else if lastTextareaPreviewed
- $target = lastTextareaPreviewed
- $(document).triggerHandler('markdown-preview:hide', [$target.closest('form')])
- keyboardEvent.preventDefault()
-
-$(document).on 'click', previewButtonSelector, (e) ->
- e.preventDefault()
-
- $form = $(this).closest('form')
-
- $(document).triggerHandler('markdown-preview:show', [$form])
-
-$(document).on 'click', writeButtonSelector, (e) ->
- e.preventDefault()
-
- $form = $(this).closest('form')
-
- $(document).triggerHandler('markdown-preview:hide', [$form])
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
new file mode 100644
index 00000000000..d320ce70970
--- /dev/null
+++ b/app/assets/javascripts/merge_request.js
@@ -0,0 +1,99 @@
+
+/*= require jquery.waitforimages */
+
+/*= require task_list */
+
+/*= require merge_request_tabs */
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.MergeRequest = (function() {
+ function MergeRequest(opts) {
+ this.opts = opts != null ? opts : {};
+ this.submitNoteForm = bind(this.submitNoteForm, this);
+ this.$el = $('.merge-request');
+ this.$('.show-all-commits').on('click', (function(_this) {
+ return function() {
+ return _this.showAllCommits();
+ };
+ })(this));
+ this.initTabs();
+ this.disableTaskList();
+ this.initMRBtnListeners();
+ if ($("a.btn-close").length) {
+ this.initTaskList();
+ }
+ }
+
+ MergeRequest.prototype.$ = function(selector) {
+ return this.$el.find(selector);
+ };
+
+ MergeRequest.prototype.initTabs = function() {
+ if (this.opts.action !== 'new') {
+ return new MergeRequestTabs(this.opts);
+ } else {
+ return $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show');
+ }
+ };
+
+ MergeRequest.prototype.showAllCommits = function() {
+ this.$('.first-commits').remove();
+ return this.$('.all-commits').removeClass('hide');
+ };
+
+ MergeRequest.prototype.initTaskList = function() {
+ $('.detail-page-description .js-task-list-container').taskList('enable');
+ return $(document).on('tasklist:changed', '.detail-page-description .js-task-list-container', this.updateTaskList);
+ };
+
+ MergeRequest.prototype.initMRBtnListeners = function() {
+ var _this;
+ _this = this;
+ return $('a.btn-close, a.btn-reopen').on('click', function(e) {
+ var $this, shouldSubmit;
+ $this = $(this);
+ shouldSubmit = $this.hasClass('btn-comment');
+ if (shouldSubmit && $this.data('submitted')) {
+ return;
+ }
+ if (shouldSubmit) {
+ if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ return _this.submitNoteForm($this.closest('form'), $this);
+ }
+ }
+ });
+ };
+
+ MergeRequest.prototype.submitNoteForm = function(form, $button) {
+ var noteText;
+ noteText = form.find("textarea.js-note-text").val();
+ if (noteText.trim().length > 0) {
+ form.submit();
+ $button.data('submitted', true);
+ return $button.trigger('click');
+ }
+ };
+
+ MergeRequest.prototype.disableTaskList = function() {
+ $('.detail-page-description .js-task-list-container').taskList('disable');
+ return $(document).off('tasklist:changed', '.detail-page-description .js-task-list-container');
+ };
+
+ MergeRequest.prototype.updateTaskList = function() {
+ var patchData;
+ patchData = {};
+ patchData['merge_request'] = {
+ 'description': $('.js-task-list-field', this).val()
+ };
+ return $.ajax({
+ type: 'PATCH',
+ url: $('form.js-issuable-update').attr('action'),
+ data: patchData
+ });
+ };
+
+ return MergeRequest;
+
+})();
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
deleted file mode 100644
index dabfd91cf14..00000000000
--- a/app/assets/javascripts/merge_request.js.coffee
+++ /dev/null
@@ -1,82 +0,0 @@
-#= require jquery.waitforimages
-#= require task_list
-
-#= require merge_request_tabs
-
-class @MergeRequest
- # Initialize MergeRequest behavior
- #
- # Options:
- # action - String, current controller action
- #
- constructor: (@opts = {}) ->
- this.$el = $('.merge-request')
-
- this.$('.show-all-commits').on 'click', =>
- this.showAllCommits()
-
- @initTabs()
-
- # Prevent duplicate event bindings
- @disableTaskList()
- @initMRBtnListeners()
-
- if $("a.btn-close").length
- @initTaskList()
-
- # Local jQuery finder
- $: (selector) ->
- this.$el.find(selector)
-
- initTabs: ->
- if @opts.action != 'new'
- # `MergeRequests#new` has no tab-persisting or lazy-loading behavior
- new MergeRequestTabs(@opts)
- else
- # Show the first tab (Commits)
- $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show')
-
- showAllCommits: ->
- this.$('.first-commits').remove()
- this.$('.all-commits').removeClass 'hide'
-
- initTaskList: ->
- $('.detail-page-description .js-task-list-container').taskList('enable')
- $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
-
- initMRBtnListeners: ->
- _this = @
- $('a.btn-close, a.btn-reopen').on 'click', (e) ->
- $this = $(this)
- shouldSubmit = $this.hasClass('btn-comment')
- if shouldSubmit && $this.data('submitted')
- return
- if shouldSubmit
- if $this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')
- e.preventDefault()
- e.stopImmediatePropagation()
- _this.submitNoteForm($this.closest('form'),$this)
-
-
- submitNoteForm: (form, $button) =>
- noteText = form.find("textarea.js-note-text").val()
- if noteText.trim().length > 0
- form.submit()
- $button.data('submitted',true)
- $button.trigger('click')
-
-
- disableTaskList: ->
- $('.detail-page-description .js-task-list-container').taskList('disable')
- $(document).off 'tasklist:changed', '.detail-page-description .js-task-list-container'
-
- # TODO (rspeicher): Make the merge request description inline-editable like a
- # note so that we can re-use its form here
- updateTaskList: ->
- patchData = {}
- patchData['merge_request'] = {'description': $('.js-task-list-field', this).val()}
-
- $.ajax
- type: 'PATCH'
- url: $('form.js-issuable-update').attr('action')
- data: patchData
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
new file mode 100644
index 00000000000..29ea5c90e35
--- /dev/null
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -0,0 +1,235 @@
+
+/*= require jquery.cookie */
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.MergeRequestTabs = (function() {
+ MergeRequestTabs.prototype.diffsLoaded = false;
+
+ MergeRequestTabs.prototype.buildsLoaded = false;
+
+ MergeRequestTabs.prototype.commitsLoaded = false;
+
+ function MergeRequestTabs(opts) {
+ this.opts = opts != null ? opts : {};
+ this.setCurrentAction = bind(this.setCurrentAction, this);
+ this.tabShown = bind(this.tabShown, this);
+ this.showTab = bind(this.showTab, this);
+ this._location = location;
+ this.bindEvents();
+ this.activateTab(this.opts.action);
+ }
+
+ MergeRequestTabs.prototype.bindEvents = function() {
+ $(document).on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown);
+ return $(document).on('click', '.js-show-tab', this.showTab);
+ };
+
+ MergeRequestTabs.prototype.showTab = function(event) {
+ event.preventDefault();
+ return this.activateTab($(event.target).data('action'));
+ };
+
+ MergeRequestTabs.prototype.tabShown = function(event) {
+ var $target, action, navBarHeight;
+ $target = $(event.target);
+ action = $target.data('action');
+ if (action === 'commits') {
+ this.loadCommits($target.attr('href'));
+ this.expandView();
+ } else if (action === 'diffs') {
+ this.loadDiff($target.attr('href'));
+ if ((typeof bp !== "undefined" && bp !== null) && bp.getBreakpointSize() !== 'lg') {
+ this.shrinkView();
+ }
+ navBarHeight = $('.navbar-gitlab').outerHeight();
+ $.scrollTo(".merge-request-details .merge-request-tabs", {
+ offset: -navBarHeight
+ });
+ } else if (action === 'builds') {
+ this.loadBuilds($target.attr('href'));
+ this.expandView();
+ } else {
+ this.expandView();
+ }
+ return this.setCurrentAction(action);
+ };
+
+ MergeRequestTabs.prototype.scrollToElement = function(container) {
+ var $el, navBarHeight;
+ if (window.location.hash) {
+ navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight();
+ $el = $(container + " " + window.location.hash + ":not(.match)");
+ if ($el.length) {
+ return $.scrollTo(container + " " + window.location.hash + ":not(.match)", {
+ offset: -navBarHeight
+ });
+ }
+ }
+ };
+
+ MergeRequestTabs.prototype.activateTab = function(action) {
+ if (action === 'show') {
+ action = 'notes';
+ }
+ return $(".merge-request-tabs a[data-action='" + action + "']").tab('show');
+ };
+
+ MergeRequestTabs.prototype.setCurrentAction = function(action) {
+ var new_state;
+ if (action === 'show') {
+ action = 'notes';
+ }
+ new_state = this._location.pathname.replace(/\/(commits|diffs|builds)(\.html)?\/?$/, '');
+ if (action !== 'notes') {
+ new_state += "/" + action;
+ }
+ new_state += this._location.search + this._location.hash;
+ history.replaceState({
+ turbolinks: true,
+ url: new_state
+ }, document.title, new_state);
+ return new_state;
+ };
+
+ MergeRequestTabs.prototype.loadCommits = function(source) {
+ if (this.commitsLoaded) {
+ return;
+ }
+ return this._get({
+ url: source + ".json",
+ success: (function(_this) {
+ return function(data) {
+ document.querySelector("div#commits").innerHTML = data.html;
+ gl.utils.localTimeAgo($('.js-timeago', 'div#commits'));
+ _this.commitsLoaded = true;
+ return _this.scrollToElement("#commits");
+ };
+ })(this)
+ });
+ };
+
+ MergeRequestTabs.prototype.loadDiff = function(source) {
+ if (this.diffsLoaded) {
+ return;
+ }
+ return this._get({
+ url: (source + ".json") + this._location.search,
+ success: (function(_this) {
+ return function(data) {
+ $('#diffs').html(data.html);
+ gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
+ $('#diffs .js-syntax-highlight').syntaxHighlight();
+ $('#diffs .diff-file').singleFileDiff();
+ if (_this.diffViewType() === 'parallel') {
+ _this.expandViewContainer();
+ }
+ _this.diffsLoaded = true;
+ _this.scrollToElement("#diffs");
+ _this.highlighSelectedLine();
+ _this.filesCommentButton = $('.files .diff-file').filesCommentButton();
+ return $(document).off('click', '.diff-line-num a').on('click', '.diff-line-num a', function(e) {
+ e.preventDefault();
+ window.location.hash = $(e.currentTarget).attr('href');
+ _this.highlighSelectedLine();
+ return _this.scrollToElement("#diffs");
+ });
+ };
+ })(this)
+ });
+ };
+
+ MergeRequestTabs.prototype.highlighSelectedLine = function() {
+ var $diffLine, diffLineTop, hashClassString, locationHash, navBarHeight;
+ $('.hll').removeClass('hll');
+ locationHash = window.location.hash;
+ if (locationHash !== '') {
+ hashClassString = "." + (locationHash.replace('#', ''));
+ $diffLine = $(locationHash + ":not(.match)", $('#diffs'));
+ if (!$diffLine.is('tr')) {
+ $diffLine = $('#diffs').find("td" + locationHash + ", td" + hashClassString);
+ } else {
+ $diffLine = $diffLine.find('td');
+ }
+ if ($diffLine.length) {
+ $diffLine.addClass('hll');
+ diffLineTop = $diffLine.offset().top;
+ return navBarHeight = $('.navbar-gitlab').outerHeight();
+ }
+ }
+ };
+
+ MergeRequestTabs.prototype.loadBuilds = function(source) {
+ if (this.buildsLoaded) {
+ return;
+ }
+ return this._get({
+ url: source + ".json",
+ success: (function(_this) {
+ return function(data) {
+ document.querySelector("div#builds").innerHTML = data.html;
+ gl.utils.localTimeAgo($('.js-timeago', 'div#builds'));
+ _this.buildsLoaded = true;
+ return _this.scrollToElement("#builds");
+ };
+ })(this)
+ });
+ };
+
+ MergeRequestTabs.prototype.toggleLoading = function(status) {
+ return $('.mr-loading-status .loading').toggle(status);
+ };
+
+ MergeRequestTabs.prototype._get = function(options) {
+ var defaults;
+ defaults = {
+ beforeSend: (function(_this) {
+ return function() {
+ return _this.toggleLoading(true);
+ };
+ })(this),
+ complete: (function(_this) {
+ return function() {
+ return _this.toggleLoading(false);
+ };
+ })(this),
+ dataType: 'json',
+ type: 'GET'
+ };
+ options = $.extend({}, defaults, options);
+ return $.ajax(options);
+ };
+
+ MergeRequestTabs.prototype.diffViewType = function() {
+ return $('.inline-parallel-buttons a.active').data('view-type');
+ };
+
+ MergeRequestTabs.prototype.expandViewContainer = function() {
+ return $('.container-fluid').removeClass('container-limited');
+ };
+
+ MergeRequestTabs.prototype.shrinkView = function() {
+ var $gutterIcon;
+ $gutterIcon = $('.js-sidebar-toggle i:visible');
+ return setTimeout(function() {
+ if ($gutterIcon.is('.fa-angle-double-right')) {
+ return $gutterIcon.closest('a').trigger('click', [true]);
+ }
+ }, 0);
+ };
+
+ MergeRequestTabs.prototype.expandView = function() {
+ var $gutterIcon;
+ if ($.cookie('collapsed_gutter') === 'true') {
+ return;
+ }
+ $gutterIcon = $('.js-sidebar-toggle i:visible');
+ return setTimeout(function() {
+ if ($gutterIcon.is('.fa-angle-double-left')) {
+ return $gutterIcon.closest('a').trigger('click', [true]);
+ }
+ }, 0);
+ };
+
+ return MergeRequestTabs;
+
+})();
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
deleted file mode 100644
index 86539e0d725..00000000000
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ /dev/null
@@ -1,252 +0,0 @@
-# MergeRequestTabs
-#
-# Handles persisting and restoring the current tab selection and lazily-loading
-# content on the MergeRequests#show page.
-#
-#= require jquery.cookie
-#
-# ### Example Markup
-#
-# <ul class="nav-links merge-request-tabs">
-# <li class="notes-tab active">
-# <a data-action="notes" data-target="#notes" data-toggle="tab" href="/foo/bar/merge_requests/1">
-# Discussion
-# </a>
-# </li>
-# <li class="commits-tab">
-# <a data-action="commits" data-target="#commits" data-toggle="tab" href="/foo/bar/merge_requests/1/commits">
-# Commits
-# </a>
-# </li>
-# <li class="diffs-tab">
-# <a data-action="diffs" data-target="#diffs" data-toggle="tab" href="/foo/bar/merge_requests/1/diffs">
-# Diffs
-# </a>
-# </li>
-# </ul>
-#
-# <div class="tab-content">
-# <div class="notes tab-pane active" id="notes">
-# Notes Content
-# </div>
-# <div class="commits tab-pane" id="commits">
-# Commits Content
-# </div>
-# <div class="diffs tab-pane" id="diffs">
-# Diffs Content
-# </div>
-# </div>
-#
-# <div class="mr-loading-status">
-# <div class="loading">
-# Loading Animation
-# </div>
-# </div>
-#
-class @MergeRequestTabs
- diffsLoaded: false
- buildsLoaded: false
- commitsLoaded: false
-
- constructor: (@opts = {}) ->
- # Store the `location` object, allowing for easier stubbing in tests
- @_location = location
-
- @bindEvents()
- @activateTab(@opts.action)
-
- bindEvents: ->
- $(document).on 'shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', @tabShown
- $(document).on 'click', '.js-show-tab', @showTab
-
- showTab: (event) =>
- event.preventDefault()
-
- @activateTab $(event.target).data('action')
-
- tabShown: (event) =>
- $target = $(event.target)
- action = $target.data('action')
-
- if action == 'commits'
- @loadCommits($target.attr('href'))
- @expandView()
- else if action == 'diffs'
- @loadDiff($target.attr('href'))
- if bp? and bp.getBreakpointSize() isnt 'lg'
- @shrinkView()
-
- navBarHeight = $('.navbar-gitlab').outerHeight()
- $.scrollTo(".merge-request-details .merge-request-tabs", offset: -navBarHeight)
- else if action == 'builds'
- @loadBuilds($target.attr('href'))
- @expandView()
- else
- @expandView()
-
- @setCurrentAction(action)
-
- scrollToElement: (container) ->
- if window.location.hash
- navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()
-
- $el = $("#{container} #{window.location.hash}:not(.match)")
- $.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length
-
- # Activate a tab based on the current action
- activateTab: (action) ->
- action = 'notes' if action == 'show'
- $(".merge-request-tabs a[data-action='#{action}']").tab('show')
-
- # Replaces the current Merge Request-specific action in the URL with a new one
- #
- # If the action is "notes", the URL is reset to the standard
- # `MergeRequests#show` route.
- #
- # Examples:
- #
- # location.pathname # => "/namespace/project/merge_requests/1"
- # setCurrentAction('diffs')
- # location.pathname # => "/namespace/project/merge_requests/1/diffs"
- #
- # location.pathname # => "/namespace/project/merge_requests/1/diffs"
- # setCurrentAction('notes')
- # location.pathname # => "/namespace/project/merge_requests/1"
- #
- # location.pathname # => "/namespace/project/merge_requests/1/diffs"
- # setCurrentAction('commits')
- # location.pathname # => "/namespace/project/merge_requests/1/commits"
- #
- # Returns the new URL String
- setCurrentAction: (action) =>
- # Normalize action, just to be safe
- action = 'notes' if action == 'show'
-
- # Remove a trailing '/commits' or '/diffs'
- new_state = @_location.pathname.replace(/\/(commits|diffs|builds)(\.html)?\/?$/, '')
-
- # Append the new action if we're on a tab other than 'notes'
- unless action == 'notes'
- new_state += "/#{action}"
-
- # Ensure parameters and hash come along for the ride
- new_state += @_location.search + @_location.hash
-
- # Replace the current history state with the new one without breaking
- # Turbolinks' history.
- #
- # See https://github.com/rails/turbolinks/issues/363
- history.replaceState {turbolinks: true, url: new_state}, document.title, new_state
-
- new_state
-
- loadCommits: (source) ->
- return if @commitsLoaded
-
- @_get
- url: "#{source}.json"
- success: (data) =>
- document.querySelector("div#commits").innerHTML = data.html
- gl.utils.localTimeAgo($('.js-timeago', 'div#commits'))
- @commitsLoaded = true
- @scrollToElement("#commits")
-
- loadDiff: (source) ->
- return if @diffsLoaded
- @_get
- url: "#{source}.json" + @_location.search
- success: (data) =>
- $('#diffs').html data.html
- gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
- $('#diffs .js-syntax-highlight').syntaxHighlight()
- $('#diffs .diff-file').singleFileDiff()
- @expandViewContainer() if @diffViewType() is 'parallel'
- @diffsLoaded = true
- @scrollToElement("#diffs")
- @highlighSelectedLine()
- @filesCommentButton = $('.files .diff-file').filesCommentButton()
-
- $(document)
- .off 'click', '.diff-line-num a'
- .on 'click', '.diff-line-num a', (e) =>
- e.preventDefault()
- window.location.hash = $(e.currentTarget).attr 'href'
- @highlighSelectedLine()
- @scrollToElement("#diffs")
-
- highlighSelectedLine: ->
- $('.hll').removeClass 'hll'
- locationHash = window.location.hash
-
- if locationHash isnt ''
- hashClassString = ".#{locationHash.replace('#', '')}"
- $diffLine = $("#{locationHash}:not(.match)", $('#diffs'))
-
- if not $diffLine.is 'tr'
- $diffLine = $('#diffs').find("td#{locationHash}, td#{hashClassString}")
- else
- $diffLine = $diffLine.find('td')
-
- if $diffLine.length
- $diffLine.addClass 'hll'
- diffLineTop = $diffLine.offset().top
- navBarHeight = $('.navbar-gitlab').outerHeight()
-
- loadBuilds: (source) ->
- return if @buildsLoaded
-
- @_get
- url: "#{source}.json"
- success: (data) =>
- document.querySelector("div#builds").innerHTML = data.html
- gl.utils.localTimeAgo($('.js-timeago', 'div#builds'))
- @buildsLoaded = true
- @scrollToElement("#builds")
-
- # Show or hide the loading spinner
- #
- # status - Boolean, true to show, false to hide
- toggleLoading: (status) ->
- $('.mr-loading-status .loading').toggle(status)
-
- _get: (options) ->
- defaults = {
- beforeSend: => @toggleLoading(true)
- complete: => @toggleLoading(false)
- dataType: 'json'
- type: 'GET'
- }
-
- options = $.extend({}, defaults, options)
-
- $.ajax(options)
-
- # Returns diff view type
- diffViewType: ->
- $('.inline-parallel-buttons a.active').data('view-type')
-
- expandViewContainer: ->
- $('.container-fluid').removeClass('container-limited')
-
- shrinkView: ->
- $gutterIcon = $('.js-sidebar-toggle i:visible')
-
- # Wait until listeners are set
- setTimeout( ->
- # Only when sidebar is expanded
- if $gutterIcon.is('.fa-angle-double-right')
- $gutterIcon.closest('a').trigger('click', [true])
- , 0)
-
- # Expand the issuable sidebar unless the user explicitly collapsed it
- expandView: ->
- return if $.cookie('collapsed_gutter') == 'true'
-
- $gutterIcon = $('.js-sidebar-toggle i:visible')
-
- # Wait until listeners are set
- setTimeout( ->
- # Only when sidebar is collapsed
- if $gutterIcon.is('.fa-angle-double-left')
- $gutterIcon.closest('a').trigger('click', [true])
- , 0)
diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js
new file mode 100644
index 00000000000..de749c9f98f
--- /dev/null
+++ b/app/assets/javascripts/merge_request_widget.js
@@ -0,0 +1,182 @@
+var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+this.MergeRequestWidget = (function() {
+ function MergeRequestWidget(opts) {
+ this.opts = opts;
+ $('#modal_merge_info').modal({
+ show: false
+ });
+ this.firstCICheck = true;
+ this.readyForCICheck = false;
+ this.cancel = false;
+ clearInterval(this.fetchBuildStatusInterval);
+ this.clearEventListeners();
+ this.addEventListeners();
+ this.getCIStatus(false);
+ this.pollCIStatus();
+ notifyPermissions();
+ }
+
+ MergeRequestWidget.prototype.clearEventListeners = function() {
+ return $(document).off('page:change.merge_request');
+ };
+
+ MergeRequestWidget.prototype.cancelPolling = function() {
+ return this.cancel = true;
+ };
+
+ MergeRequestWidget.prototype.addEventListeners = function() {
+ var allowedPages;
+ allowedPages = ['show', 'commits', 'builds', 'changes'];
+ return $(document).on('page:change.merge_request', (function(_this) {
+ return function() {
+ var page;
+ page = $('body').data('page').split(':').last();
+ if (allowedPages.indexOf(page) < 0) {
+ clearInterval(_this.fetchBuildStatusInterval);
+ _this.cancelPolling();
+ return _this.clearEventListeners();
+ }
+ };
+ })(this));
+ };
+
+ MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) {
+ if (deleteSourceBranch == null) {
+ deleteSourceBranch = false;
+ }
+ return $.ajax({
+ type: 'GET',
+ url: $('.merge-request').data('url'),
+ success: (function(_this) {
+ return function(data) {
+ var callback, urlSuffix;
+ if (data.state === "merged") {
+ urlSuffix = deleteSourceBranch ? '?delete_source=true' : '';
+ return window.location.href = window.location.pathname + urlSuffix;
+ } else if (data.merge_error) {
+ return $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>");
+ } else {
+ callback = function() {
+ return merge_request_widget.mergeInProgress(deleteSourceBranch);
+ };
+ return setTimeout(callback, 2000);
+ }
+ };
+ })(this),
+ dataType: 'json'
+ });
+ };
+
+ MergeRequestWidget.prototype.getMergeStatus = function() {
+ return $.get(this.opts.merge_check_url, function(data) {
+ return $('.mr-state-widget').replaceWith(data);
+ });
+ };
+
+ MergeRequestWidget.prototype.ciLabelForStatus = function(status) {
+ switch (status) {
+ case 'success':
+ return 'passed';
+ case 'success_with_warnings':
+ return 'passed with warnings';
+ default:
+ return status;
+ }
+ };
+
+ MergeRequestWidget.prototype.pollCIStatus = function() {
+ return this.fetchBuildStatusInterval = setInterval(((function(_this) {
+ return function() {
+ if (!_this.readyForCICheck) {
+ return;
+ }
+ _this.getCIStatus(true);
+ return _this.readyForCICheck = false;
+ };
+ })(this)), 10000);
+ };
+
+ MergeRequestWidget.prototype.getCIStatus = function(showNotification) {
+ var _this;
+ _this = this;
+ $('.ci-widget-fetching').show();
+ return $.getJSON(this.opts.ci_status_url, (function(_this) {
+ return function(data) {
+ var message, status, title;
+ if (_this.cancel) {
+ return;
+ }
+ _this.readyForCICheck = true;
+ if (data.status === '') {
+ return;
+ }
+ if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) {
+ _this.opts.ci_status = data.status;
+ _this.showCIStatus(data.status);
+ if (data.coverage) {
+ _this.showCICoverage(data.coverage);
+ }
+ if (showNotification && !_this.firstCICheck) {
+ status = _this.ciLabelForStatus(data.status);
+ if (status === "preparing") {
+ title = _this.opts.ci_title.preparing;
+ status = status.charAt(0).toUpperCase() + status.slice(1);
+ message = _this.opts.ci_message.preparing.replace('{{status}}', status);
+ } else {
+ title = _this.opts.ci_title.normal;
+ message = _this.opts.ci_message.normal.replace('{{status}}', status);
+ }
+ title = title.replace('{{status}}', status);
+ message = message.replace('{{sha}}', data.sha);
+ message = message.replace('{{title}}', data.title);
+ notify(title, message, _this.opts.gitlab_icon, function() {
+ this.close();
+ return Turbolinks.visit(_this.opts.builds_path);
+ });
+ }
+ return _this.firstCICheck = false;
+ }
+ };
+ })(this));
+ };
+
+ MergeRequestWidget.prototype.showCIStatus = function(state) {
+ var allowed_states;
+ if (state == null) {
+ return;
+ }
+ $('.ci_widget').hide();
+ allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"];
+ if (indexOf.call(allowed_states, state) >= 0) {
+ $('.ci_widget.ci-' + state).show();
+ switch (state) {
+ case "failed":
+ case "canceled":
+ case "not_found":
+ return this.setMergeButtonClass('btn-danger');
+ case "running":
+ return this.setMergeButtonClass('btn-warning');
+ case "success":
+ case "success_with_warnings":
+ return this.setMergeButtonClass('btn-create');
+ }
+ } else {
+ $('.ci_widget.ci-error').show();
+ return this.setMergeButtonClass('btn-danger');
+ }
+ };
+
+ MergeRequestWidget.prototype.showCICoverage = function(coverage) {
+ var text;
+ text = 'Coverage ' + coverage + '%';
+ return $('.ci_widget:visible .ci-coverage').text(text);
+ };
+
+ MergeRequestWidget.prototype.setMergeButtonClass = function(css_class) {
+ return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-warning btn-create').addClass(css_class);
+ };
+
+ return MergeRequestWidget;
+
+})();
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
deleted file mode 100644
index 963a0550c35..00000000000
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ /dev/null
@@ -1,143 +0,0 @@
-class @MergeRequestWidget
- # Initialize MergeRequestWidget behavior
- #
- # check_enable - Boolean, whether to check automerge status
- # merge_check_url - String, URL to use to check automerge status
- # ci_status_url - String, URL to use to check CI status
- #
-
- constructor: (@opts) ->
- $('#modal_merge_info').modal(show: false)
- @firstCICheck = true
- @readyForCICheck = false
- @cancel = false
- clearInterval @fetchBuildStatusInterval
-
- @clearEventListeners()
- @addEventListeners()
- @getCIStatus(false)
- @pollCIStatus()
- notifyPermissions()
-
- clearEventListeners: ->
- $(document).off 'page:change.merge_request'
-
- cancelPolling: ->
- @cancel = true
-
- addEventListeners: ->
- allowedPages = ['show', 'commits', 'builds', 'changes']
- $(document).on 'page:change.merge_request', =>
- page = $('body').data('page').split(':').last()
- if allowedPages.indexOf(page) < 0
- clearInterval @fetchBuildStatusInterval
- @cancelPolling()
- @clearEventListeners()
-
- mergeInProgress: (deleteSourceBranch = false)->
- $.ajax
- type: 'GET'
- url: $('.merge-request').data('url')
- success: (data) =>
- if data.state == "merged"
- urlSuffix = if deleteSourceBranch then '?delete_source=true' else ''
-
- window.location.href = window.location.pathname + urlSuffix
- else if data.merge_error
- $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>")
- else
- callback = -> merge_request_widget.mergeInProgress(deleteSourceBranch)
- setTimeout(callback, 2000)
- dataType: 'json'
-
- getMergeStatus: ->
- $.get @opts.merge_check_url, (data) ->
- $('.mr-state-widget').replaceWith(data)
-
- ciLabelForStatus: (status) ->
- switch status
- when 'success'
- 'passed'
- when 'success_with_warnings'
- 'passed with warnings'
- else
- status
-
- pollCIStatus: ->
- @fetchBuildStatusInterval = setInterval ( =>
- return if not @readyForCICheck
-
- @getCIStatus(true)
-
- @readyForCICheck = false
- ), 10000
-
- getCIStatus: (showNotification) ->
- _this = @
- $('.ci-widget-fetching').show()
-
- $.getJSON @opts.ci_status_url, (data) =>
- return if @cancel
- @readyForCICheck = true
-
- if data.status is ''
- return
-
- if @firstCICheck || data.status isnt @opts.ci_status and data.status?
- @opts.ci_status = data.status
- @showCIStatus data.status
- if data.coverage
- @showCICoverage data.coverage
-
- # The first check should only update the UI, a notification
- # should only be displayed on status changes
- if showNotification and not @firstCICheck
- status = @ciLabelForStatus(data.status)
-
- if status is "preparing"
- title = @opts.ci_title.preparing
- status = status.charAt(0).toUpperCase() + status.slice(1);
- message = @opts.ci_message.preparing.replace('{{status}}', status)
- else
- title = @opts.ci_title.normal
- message = @opts.ci_message.normal.replace('{{status}}', status)
-
- title = title.replace('{{status}}', status)
- message = message.replace('{{sha}}', data.sha)
- message = message.replace('{{title}}', data.title)
-
- notify(
- title,
- message,
- @opts.gitlab_icon,
- ->
- @close()
- Turbolinks.visit _this.opts.builds_path
- )
- @firstCICheck = false
-
- showCIStatus: (state) ->
- return if not state?
- $('.ci_widget').hide()
- allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"]
- if state in allowed_states
- $('.ci_widget.ci-' + state).show()
- switch state
- when "failed", "canceled", "not_found"
- @setMergeButtonClass('btn-danger')
- when "running"
- @setMergeButtonClass('btn-warning')
- when "success", "success_with_warnings"
- @setMergeButtonClass('btn-create')
- else
- $('.ci_widget.ci-error').show()
- @setMergeButtonClass('btn-danger')
-
- showCICoverage: (coverage) ->
- text = 'Coverage ' + coverage + '%'
- $('.ci_widget:visible .ci-coverage').text(text)
-
- setMergeButtonClass: (css_class) ->
- $('.js-merge-button,.accept-action .dropdown-toggle')
- .removeClass('btn-danger btn-warning btn-create')
- .addClass(css_class)
diff --git a/app/assets/javascripts/merged_buttons.js b/app/assets/javascripts/merged_buttons.js
new file mode 100644
index 00000000000..fc909f18569
--- /dev/null
+++ b/app/assets/javascripts/merged_buttons.js
@@ -0,0 +1,42 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.MergedButtons = (function() {
+ function MergedButtons() {
+ this.removeSourceBranch = bind(this.removeSourceBranch, this);
+ this.$removeBranchWidget = $('.remove_source_branch_widget');
+ this.$removeBranchProgress = $('.remove_source_branch_in_progress');
+ this.$removeBranchFailed = $('.remove_source_branch_widget.failed');
+ this.cleanEventListeners();
+ this.initEventListeners();
+ }
+
+ MergedButtons.prototype.cleanEventListeners = function() {
+ $(document).off('click', '.remove_source_branch');
+ $(document).off('ajax:success', '.remove_source_branch');
+ return $(document).off('ajax:error', '.remove_source_branch');
+ };
+
+ MergedButtons.prototype.initEventListeners = function() {
+ $(document).on('click', '.remove_source_branch', this.removeSourceBranch);
+ $(document).on('ajax:success', '.remove_source_branch', this.removeBranchSuccess);
+ return $(document).on('ajax:error', '.remove_source_branch', this.removeBranchError);
+ };
+
+ MergedButtons.prototype.removeSourceBranch = function() {
+ this.$removeBranchWidget.hide();
+ return this.$removeBranchProgress.show();
+ };
+
+ MergedButtons.prototype.removeBranchSuccess = function() {
+ return location.reload();
+ };
+
+ MergedButtons.prototype.removeBranchError = function() {
+ this.$removeBranchWidget.hide();
+ this.$removeBranchProgress.hide();
+ return this.$removeBranchFailed.show();
+ };
+
+ return MergedButtons;
+
+})();
diff --git a/app/assets/javascripts/merged_buttons.js.coffee b/app/assets/javascripts/merged_buttons.js.coffee
deleted file mode 100644
index 4929295c10b..00000000000
--- a/app/assets/javascripts/merged_buttons.js.coffee
+++ /dev/null
@@ -1,30 +0,0 @@
-class @MergedButtons
- constructor: ->
- @$removeBranchWidget = $('.remove_source_branch_widget')
- @$removeBranchProgress = $('.remove_source_branch_in_progress')
- @$removeBranchFailed = $('.remove_source_branch_widget.failed')
-
- @cleanEventListeners()
- @initEventListeners()
-
- cleanEventListeners: ->
- $(document).off 'click', '.remove_source_branch'
- $(document).off 'ajax:success', '.remove_source_branch'
- $(document).off 'ajax:error', '.remove_source_branch'
-
- initEventListeners: ->
- $(document).on 'click', '.remove_source_branch', @removeSourceBranch
- $(document).on 'ajax:success', '.remove_source_branch', @removeBranchSuccess
- $(document).on 'ajax:error', '.remove_source_branch', @removeBranchError
-
- removeSourceBranch: =>
- @$removeBranchWidget.hide()
- @$removeBranchProgress.show()
-
- removeBranchSuccess: ->
- location.reload()
-
- removeBranchError: ->
- @$removeBranchWidget.hide()
- @$removeBranchProgress.hide()
- @$removeBranchFailed.show()
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
new file mode 100644
index 00000000000..c86cc03a340
--- /dev/null
+++ b/app/assets/javascripts/milestone.js
@@ -0,0 +1,192 @@
+this.Milestone = (function() {
+ Milestone.updateIssue = function(li, issue_url, data) {
+ return $.ajax({
+ type: "PUT",
+ url: issue_url,
+ data: data,
+ success: (function(_this) {
+ return function(_data) {
+ return _this.successCallback(_data, li);
+ };
+ })(this),
+ error: function(data) {
+ return new Flash("Issue update failed", 'alert');
+ },
+ dataType: "json"
+ });
+ };
+
+ Milestone.sortIssues = function(data) {
+ var sort_issues_url;
+ sort_issues_url = location.href + "/sort_issues";
+ return $.ajax({
+ type: "PUT",
+ url: sort_issues_url,
+ data: data,
+ success: (function(_this) {
+ return function(_data) {
+ return _this.successCallback(_data);
+ };
+ })(this),
+ error: function() {
+ return new Flash("Issues update failed", 'alert');
+ },
+ dataType: "json"
+ });
+ };
+
+ Milestone.sortMergeRequests = function(data) {
+ var sort_mr_url;
+ sort_mr_url = location.href + "/sort_merge_requests";
+ return $.ajax({
+ type: "PUT",
+ url: sort_mr_url,
+ data: data,
+ success: (function(_this) {
+ return function(_data) {
+ return _this.successCallback(_data);
+ };
+ })(this),
+ error: function(data) {
+ return new Flash("Issue update failed", 'alert');
+ },
+ dataType: "json"
+ });
+ };
+
+ Milestone.updateMergeRequest = function(li, merge_request_url, data) {
+ return $.ajax({
+ type: "PUT",
+ url: merge_request_url,
+ data: data,
+ success: (function(_this) {
+ return function(_data) {
+ return _this.successCallback(_data, li);
+ };
+ })(this),
+ error: function(data) {
+ return new Flash("Issue update failed", 'alert');
+ },
+ dataType: "json"
+ });
+ };
+
+ Milestone.successCallback = function(data, element) {
+ var img_tag;
+ if (data.assignee) {
+ img_tag = $('<img/>');
+ img_tag.attr('src', data.assignee.avatar_url);
+ img_tag.addClass('avatar s16');
+ $(element).find('.assignee-icon').html(img_tag);
+ } else {
+ $(element).find('.assignee-icon').html('');
+ }
+ return $(element).effect('highlight');
+ };
+
+ function Milestone() {
+ var oldMouseStart;
+ oldMouseStart = $.ui.sortable.prototype._mouseStart;
+ $.ui.sortable.prototype._mouseStart = function(event, overrideHandle, noActivation) {
+ this._trigger("beforeStart", event, this._uiHash());
+ return oldMouseStart.apply(this, [event, overrideHandle, noActivation]);
+ };
+ this.bindIssuesSorting();
+ this.bindMergeRequestSorting();
+ this.bindTabsSwitching();
+ }
+
+ Milestone.prototype.bindIssuesSorting = function() {
+ return $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable({
+ connectWith: ".issues-sortable-list",
+ dropOnEmpty: true,
+ items: "li:not(.ui-sort-disabled)",
+ beforeStart: function(event, ui) {
+ return $(".issues-sortable-list").css("min-height", ui.item.outerHeight());
+ },
+ stop: function(event, ui) {
+ return $(".issues-sortable-list").css("min-height", "0px");
+ },
+ update: function(event, ui) {
+ var data;
+ if ($(this).find(ui.item).length > 0) {
+ data = $(this).sortable("serialize");
+ return Milestone.sortIssues(data);
+ }
+ },
+ receive: function(event, ui) {
+ var data, issue_id, issue_url, new_state;
+ new_state = $(this).data('state');
+ issue_id = ui.item.data('iid');
+ issue_url = ui.item.data('url');
+ data = (function() {
+ switch (new_state) {
+ case 'ongoing':
+ return "issue[assignee_id]=" + gon.current_user_id;
+ case 'unassigned':
+ return "issue[assignee_id]=";
+ case 'closed':
+ return "issue[state_event]=close";
+ }
+ })();
+ if ($(ui.sender).data('state') === "closed") {
+ data += "&issue[state_event]=reopen";
+ }
+ return Milestone.updateIssue(ui.item, issue_url, data);
+ }
+ }).disableSelection();
+ };
+
+ Milestone.prototype.bindTabsSwitching = function() {
+ return $('a[data-toggle="tab"]').on('show.bs.tab', function(e) {
+ var currentTabClass, previousTabClass;
+ currentTabClass = $(e.target).data('show');
+ previousTabClass = $(e.relatedTarget).data('show');
+ $(previousTabClass).hide();
+ $(currentTabClass).removeClass('hidden');
+ return $(currentTabClass).show();
+ });
+ };
+
+ Milestone.prototype.bindMergeRequestSorting = function() {
+ return $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable({
+ connectWith: ".merge_requests-sortable-list",
+ dropOnEmpty: true,
+ items: "li:not(.ui-sort-disabled)",
+ beforeStart: function(event, ui) {
+ return $(".merge_requests-sortable-list").css("min-height", ui.item.outerHeight());
+ },
+ stop: function(event, ui) {
+ return $(".merge_requests-sortable-list").css("min-height", "0px");
+ },
+ update: function(event, ui) {
+ var data;
+ data = $(this).sortable("serialize");
+ return Milestone.sortMergeRequests(data);
+ },
+ receive: function(event, ui) {
+ var data, merge_request_id, merge_request_url, new_state;
+ new_state = $(this).data('state');
+ merge_request_id = ui.item.data('iid');
+ merge_request_url = ui.item.data('url');
+ data = (function() {
+ switch (new_state) {
+ case 'ongoing':
+ return "merge_request[assignee_id]=" + gon.current_user_id;
+ case 'unassigned':
+ return "merge_request[assignee_id]=";
+ case 'closed':
+ return "merge_request[state_event]=close";
+ }
+ })();
+ if ($(ui.sender).data('state') === "closed") {
+ data += "&merge_request[state_event]=reopen";
+ }
+ return Milestone.updateMergeRequest(ui.item, merge_request_url, data);
+ }
+ }).disableSelection();
+ };
+
+ return Milestone;
+
+})();
diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee
deleted file mode 100644
index a19e68b39e2..00000000000
--- a/app/assets/javascripts/milestone.js.coffee
+++ /dev/null
@@ -1,146 +0,0 @@
-class @Milestone
- @updateIssue: (li, issue_url, data) ->
- $.ajax
- type: "PUT"
- url: issue_url
- data: data
- success: (_data) =>
- @successCallback(_data, li)
- error: (data) ->
- new Flash("Issue update failed", 'alert')
- dataType: "json"
-
- @sortIssues: (data) ->
- sort_issues_url = location.href + "/sort_issues"
-
- $.ajax
- type: "PUT"
- url: sort_issues_url
- data: data
- success: (_data) =>
- @successCallback(_data)
- error: ->
- new Flash("Issues update failed", 'alert')
- dataType: "json"
-
- @sortMergeRequests: (data) ->
- sort_mr_url = location.href + "/sort_merge_requests"
-
- $.ajax
- type: "PUT"
- url: sort_mr_url
- data: data
- success: (_data) =>
- @successCallback(_data)
- error: (data) ->
- new Flash("Issue update failed", 'alert')
- dataType: "json"
-
- @updateMergeRequest: (li, merge_request_url, data) ->
- $.ajax
- type: "PUT"
- url: merge_request_url
- data: data
- success: (_data) =>
- @successCallback(_data, li)
- error: (data) ->
- new Flash("Issue update failed", 'alert')
- dataType: "json"
-
- @successCallback: (data, element) =>
- if data.assignee
- img_tag = $('<img/>')
- img_tag.attr('src', data.assignee.avatar_url)
- img_tag.addClass('avatar s16')
- $(element).find('.assignee-icon').html(img_tag)
- else
- $(element).find('.assignee-icon').html('')
-
- $(element).effect 'highlight'
-
- constructor: ->
- oldMouseStart = $.ui.sortable.prototype._mouseStart
- $.ui.sortable.prototype._mouseStart = (event, overrideHandle, noActivation) ->
- this._trigger "beforeStart", event, this._uiHash()
- oldMouseStart.apply this, [event, overrideHandle, noActivation]
-
- @bindIssuesSorting()
- @bindMergeRequestSorting()
- @bindTabsSwitching()
-
- bindIssuesSorting: ->
- $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
- connectWith: ".issues-sortable-list",
- dropOnEmpty: true,
- items: "li:not(.ui-sort-disabled)",
- beforeStart: (event, ui) ->
- $(".issues-sortable-list").css "min-height", ui.item.outerHeight()
- stop: (event, ui) ->
- $(".issues-sortable-list").css "min-height", "0px"
- update: (event, ui) ->
- # Prevents sorting from container which element has been removed.
- if $(this).find(ui.item).length > 0
- data = $(this).sortable("serialize")
- Milestone.sortIssues(data)
-
- receive: (event, ui) ->
- new_state = $(this).data('state')
- issue_id = ui.item.data('iid')
- issue_url = ui.item.data('url')
-
- data = switch new_state
- when 'ongoing'
- "issue[assignee_id]=" + gon.current_user_id
- when 'unassigned'
- "issue[assignee_id]="
- when 'closed'
- "issue[state_event]=close"
-
- if $(ui.sender).data('state') == "closed"
- data += "&issue[state_event]=reopen"
-
- Milestone.updateIssue(ui.item, issue_url, data)
-
- ).disableSelection()
-
- bindTabsSwitching: ->
- $('a[data-toggle="tab"]').on 'show.bs.tab', (e) ->
- currentTabClass = $(e.target).data('show')
- previousTabClass = $(e.relatedTarget).data('show')
-
- $(previousTabClass).hide()
- $(currentTabClass).removeClass('hidden')
- $(currentTabClass).show()
-
- bindMergeRequestSorting: ->
- $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable(
- connectWith: ".merge_requests-sortable-list",
- dropOnEmpty: true,
- items: "li:not(.ui-sort-disabled)",
- beforeStart: (event, ui) ->
- $(".merge_requests-sortable-list").css "min-height", ui.item.outerHeight()
- stop: (event, ui) ->
- $(".merge_requests-sortable-list").css "min-height", "0px"
- update: (event, ui) ->
- data = $(this).sortable("serialize")
- Milestone.sortMergeRequests(data)
-
- receive: (event, ui) ->
- new_state = $(this).data('state')
- merge_request_id = ui.item.data('iid')
- merge_request_url = ui.item.data('url')
-
- data = switch new_state
- when 'ongoing'
- "merge_request[assignee_id]=" + gon.current_user_id
- when 'unassigned'
- "merge_request[assignee_id]="
- when 'closed'
- "merge_request[state_event]=close"
-
- if $(ui.sender).data('state') == "closed"
- data += "&merge_request[state_event]=reopen"
-
- Milestone.updateMergeRequest(ui.item, merge_request_url, data)
-
- ).disableSelection()
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
new file mode 100644
index 00000000000..ed7b320f8d1
--- /dev/null
+++ b/app/assets/javascripts/milestone_select.js
@@ -0,0 +1,148 @@
+this.MilestoneSelect = (function() {
+ function MilestoneSelect(currentProject) {
+ var _this;
+ if (currentProject != null) {
+ _this = this;
+ this.currentProject = JSON.parse(currentProject);
+ }
+ $('.js-milestone-select').each(function(i, dropdown) {
+ var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId;
+ $dropdown = $(dropdown);
+ projectId = $dropdown.data('project-id');
+ milestonesUrl = $dropdown.data('milestones');
+ issueUpdateURL = $dropdown.data('issueUpdate');
+ selectedMilestone = $dropdown.data('selected');
+ showNo = $dropdown.data('show-no');
+ showAny = $dropdown.data('show-any');
+ showUpcoming = $dropdown.data('show-upcoming');
+ useId = $dropdown.data('use-id');
+ defaultLabel = $dropdown.data('default-label');
+ issuableId = $dropdown.data('issuable-id');
+ abilityName = $dropdown.data('ability-name');
+ $selectbox = $dropdown.closest('.selectbox');
+ $block = $selectbox.closest('.block');
+ $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
+ $value = $block.find('.value');
+ $loading = $block.find('.block-loading').fadeOut();
+ if (issueUpdateURL) {
+ milestoneLinkTemplate = _.template('<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
+ milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
+ collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>');
+ }
+ return $dropdown.glDropdown({
+ data: function(term, callback) {
+ return $.ajax({
+ url: milestonesUrl
+ }).done(function(data) {
+ var extraOptions;
+ extraOptions = [];
+ if (showAny) {
+ extraOptions.push({
+ id: 0,
+ name: '',
+ title: 'Any Milestone'
+ });
+ }
+ if (showNo) {
+ extraOptions.push({
+ id: -1,
+ name: 'No Milestone',
+ title: 'No Milestone'
+ });
+ }
+ if (showUpcoming) {
+ extraOptions.push({
+ id: -2,
+ name: '#upcoming',
+ title: 'Upcoming'
+ });
+ }
+ if (extraOptions.length > 2) {
+ extraOptions.push('divider');
+ }
+ return callback(extraOptions.concat(data));
+ });
+ },
+ filterable: true,
+ search: {
+ fields: ['title']
+ },
+ selectable: true,
+ toggleLabel: function(selected) {
+ if (selected && 'id' in selected) {
+ return selected.title;
+ } else {
+ return defaultLabel;
+ }
+ },
+ fieldName: $dropdown.data('field-name'),
+ text: function(milestone) {
+ return _.escape(milestone.title);
+ },
+ id: function(milestone) {
+ if (!useId) {
+ return milestone.name;
+ } else {
+ return milestone.id;
+ }
+ },
+ isSelected: function(milestone) {
+ return milestone.name === selectedMilestone;
+ },
+ hidden: function() {
+ $selectbox.hide();
+ return $value.css('display', '');
+ },
+ clicked: function(selected) {
+ var data, isIssueIndex, isMRIndex, page;
+ page = $('body').data('page');
+ isIssueIndex = page === 'projects:issues:index';
+ isMRIndex = (page === page && page === 'projects:merge_requests:index');
+ if ($dropdown.hasClass('js-filter-bulk-update')) {
+ return;
+ }
+ if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+ if (selected.name != null) {
+ selectedMilestone = selected.name;
+ } else {
+ selectedMilestone = '';
+ }
+ return Issuable.filterResults($dropdown.closest('form'));
+ } else if ($dropdown.hasClass('js-filter-submit')) {
+ return $dropdown.closest('form').submit();
+ } else {
+ selected = $selectbox.find('input[type="hidden"]').val();
+ data = {};
+ data[abilityName] = {};
+ data[abilityName].milestone_id = selected != null ? selected : null;
+ $loading.fadeIn();
+ $dropdown.trigger('loading.gl.dropdown');
+ return $.ajax({
+ type: 'PUT',
+ url: issueUpdateURL,
+ data: data
+ }).done(function(data) {
+ $dropdown.trigger('loaded.gl.dropdown');
+ $loading.fadeOut();
+ $selectbox.hide();
+ $value.css('display', '');
+ if (data.milestone != null) {
+ data.milestone.namespace = _this.currentProject.namespace;
+ data.milestone.path = _this.currentProject.path;
+ data.milestone.remaining = $.timefor(data.milestone.due_date);
+ $value.html(milestoneLinkTemplate(data.milestone));
+ return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
+ } else {
+ $value.html(milestoneLinkNoneTemplate);
+ return $sidebarCollapsedValue.find('span').text('No');
+ }
+ });
+ }
+ }
+ });
+ });
+ }
+
+ return MilestoneSelect;
+
+})();
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
deleted file mode 100644
index 8ab03ed93ee..00000000000
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ /dev/null
@@ -1,137 +0,0 @@
-class @MilestoneSelect
- constructor: (currentProject) ->
- if currentProject?
- _this = @
- @currentProject = JSON.parse(currentProject)
- $('.js-milestone-select').each (i, dropdown) ->
- $dropdown = $(dropdown)
- projectId = $dropdown.data('project-id')
- milestonesUrl = $dropdown.data('milestones')
- issueUpdateURL = $dropdown.data('issueUpdate')
- selectedMilestone = $dropdown.data('selected')
- showNo = $dropdown.data('show-no')
- showAny = $dropdown.data('show-any')
- showUpcoming = $dropdown.data('show-upcoming')
- useId = $dropdown.data('use-id')
- defaultLabel = $dropdown.data('default-label')
- issuableId = $dropdown.data('issuable-id')
- abilityName = $dropdown.data('ability-name')
- $selectbox = $dropdown.closest('.selectbox')
- $block = $selectbox.closest('.block')
- $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon')
- $value = $block.find('.value')
- $loading = $block.find('.block-loading').fadeOut()
-
- if issueUpdateURL
- milestoneLinkTemplate = _.template(
- '<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>'
- )
-
- milestoneLinkNoneTemplate = '<span class="no-value">None</span>'
-
- collapsedSidebarLabelTemplate = _.template(
- '<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left">
- <%- title %>
- </span>'
- )
-
- $dropdown.glDropdown(
- data: (term, callback) ->
- $.ajax(
- url: milestonesUrl
- ).done (data) ->
- extraOptions = []
- if showAny
- extraOptions.push(
- id: 0
- name: ''
- title: 'Any Milestone'
- )
-
- if showNo
- extraOptions.push(
- id: -1
- name: 'No Milestone'
- title: 'No Milestone'
- )
-
- if showUpcoming
- extraOptions.push(
- id: -2
- name: '#upcoming'
- title: 'Upcoming'
- )
-
- if extraOptions.length > 2
- extraOptions.push 'divider'
-
- callback(extraOptions.concat(data))
- filterable: true
- search:
- fields: ['title']
- selectable: true
- toggleLabel: (selected) ->
- if selected && 'id' of selected
- selected.title
- else
- defaultLabel
- fieldName: $dropdown.data('field-name')
- text: (milestone) ->
- _.escape(milestone.title)
- id: (milestone) ->
- if !useId
- milestone.name
- else
- milestone.id
- isSelected: (milestone) ->
- milestone.name is selectedMilestone
- hidden: ->
- $selectbox.hide()
-
- # display:block overrides the hide-collapse rule
- $value.css('display', '')
- clicked: (selected) ->
- page = $('body').data 'page'
- isIssueIndex = page is 'projects:issues:index'
- isMRIndex = page is page is 'projects:merge_requests:index'
-
- if $dropdown.hasClass 'js-filter-bulk-update'
- return
-
- if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
- if selected.name?
- selectedMilestone = selected.name
- else
- selectedMilestone = ''
- Issuable.filterResults $dropdown.closest('form')
- else if $dropdown.hasClass('js-filter-submit')
- $dropdown.closest('form').submit()
- else
- selected = $selectbox
- .find('input[type="hidden"]')
- .val()
- data = {}
- data[abilityName] = {}
- data[abilityName].milestone_id = if selected? then selected else null
- $loading
- .fadeIn()
- $dropdown.trigger('loading.gl.dropdown')
- $.ajax(
- type: 'PUT'
- url: issueUpdateURL
- data: data
- ).done (data) ->
- $dropdown.trigger('loaded.gl.dropdown')
- $loading.fadeOut()
- $selectbox.hide()
- $value.css('display', '')
- if data.milestone?
- data.milestone.namespace = _this.currentProject.namespace
- data.milestone.path = _this.currentProject.path
- data.milestone.remaining = $.timefor data.milestone.due_date
- $value.html(milestoneLinkTemplate(data.milestone))
- $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone))
- else
- $value.html(milestoneLinkNoneTemplate)
- $sidebarCollapsedValue.find('span').text('No')
- )
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
new file mode 100644
index 00000000000..960c121e5fe
--- /dev/null
+++ b/app/assets/javascripts/namespace_select.js
@@ -0,0 +1,83 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.NamespaceSelect = (function() {
+ function NamespaceSelect(opts) {
+ this.onSelectItem = bind(this.onSelectItem, this);
+ var fieldName, showAny;
+ this.dropdown = opts.dropdown;
+ showAny = true;
+ fieldName = 'namespace_id';
+ if (this.dropdown.attr('data-field-name')) {
+ fieldName = this.dropdown.data('fieldName');
+ }
+ if (this.dropdown.attr('data-show-any')) {
+ showAny = this.dropdown.data('showAny');
+ }
+ this.dropdown.glDropdown({
+ filterable: true,
+ selectable: true,
+ filterRemote: true,
+ search: {
+ fields: ['path']
+ },
+ fieldName: fieldName,
+ toggleLabel: function(selected) {
+ if (selected.id == null) {
+ return selected.text;
+ } else {
+ return selected.kind + ": " + selected.path;
+ }
+ },
+ data: function(term, dataCallback) {
+ return Api.namespaces(term, function(namespaces) {
+ var anyNamespace;
+ if (showAny) {
+ anyNamespace = {
+ text: 'Any namespace',
+ id: null
+ };
+ namespaces.unshift(anyNamespace);
+ namespaces.splice(1, 0, 'divider');
+ }
+ return dataCallback(namespaces);
+ });
+ },
+ text: function(namespace) {
+ if (namespace.id == null) {
+ return namespace.text;
+ } else {
+ return namespace.kind + ": " + namespace.path;
+ }
+ },
+ renderRow: this.renderRow,
+ clicked: this.onSelectItem
+ });
+ }
+
+ NamespaceSelect.prototype.onSelectItem = function(item, el, e) {
+ return e.preventDefault();
+ };
+
+ return NamespaceSelect;
+
+})();
+
+this.NamespaceSelects = (function() {
+ function NamespaceSelects(opts) {
+ var ref;
+ if (opts == null) {
+ opts = {};
+ }
+ this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-namespace-select');
+ this.$dropdowns.each(function(i, dropdown) {
+ var $dropdown;
+ $dropdown = $(dropdown);
+ return new NamespaceSelect({
+ dropdown: $dropdown
+ });
+ });
+ }
+
+ return NamespaceSelects;
+
+})();
diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee
deleted file mode 100644
index 3b419dff105..00000000000
--- a/app/assets/javascripts/namespace_select.js.coffee
+++ /dev/null
@@ -1,56 +0,0 @@
-class @NamespaceSelect
- constructor: (opts) ->
- {
- @dropdown
- } = opts
-
- showAny = true
- fieldName = 'namespace_id'
-
- if @dropdown.attr 'data-field-name'
- fieldName = @dropdown.data 'fieldName'
-
- if @dropdown.attr 'data-show-any'
- showAny = @dropdown.data 'showAny'
-
- @dropdown.glDropdown(
- filterable: true
- selectable: true
- filterRemote: true
- search:
- fields: ['path']
- fieldName: fieldName
- toggleLabel: (selected) ->
- return if not selected.id? then selected.text else "#{selected.kind}: #{selected.path}"
- data: (term, dataCallback) ->
- Api.namespaces term, (namespaces) ->
- if showAny
- anyNamespace =
- text: 'Any namespace'
- id: null
-
- namespaces.unshift(anyNamespace)
- namespaces.splice 1, 0, 'divider'
-
- dataCallback(namespaces)
- text: (namespace) ->
- return if not namespace.id? then namespace.text else "#{namespace.kind}: #{namespace.path}"
- renderRow: @renderRow
- clicked: @onSelectItem
- )
-
- onSelectItem: (item, el, e) =>
- e.preventDefault()
-
-class @NamespaceSelects
- constructor: (opts = {}) ->
- {
- @$dropdowns = $('.js-namespace-select')
- } = opts
-
- @$dropdowns.each (i, dropdown) ->
- $dropdown = $(dropdown)
-
- new NamespaceSelect(
- dropdown: $dropdown
- )
diff --git a/app/assets/javascripts/network/branch-graph.js b/app/assets/javascripts/network/branch-graph.js
new file mode 100644
index 00000000000..4fab506f0a4
--- /dev/null
+++ b/app/assets/javascripts/network/branch-graph.js
@@ -0,0 +1,401 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.BranchGraph = (function() {
+ function BranchGraph(element1, options1) {
+ this.element = element1;
+ this.options = options1;
+ this.scrollTop = bind(this.scrollTop, this);
+ this.scrollBottom = bind(this.scrollBottom, this);
+ this.scrollRight = bind(this.scrollRight, this);
+ this.scrollLeft = bind(this.scrollLeft, this);
+ this.scrollUp = bind(this.scrollUp, this);
+ this.scrollDown = bind(this.scrollDown, this);
+ this.preparedCommits = {};
+ this.mtime = 0;
+ this.mspace = 0;
+ this.parents = {};
+ this.colors = ["#000"];
+ this.offsetX = 150;
+ this.offsetY = 20;
+ this.unitTime = 30;
+ this.unitSpace = 10;
+ this.prev_start = -1;
+ this.load();
+ }
+
+ BranchGraph.prototype.load = function() {
+ return $.ajax({
+ url: this.options.url,
+ method: "get",
+ dataType: "json",
+ success: $.proxy(function(data) {
+ $(".loading", this.element).hide();
+ this.prepareData(data.days, data.commits);
+ return this.buildGraph();
+ }, this)
+ });
+ };
+
+ BranchGraph.prototype.prepareData = function(days, commits) {
+ var c, ch, cw, j, len, ref;
+ this.days = days;
+ this.commits = commits;
+ this.collectParents();
+ this.graphHeight = $(this.element).height();
+ this.graphWidth = $(this.element).width();
+ ch = Math.max(this.graphHeight, this.offsetY + this.unitTime * this.mtime + 150);
+ cw = Math.max(this.graphWidth, this.offsetX + this.unitSpace * this.mspace + 300);
+ this.r = Raphael(this.element.get(0), cw, ch);
+ this.top = this.r.set();
+ this.barHeight = Math.max(this.graphHeight, this.unitTime * this.days.length + 320);
+ ref = this.commits;
+ for (j = 0, len = ref.length; j < len; j++) {
+ c = ref[j];
+ if (c.id in this.parents) {
+ c.isParent = true;
+ }
+ this.preparedCommits[c.id] = c;
+ this.markCommit(c);
+ }
+ return this.collectColors();
+ };
+
+ BranchGraph.prototype.collectParents = function() {
+ var c, j, len, p, ref, results;
+ ref = this.commits;
+ results = [];
+ for (j = 0, len = ref.length; j < len; j++) {
+ c = ref[j];
+ this.mtime = Math.max(this.mtime, c.time);
+ this.mspace = Math.max(this.mspace, c.space);
+ results.push((function() {
+ var l, len1, ref1, results1;
+ ref1 = c.parents;
+ results1 = [];
+ for (l = 0, len1 = ref1.length; l < len1; l++) {
+ p = ref1[l];
+ this.parents[p[0]] = true;
+ results1.push(this.mspace = Math.max(this.mspace, p[1]));
+ }
+ return results1;
+ }).call(this));
+ }
+ return results;
+ };
+
+ BranchGraph.prototype.collectColors = function() {
+ var k, results;
+ k = 0;
+ results = [];
+ while (k < this.mspace) {
+ this.colors.push(Raphael.getColor(.8));
+ Raphael.getColor();
+ Raphael.getColor();
+ results.push(k++);
+ }
+ return results;
+ };
+
+ BranchGraph.prototype.buildGraph = function() {
+ var cuday, cumonth, day, j, len, mm, r, ref;
+ r = this.r;
+ cuday = 0;
+ cumonth = "";
+ r.rect(0, 0, 40, this.barHeight).attr({
+ fill: "#222"
+ });
+ r.rect(40, 0, 30, this.barHeight).attr({
+ fill: "#444"
+ });
+ ref = this.days;
+ for (mm = j = 0, len = ref.length; j < len; mm = ++j) {
+ day = ref[mm];
+ if (cuday !== day[0] || cumonth !== day[1]) {
+ r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({
+ font: "12px Monaco, monospace",
+ fill: "#BBB"
+ });
+ cuday = day[0];
+ }
+ if (cumonth !== day[1]) {
+ r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({
+ font: "12px Monaco, monospace",
+ fill: "#EEE"
+ });
+ cumonth = day[1];
+ }
+ }
+ this.renderPartialGraph();
+ return this.bindEvents();
+ };
+
+ BranchGraph.prototype.renderPartialGraph = function() {
+ var commit, end, i, isGraphEdge, start, x, y;
+ start = Math.floor((this.element.scrollTop() - this.offsetY) / this.unitTime) - 10;
+ if (start < 0) {
+ isGraphEdge = true;
+ start = 0;
+ }
+ end = start + 40;
+ if (this.commits.length < end) {
+ isGraphEdge = true;
+ end = this.commits.length;
+ }
+ if (this.prev_start === -1 || Math.abs(this.prev_start - start) > 10 || isGraphEdge) {
+ i = start;
+ this.prev_start = start;
+ while (i < end) {
+ commit = this.commits[i];
+ i += 1;
+ if (commit.hasDrawn !== true) {
+ x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
+ y = this.offsetY + this.unitTime * commit.time;
+ this.drawDot(x, y, commit);
+ this.drawLines(x, y, commit);
+ this.appendLabel(x, y, commit);
+ this.appendAnchor(x, y, commit);
+ commit.hasDrawn = true;
+ }
+ }
+ return this.top.toFront();
+ }
+ };
+
+ BranchGraph.prototype.bindEvents = function() {
+ var element;
+ element = this.element;
+ return $(element).scroll((function(_this) {
+ return function(event) {
+ return _this.renderPartialGraph();
+ };
+ })(this));
+ };
+
+ BranchGraph.prototype.scrollDown = function() {
+ this.element.scrollTop(this.element.scrollTop() + 50);
+ return this.renderPartialGraph();
+ };
+
+ BranchGraph.prototype.scrollUp = function() {
+ this.element.scrollTop(this.element.scrollTop() - 50);
+ return this.renderPartialGraph();
+ };
+
+ BranchGraph.prototype.scrollLeft = function() {
+ this.element.scrollLeft(this.element.scrollLeft() - 50);
+ return this.renderPartialGraph();
+ };
+
+ BranchGraph.prototype.scrollRight = function() {
+ this.element.scrollLeft(this.element.scrollLeft() + 50);
+ return this.renderPartialGraph();
+ };
+
+ BranchGraph.prototype.scrollBottom = function() {
+ return this.element.scrollTop(this.element.find('svg').height());
+ };
+
+ BranchGraph.prototype.scrollTop = function() {
+ return this.element.scrollTop(0);
+ };
+
+ BranchGraph.prototype.appendLabel = function(x, y, commit) {
+ var label, r, rect, shortrefs, text, textbox, triangle;
+ if (!commit.refs) {
+ return;
+ }
+ r = this.r;
+ shortrefs = commit.refs;
+ if (shortrefs.length > 17) {
+ shortrefs = shortrefs.substr(0, 15) + "…";
+ }
+ text = r.text(x + 4, y, shortrefs).attr({
+ "text-anchor": "start",
+ font: "10px Monaco, monospace",
+ fill: "#FFF",
+ title: commit.refs
+ });
+ textbox = text.getBBox();
+ rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({
+ fill: "#000",
+ "fill-opacity": .5,
+ stroke: "none"
+ });
+ triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr({
+ fill: "#000",
+ "fill-opacity": .5,
+ stroke: "none"
+ });
+ label = r.set(rect, text);
+ label.transform(["t", -rect.getBBox().width - 15, 0]);
+ return text.toFront();
+ };
+
+ BranchGraph.prototype.appendAnchor = function(x, y, commit) {
+ var anchor, options, r, top;
+ r = this.r;
+ top = this.top;
+ options = this.options;
+ anchor = r.circle(x, y, 10).attr({
+ fill: "#000",
+ opacity: 0,
+ cursor: "pointer"
+ }).click(function() {
+ return window.open(options.commit_url.replace("%s", commit.id), "_blank");
+ }).hover(function() {
+ this.tooltip = r.commitTooltip(x + 5, y, commit);
+ return top.push(this.tooltip.insertBefore(this));
+ }, function() {
+ return this.tooltip && this.tooltip.remove() && delete this.tooltip;
+ });
+ return top.push(anchor);
+ };
+
+ BranchGraph.prototype.drawDot = function(x, y, commit) {
+ var avatar_box_x, avatar_box_y, r;
+ r = this.r;
+ r.circle(x, y, 3).attr({
+ fill: this.colors[commit.space],
+ stroke: "none"
+ });
+ avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10;
+ avatar_box_y = y - 10;
+ r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({
+ stroke: this.colors[commit.space],
+ "stroke-width": 2
+ });
+ r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20);
+ return r.text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split("\n")[0]).attr({
+ "text-anchor": "start",
+ font: "14px Monaco, monospace"
+ });
+ };
+
+ BranchGraph.prototype.drawLines = function(x, y, commit) {
+ var arrow, color, i, j, len, offset, parent, parentCommit, parentX1, parentX2, parentY, r, ref, results, route;
+ r = this.r;
+ ref = commit.parents;
+ results = [];
+ for (i = j = 0, len = ref.length; j < len; i = ++j) {
+ parent = ref[i];
+ parentCommit = this.preparedCommits[parent[0]];
+ parentY = this.offsetY + this.unitTime * parentCommit.time;
+ parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space);
+ parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]);
+ if (parentCommit.space <= commit.space) {
+ color = this.colors[commit.space];
+ } else {
+ color = this.colors[parentCommit.space];
+ }
+ if (parent[1] === commit.space) {
+ offset = [0, 5];
+ arrow = "l-2,5,4,0,-2,-5,0,5";
+ } else if (parent[1] < commit.space) {
+ offset = [3, 3];
+ arrow = "l5,0,-2,4,-3,-4,4,2";
+ } else {
+ offset = [-3, 3];
+ arrow = "l-5,0,2,4,3,-4,-4,2";
+ }
+ route = ["M", x + offset[0], y + offset[1]];
+ if (i > 0) {
+ route.push(arrow);
+ }
+ if (commit.space !== parentCommit.space || commit.space !== parent[1]) {
+ route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5);
+ }
+ route.push("L", parentX1, parentY);
+ results.push(r.path(route).attr({
+ stroke: color,
+ "stroke-width": 2
+ }));
+ }
+ return results;
+ };
+
+ BranchGraph.prototype.markCommit = function(commit) {
+ var r, x, y;
+ if (commit.id === this.options.commit_id) {
+ r = this.r;
+ x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
+ y = this.offsetY + this.unitTime * commit.time;
+ r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr({
+ fill: "#000",
+ "fill-opacity": .5,
+ stroke: "none"
+ });
+ return this.element.scrollTop(y - this.graphHeight / 2);
+ }
+ };
+
+ return BranchGraph;
+
+})();
+
+Raphael.prototype.commitTooltip = function(x, y, commit) {
+ var boxHeight, boxWidth, icon, idText, messageText, nameText, rect, textSet, tooltip;
+ boxWidth = 300;
+ boxHeight = 200;
+ icon = this.image(gon.relative_url_root + commit.author.icon, x, y, 20, 20);
+ nameText = this.text(x + 25, y + 10, commit.author.name);
+ idText = this.text(x, y + 35, commit.id);
+ messageText = this.text(x, y + 50, commit.message);
+ textSet = this.set(icon, nameText, idText, messageText).attr({
+ "text-anchor": "start",
+ font: "12px Monaco, monospace"
+ });
+ nameText.attr({
+ font: "14px Arial",
+ "font-weight": "bold"
+ });
+ idText.attr({
+ fill: "#AAA"
+ });
+ this.textWrap(messageText, boxWidth - 50);
+ rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({
+ fill: "#FFF",
+ stroke: "#000",
+ "stroke-linecap": "round",
+ "stroke-width": 2
+ });
+ tooltip = this.set(rect, textSet);
+ rect.attr({
+ height: tooltip.getBBox().height + 10,
+ width: tooltip.getBBox().width + 10
+ });
+ tooltip.transform(["t", 20, 20]);
+ return tooltip;
+};
+
+Raphael.prototype.textWrap = function(t, width) {
+ var abc, b, content, h, j, len, letterWidth, s, word, words, x;
+ content = t.attr("text");
+ abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ t.attr({
+ text: abc
+ });
+ letterWidth = t.getBBox().width / abc.length;
+ t.attr({
+ text: content
+ });
+ words = content.split(" ");
+ x = 0;
+ s = [];
+ for (j = 0, len = words.length; j < len; j++) {
+ word = words[j];
+ if (x + (word.length * letterWidth) > width) {
+ s.push("\n");
+ x = 0;
+ }
+ x += word.length * letterWidth;
+ s.push(word + " ");
+ }
+ t.attr({
+ text: s.join("")
+ });
+ b = t.getBBox();
+ h = Math.abs(b.y2) - Math.abs(b.y) + 1;
+ return t.attr({
+ y: b.y + h
+ });
+};
diff --git a/app/assets/javascripts/network/branch-graph.js.coffee b/app/assets/javascripts/network/branch-graph.js.coffee
deleted file mode 100644
index f2fd2a775a4..00000000000
--- a/app/assets/javascripts/network/branch-graph.js.coffee
+++ /dev/null
@@ -1,340 +0,0 @@
-class @BranchGraph
- constructor: (@element, @options) ->
- @preparedCommits = {}
- @mtime = 0
- @mspace = 0
- @parents = {}
- @colors = ["#000"]
- @offsetX = 150
- @offsetY = 20
- @unitTime = 30
- @unitSpace = 10
- @prev_start = -1
- @load()
-
- load: ->
- $.ajax
- url: @options.url
- method: "get"
- dataType: "json"
- success: $.proxy((data) ->
- $(".loading", @element).hide()
- @prepareData data.days, data.commits
- @buildGraph()
- , this)
-
- prepareData: (@days, @commits) ->
- @collectParents()
- @graphHeight = $(@element).height()
- @graphWidth = $(@element).width()
- ch = Math.max(@graphHeight, @offsetY + @unitTime * @mtime + 150)
- cw = Math.max(@graphWidth, @offsetX + @unitSpace * @mspace + 300)
- @r = Raphael(@element.get(0), cw, ch)
- @top = @r.set()
- @barHeight = Math.max(@graphHeight, @unitTime * @days.length + 320)
-
- for c in @commits
- c.isParent = true if c.id of @parents
- @preparedCommits[c.id] = c
- @markCommit(c)
-
- @collectColors()
-
- collectParents: ->
- for c in @commits
- @mtime = Math.max(@mtime, c.time)
- @mspace = Math.max(@mspace, c.space)
- for p in c.parents
- @parents[p[0]] = true
- @mspace = Math.max(@mspace, p[1])
-
- collectColors: ->
- k = 0
- while k < @mspace
- @colors.push Raphael.getColor(.8)
- # Skipping a few colors in the spectrum to get more contrast between colors
- Raphael.getColor()
- Raphael.getColor()
- k++
-
- buildGraph: ->
- r = @r
- cuday = 0
- cumonth = ""
-
- r.rect(0, 0, 40, @barHeight).attr fill: "#222"
- r.rect(40, 0, 30, @barHeight).attr fill: "#444"
-
- for day, mm in @days
- if cuday isnt day[0] || cumonth isnt day[1]
- # Dates
- r.text(55, @offsetY + @unitTime * mm, day[0])
- .attr(
- font: "12px Monaco, monospace"
- fill: "#BBB"
- )
- cuday = day[0]
-
- if cumonth isnt day[1]
- # Months
- r.text(20, @offsetY + @unitTime * mm, day[1])
- .attr(
- font: "12px Monaco, monospace"
- fill: "#EEE"
- )
- cumonth = day[1]
-
- @renderPartialGraph()
-
- @bindEvents()
-
- renderPartialGraph: ->
- start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10
- if start < 0
- isGraphEdge = true
- start = 0
- end = start + 40
- if @commits.length < end
- isGraphEdge = true
- end = @commits.length
-
- if @prev_start == -1 or Math.abs(@prev_start - start) > 10 or isGraphEdge
- i = start
-
- @prev_start = start
-
- while i < end
- commit = @commits[i]
- i += 1
-
- if commit.hasDrawn isnt true
- x = @offsetX + @unitSpace * (@mspace - commit.space)
- y = @offsetY + @unitTime * commit.time
-
- @drawDot(x, y, commit)
-
- @drawLines(x, y, commit)
-
- @appendLabel(x, y, commit)
-
- @appendAnchor(x, y, commit)
-
- commit.hasDrawn = true
-
- @top.toFront()
-
- bindEvents: ->
- element = @element
-
- $(element).scroll (event) =>
- @renderPartialGraph()
-
- scrollDown: =>
- @element.scrollTop @element.scrollTop() + 50
- @renderPartialGraph()
-
- scrollUp: =>
- @element.scrollTop @element.scrollTop() - 50
- @renderPartialGraph()
-
- scrollLeft: =>
- @element.scrollLeft @element.scrollLeft() - 50
- @renderPartialGraph()
-
- scrollRight: =>
- @element.scrollLeft @element.scrollLeft() + 50
- @renderPartialGraph()
-
- scrollBottom: =>
- @element.scrollTop @element.find('svg').height()
-
- scrollTop: =>
- @element.scrollTop 0
-
- appendLabel: (x, y, commit) ->
- return unless commit.refs
-
- r = @r
- shortrefs = commit.refs
- # Truncate if longer than 15 chars
- shortrefs = shortrefs.substr(0, 15) + "…" if shortrefs.length > 17
- text = r.text(x + 4, y, shortrefs).attr(
- "text-anchor": "start"
- font: "10px Monaco, monospace"
- fill: "#FFF"
- title: commit.refs
- )
- textbox = text.getBBox()
- # Create rectangle based on the size of the textbox
- rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr(
- fill: "#000"
- "fill-opacity": .5
- stroke: "none"
- )
- triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr(
- fill: "#000"
- "fill-opacity": .5
- stroke: "none"
- )
-
- label = r.set(rect, text)
- label.transform(["t", -rect.getBBox().width - 15, 0])
-
- # Set text to front
- text.toFront()
-
- appendAnchor: (x, y, commit) ->
- r = @r
- top = @top
- options = @options
- anchor = r.circle(x, y, 10).attr(
- fill: "#000"
- opacity: 0
- cursor: "pointer"
- ).click(->
- window.open options.commit_url.replace("%s", commit.id), "_blank"
- ).hover(->
- @tooltip = r.commitTooltip(x + 5, y, commit)
- top.push @tooltip.insertBefore(this)
- , ->
- @tooltip and @tooltip.remove() and delete @tooltip
- )
- top.push anchor
-
- drawDot: (x, y, commit) ->
- r = @r
- r.circle(x, y, 3).attr(
- fill: @colors[commit.space]
- stroke: "none"
- )
-
- avatar_box_x = @offsetX + @unitSpace * @mspace + 10
- avatar_box_y = y - 10
- r.rect(avatar_box_x, avatar_box_y, 20, 20).attr(
- stroke: @colors[commit.space]
- "stroke-width": 2
- )
- r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20)
- r.text(@offsetX + @unitSpace * @mspace + 35, y, commit.message.split("\n")[0]).attr(
- "text-anchor": "start"
- font: "14px Monaco, monospace"
- )
-
- drawLines: (x, y, commit) ->
- r = @r
- for parent, i in commit.parents
- parentCommit = @preparedCommits[parent[0]]
- parentY = @offsetY + @unitTime * parentCommit.time
- parentX1 = @offsetX + @unitSpace * (@mspace - parentCommit.space)
- parentX2 = @offsetX + @unitSpace * (@mspace - parent[1])
-
- # Set line color
- if parentCommit.space <= commit.space
- color = @colors[commit.space]
-
- else
- color = @colors[parentCommit.space]
-
- # Build line shape
- if parent[1] is commit.space
- offset = [0, 5]
- arrow = "l-2,5,4,0,-2,-5,0,5"
-
- else if parent[1] < commit.space
- offset = [3, 3]
- arrow = "l5,0,-2,4,-3,-4,4,2"
-
- else
- offset = [-3, 3]
- arrow = "l-5,0,2,4,3,-4,-4,2"
-
- # Start point
- route = ["M", x + offset[0], y + offset[1]]
-
- # Add arrow if not first parent
- if i > 0
- route.push(arrow)
-
- # Circumvent if overlap
- if commit.space isnt parentCommit.space or commit.space isnt parent[1]
- route.push(
- "L", parentX2, y + 10,
- "L", parentX2, parentY - 5,
- )
-
- # End point
- route.push("L", parentX1, parentY)
-
- r
- .path(route)
- .attr(
- stroke: color
- "stroke-width": 2)
-
- markCommit: (commit) ->
- if commit.id is @options.commit_id
- r = @r
- x = @offsetX + @unitSpace * (@mspace - commit.space)
- y = @offsetY + @unitTime * commit.time
- r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr(
- fill: "#000"
- "fill-opacity": .5
- stroke: "none"
- )
- # Displayed in the center
- @element.scrollTop(y - @graphHeight / 2)
-
-Raphael::commitTooltip = (x, y, commit) ->
- boxWidth = 300
- boxHeight = 200
- icon = @image(gon.relative_url_root + commit.author.icon, x, y, 20, 20)
- nameText = @text(x + 25, y + 10, commit.author.name)
- idText = @text(x, y + 35, commit.id)
- messageText = @text(x, y + 50, commit.message)
- textSet = @set(icon, nameText, idText, messageText).attr(
- "text-anchor": "start"
- font: "12px Monaco, monospace"
- )
- nameText.attr(
- font: "14px Arial"
- "font-weight": "bold"
- )
-
- idText.attr fill: "#AAA"
- @textWrap messageText, boxWidth - 50
- rect = @rect(x - 10, y - 10, boxWidth, 100, 4).attr(
- fill: "#FFF"
- stroke: "#000"
- "stroke-linecap": "round"
- "stroke-width": 2
- )
- tooltip = @set(rect, textSet)
- rect.attr(
- height: tooltip.getBBox().height + 10
- width: tooltip.getBBox().width + 10
- )
-
- tooltip.transform ["t", 20, 20]
- tooltip
-
-Raphael::textWrap = (t, width) ->
- content = t.attr("text")
- abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
- t.attr text: abc
- letterWidth = t.getBBox().width / abc.length
- t.attr text: content
- words = content.split(" ")
- x = 0
- s = []
-
- for word in words
- if x + (word.length * letterWidth) > width
- s.push "\n"
- x = 0
- x += word.length * letterWidth
- s.push word + " "
-
- t.attr text: s.join("")
- b = t.getBBox()
- h = Math.abs(b.y2) - Math.abs(b.y) + 1
- t.attr y: b.y + h
diff --git a/app/assets/javascripts/network/network.js b/app/assets/javascripts/network/network.js
new file mode 100644
index 00000000000..8c64c307f6e
--- /dev/null
+++ b/app/assets/javascripts/network/network.js
@@ -0,0 +1,16 @@
+this.Network = (function() {
+ function Network(opts) {
+ var vph;
+ $("#filter_ref").click(function() {
+ return $(this).closest('form').submit();
+ });
+ this.branch_graph = new BranchGraph($(".network-graph"), opts);
+ vph = $(window).height() - 250;
+ $('.network-graph').css({
+ 'height': vph + 'px'
+ });
+ }
+
+ return Network;
+
+})();
diff --git a/app/assets/javascripts/network/network.js.coffee b/app/assets/javascripts/network/network.js.coffee
deleted file mode 100644
index f4ef07a50a7..00000000000
--- a/app/assets/javascripts/network/network.js.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-class @Network
- constructor: (opts) ->
- $("#filter_ref").click ->
- $(this).closest('form').submit()
-
- @branch_graph = new BranchGraph($(".network-graph"), opts)
-
- vph = $(window).height() - 250
- $('.network-graph').css 'height': (vph + 'px')
diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js
new file mode 100644
index 00000000000..0a7ba2fcc2c
--- /dev/null
+++ b/app/assets/javascripts/network/network_bundle.js
@@ -0,0 +1,12 @@
+
+/*= require_tree . */
+$(function() {
+ var network_graph;
+ network_graph = new Network({
+ url: $(".network-graph").attr('data-url'),
+ commit_url: $(".network-graph").attr('data-commit-url'),
+ ref: $(".network-graph").attr('data-ref'),
+ commit_id: $(".network-graph").attr('data-commit-id')
+ });
+ return new ShortcutsNetwork(network_graph.branch_graph);
+});
diff --git a/app/assets/javascripts/network/network_bundle.js.coffee b/app/assets/javascripts/network/network_bundle.js.coffee
deleted file mode 100644
index f75f63869c5..00000000000
--- a/app/assets/javascripts/network/network_bundle.js.coffee
+++ /dev/null
@@ -1,17 +0,0 @@
-# This is a manifest file that'll be compiled into including all the files listed below.
-# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
-# be included in the compiled file accessible from http://example.com/assets/application.js
-# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-# the compiled file.
-#
-#= require_tree .
-
-$ ->
- network_graph = new Network({
- url: $(".network-graph").attr('data-url'),
- commit_url: $(".network-graph").attr('data-commit-url'),
- ref: $(".network-graph").attr('data-ref'),
- commit_id: $(".network-graph").attr('data-commit-id')
- })
-
- new ShortcutsNetwork(network_graph.branch_graph)
diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js
new file mode 100644
index 00000000000..0e26620adee
--- /dev/null
+++ b/app/assets/javascripts/new_branch_form.js
@@ -0,0 +1,101 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+this.NewBranchForm = (function() {
+ function NewBranchForm(form, availableRefs) {
+ this.validate = bind(this.validate, this);
+ this.branchNameError = form.find('.js-branch-name-error');
+ this.name = form.find('.js-branch-name');
+ this.ref = form.find('#ref');
+ this.setupAvailableRefs(availableRefs);
+ this.setupRestrictions();
+ this.addBinding();
+ this.init();
+ }
+
+ NewBranchForm.prototype.addBinding = function() {
+ return this.name.on('blur', this.validate);
+ };
+
+ NewBranchForm.prototype.init = function() {
+ if (this.name.val().length > 0) {
+ return this.name.trigger('blur');
+ }
+ };
+
+ NewBranchForm.prototype.setupAvailableRefs = function(availableRefs) {
+ return this.ref.autocomplete({
+ source: availableRefs,
+ minLength: 1
+ });
+ };
+
+ NewBranchForm.prototype.setupRestrictions = function() {
+ var endsWith, invalid, single, startsWith;
+ startsWith = {
+ pattern: /^(\/|\.)/g,
+ prefix: "can't start with",
+ conjunction: "or"
+ };
+ endsWith = {
+ pattern: /(\/|\.|\.lock)$/g,
+ prefix: "can't end in",
+ conjunction: "or"
+ };
+ invalid = {
+ pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g,
+ prefix: "can't contain",
+ conjunction: ", "
+ };
+ single = {
+ pattern: /^@+$/g,
+ prefix: "can't be",
+ conjunction: "or"
+ };
+ return this.restrictions = [startsWith, invalid, endsWith, single];
+ };
+
+ NewBranchForm.prototype.validate = function() {
+ var errorMessage, errors, formatter, unique, validator;
+ this.branchNameError.empty();
+ unique = function(values, value) {
+ if (indexOf.call(values, value) < 0) {
+ values.push(value);
+ }
+ return values;
+ };
+ formatter = function(values, restriction) {
+ var formatted;
+ formatted = values.map(function(value) {
+ switch (false) {
+ case !/\s/.test(value):
+ return 'spaces';
+ case !/\/{2,}/g.test(value):
+ return 'consecutive slashes';
+ default:
+ return "'" + value + "'";
+ }
+ });
+ return restriction.prefix + " " + (formatted.join(restriction.conjunction));
+ };
+ validator = (function(_this) {
+ return function(errors, restriction) {
+ var matched;
+ matched = _this.name.val().match(restriction.pattern);
+ if (matched) {
+ return errors.concat(formatter(matched.reduce(unique, []), restriction));
+ } else {
+ return errors;
+ }
+ };
+ })(this);
+ errors = this.restrictions.reduce(validator, []);
+ if (errors.length > 0) {
+ errorMessage = $("<span/>").text(errors.join(', '));
+ return this.branchNameError.append(errorMessage);
+ }
+ };
+
+ return NewBranchForm;
+
+})();
diff --git a/app/assets/javascripts/new_branch_form.js.coffee b/app/assets/javascripts/new_branch_form.js.coffee
deleted file mode 100644
index 4b350854f78..00000000000
--- a/app/assets/javascripts/new_branch_form.js.coffee
+++ /dev/null
@@ -1,78 +0,0 @@
-class @NewBranchForm
- constructor: (form, availableRefs) ->
- @branchNameError = form.find('.js-branch-name-error')
- @name = form.find('.js-branch-name')
- @ref = form.find('#ref')
-
- @setupAvailableRefs(availableRefs)
- @setupRestrictions()
- @addBinding()
- @init()
-
- addBinding: ->
- @name.on 'blur', @validate
-
- init: ->
- @name.trigger 'blur' if @name.val().length > 0
-
- setupAvailableRefs: (availableRefs) ->
- @ref.autocomplete
- source: availableRefs,
- minLength: 1
-
- setupRestrictions: ->
- startsWith = {
- pattern: /^(\/|\.)/g,
- prefix: "can't start with",
- conjunction: "or"
- }
-
- endsWith = {
- pattern: /(\/|\.|\.lock)$/g,
- prefix: "can't end in",
- conjunction: "or"
- }
-
- invalid = {
- pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g
- prefix: "can't contain",
- conjunction: ", "
- }
-
- single = {
- pattern: /^@+$/g
- prefix: "can't be",
- conjunction: "or"
- }
-
- @restrictions = [startsWith, invalid, endsWith, single]
-
- validate: =>
- @branchNameError.empty()
-
- unique = (values, value) ->
- values.push(value) unless value in values
- values
-
- formatter = (values, restriction) ->
- formatted = values.map (value) ->
- switch
- when /\s/.test value then 'spaces'
- when /\/{2,}/g.test value then 'consecutive slashes'
- else "'#{value}'"
-
- "#{restriction.prefix} #{formatted.join(restriction.conjunction)}"
-
- validator = (errors, restriction) =>
- matched = @name.val().match(restriction.pattern)
-
- if matched
- errors.concat formatter(matched.reduce(unique, []), restriction)
- else
- errors
-
- errors = @restrictions.reduce validator, []
-
- if errors.length > 0
- errorMessage = $("<span/>").text(errors.join(', '))
- @branchNameError.append(errorMessage)
diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js
new file mode 100644
index 00000000000..0c6463f051c
--- /dev/null
+++ b/app/assets/javascripts/new_commit_form.js
@@ -0,0 +1,31 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.NewCommitForm = (function() {
+ function NewCommitForm(form) {
+ this.renderDestination = bind(this.renderDestination, this);
+ this.newBranch = form.find('.js-target-branch');
+ this.originalBranch = form.find('.js-original-branch');
+ this.createMergeRequest = form.find('.js-create-merge-request');
+ this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
+ this.renderDestination();
+ this.newBranch.keyup(this.renderDestination);
+ }
+
+ NewCommitForm.prototype.renderDestination = function() {
+ var different;
+ different = this.newBranch.val() !== this.originalBranch.val();
+ if (different) {
+ this.createMergeRequestContainer.show();
+ if (!this.wasDifferent) {
+ this.createMergeRequest.prop('checked', true);
+ }
+ } else {
+ this.createMergeRequestContainer.hide();
+ this.createMergeRequest.prop('checked', false);
+ }
+ return this.wasDifferent = different;
+ };
+
+ return NewCommitForm;
+
+})();
diff --git a/app/assets/javascripts/new_commit_form.js.coffee b/app/assets/javascripts/new_commit_form.js.coffee
deleted file mode 100644
index 03f0f51acfa..00000000000
--- a/app/assets/javascripts/new_commit_form.js.coffee
+++ /dev/null
@@ -1,21 +0,0 @@
-class @NewCommitForm
- constructor: (form) ->
- @newBranch = form.find('.js-target-branch')
- @originalBranch = form.find('.js-original-branch')
- @createMergeRequest = form.find('.js-create-merge-request')
- @createMergeRequestContainer = form.find('.js-create-merge-request-container')
-
- @renderDestination()
- @newBranch.keyup @renderDestination
-
- renderDestination: =>
- different = @newBranch.val() != @originalBranch.val()
-
- if different
- @createMergeRequestContainer.show()
- @createMergeRequest.prop('checked', true) unless @wasDifferent
- else
- @createMergeRequestContainer.hide()
- @createMergeRequest.prop('checked', false)
-
- @wasDifferent = different
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
new file mode 100644
index 00000000000..83717cbeaa5
--- /dev/null
+++ b/app/assets/javascripts/notes.js
@@ -0,0 +1,722 @@
+
+/*= require autosave */
+
+/*= require autosize */
+
+/*= require dropzone */
+
+/*= require dropzone_input */
+
+/*= require gfm_auto_complete */
+
+/*= require jquery.atwho */
+
+/*= require task_list */
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.Notes = (function() {
+ var isMetaKey;
+
+ Notes.interval = null;
+
+ function Notes(notes_url, note_ids, last_fetched_at, view) {
+ this.updateTargetButtons = bind(this.updateTargetButtons, this);
+ this.updateCloseButton = bind(this.updateCloseButton, this);
+ this.visibilityChange = bind(this.visibilityChange, this);
+ this.cancelDiscussionForm = bind(this.cancelDiscussionForm, this);
+ this.addDiffNote = bind(this.addDiffNote, this);
+ this.setupDiscussionNoteForm = bind(this.setupDiscussionNoteForm, this);
+ this.replyToDiscussionNote = bind(this.replyToDiscussionNote, this);
+ this.removeNote = bind(this.removeNote, this);
+ this.cancelEdit = bind(this.cancelEdit, this);
+ this.updateNote = bind(this.updateNote, this);
+ this.addDiscussionNote = bind(this.addDiscussionNote, this);
+ this.addNoteError = bind(this.addNoteError, this);
+ this.addNote = bind(this.addNote, this);
+ this.resetMainTargetForm = bind(this.resetMainTargetForm, this);
+ this.refresh = bind(this.refresh, this);
+ this.keydownNoteText = bind(this.keydownNoteText, this);
+ this.notes_url = notes_url;
+ this.note_ids = note_ids;
+ this.last_fetched_at = last_fetched_at;
+ this.view = view;
+ this.noteable_url = document.URL;
+ this.notesCountBadge || (this.notesCountBadge = $(".issuable-details").find(".notes-tab .badge"));
+ this.basePollingInterval = 15000;
+ this.maxPollingSteps = 4;
+ this.cleanBinding();
+ this.addBinding();
+ this.setPollingInterval();
+ this.setupMainTargetNoteForm();
+ this.initTaskList();
+ }
+
+ Notes.prototype.addBinding = function() {
+ $(document).on("ajax:success", ".js-main-target-form", this.addNote);
+ $(document).on("ajax:success", ".js-discussion-note-form", this.addDiscussionNote);
+ $(document).on("ajax:error", ".js-main-target-form", this.addNoteError);
+ $(document).on("ajax:success", "form.edit-note", this.updateNote);
+ $(document).on("click", ".js-note-edit", this.showEditForm);
+ $(document).on("click", ".note-edit-cancel", this.cancelEdit);
+ $(document).on("click", ".js-comment-button", this.updateCloseButton);
+ $(document).on("keyup input", ".js-note-text", this.updateTargetButtons);
+ $(document).on("click", ".js-note-delete", this.removeNote);
+ $(document).on("click", ".js-note-attachment-delete", this.removeAttachment);
+ $(document).on("ajax:complete", ".js-main-target-form", this.reenableTargetFormSubmitButton);
+ $(document).on("ajax:success", ".js-main-target-form", this.resetMainTargetForm);
+ $(document).on("click", ".js-note-discard", this.resetMainTargetForm);
+ $(document).on("change", ".js-note-attachment-input", this.updateFormAttachment);
+ $(document).on("click", ".js-discussion-reply-button", this.replyToDiscussionNote);
+ $(document).on("click", ".js-add-diff-note-button", this.addDiffNote);
+ $(document).on("click", ".js-close-discussion-note-form", this.cancelDiscussionForm);
+ $(document).on("visibilitychange", this.visibilityChange);
+ $(document).on("issuable:change", this.refresh);
+ return $(document).on("keydown", ".js-note-text", this.keydownNoteText);
+ };
+
+ Notes.prototype.cleanBinding = function() {
+ $(document).off("ajax:success", ".js-main-target-form");
+ $(document).off("ajax:success", ".js-discussion-note-form");
+ $(document).off("ajax:success", "form.edit-note");
+ $(document).off("click", ".js-note-edit");
+ $(document).off("click", ".note-edit-cancel");
+ $(document).off("click", ".js-note-delete");
+ $(document).off("click", ".js-note-attachment-delete");
+ $(document).off("ajax:complete", ".js-main-target-form");
+ $(document).off("ajax:success", ".js-main-target-form");
+ $(document).off("click", ".js-discussion-reply-button");
+ $(document).off("click", ".js-add-diff-note-button");
+ $(document).off("visibilitychange");
+ $(document).off("keyup", ".js-note-text");
+ $(document).off("click", ".js-note-target-reopen");
+ $(document).off("click", ".js-note-target-close");
+ $(document).off("click", ".js-note-discard");
+ $(document).off("keydown", ".js-note-text");
+ $('.note .js-task-list-container').taskList('disable');
+ return $(document).off('tasklist:changed', '.note .js-task-list-container');
+ };
+
+ Notes.prototype.keydownNoteText = function(e) {
+ var $textarea, discussionNoteForm, editNote, myLastNote, myLastNoteEditBtn, newText, originalText;
+ if (isMetaKey(e)) {
+ return;
+ }
+ $textarea = $(e.target);
+ switch (e.which) {
+ case 38:
+ if ($textarea.val() !== '') {
+ return;
+ }
+ myLastNote = $("li.note[data-author-id='" + gon.current_user_id + "'][data-editable]:last");
+ if (myLastNote.length) {
+ myLastNoteEditBtn = myLastNote.find('.js-note-edit');
+ return myLastNoteEditBtn.trigger('click', [true, myLastNote]);
+ }
+ break;
+ case 27:
+ discussionNoteForm = $textarea.closest('.js-discussion-note-form');
+ if (discussionNoteForm.length) {
+ if ($textarea.val() !== '') {
+ if (!confirm('Are you sure you want to cancel creating this comment?')) {
+ return;
+ }
+ }
+ this.removeDiscussionNoteForm(discussionNoteForm);
+ return;
+ }
+ editNote = $textarea.closest('.note');
+ if (editNote.length) {
+ originalText = $textarea.closest('form').data('original-note');
+ newText = $textarea.val();
+ if (originalText !== newText) {
+ if (!confirm('Are you sure you want to cancel editing this comment?')) {
+ return;
+ }
+ }
+ return this.removeNoteEditForm(editNote);
+ }
+ }
+ };
+
+ isMetaKey = function(e) {
+ return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
+ };
+
+ Notes.prototype.initRefresh = function() {
+ clearInterval(Notes.interval);
+ return Notes.interval = setInterval((function(_this) {
+ return function() {
+ return _this.refresh();
+ };
+ })(this), this.pollingInterval);
+ };
+
+ Notes.prototype.refresh = function() {
+ if (!document.hidden && document.URL.indexOf(this.noteable_url) === 0) {
+ return this.getContent();
+ }
+ };
+
+ Notes.prototype.getContent = function() {
+ if (this.refreshing) {
+ return;
+ }
+ this.refreshing = true;
+ return $.ajax({
+ url: this.notes_url,
+ data: "last_fetched_at=" + this.last_fetched_at,
+ dataType: "json",
+ success: (function(_this) {
+ return function(data) {
+ var notes;
+ notes = data.notes;
+ _this.last_fetched_at = data.last_fetched_at;
+ _this.setPollingInterval(data.notes.length);
+ return $.each(notes, function(i, note) {
+ if (note.discussion_html != null) {
+ return _this.renderDiscussionNote(note);
+ } else {
+ return _this.renderNote(note);
+ }
+ });
+ };
+ })(this)
+ }).always((function(_this) {
+ return function() {
+ return _this.refreshing = false;
+ };
+ })(this));
+ };
+
+
+ /*
+ Increase @pollingInterval up to 120 seconds on every function call,
+ if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
+ will reset to @basePollingInterval.
+
+ Note: this function is used to gradually increase the polling interval
+ if there aren't new notes coming from the server
+ */
+
+ Notes.prototype.setPollingInterval = function(shouldReset) {
+ var nthInterval;
+ if (shouldReset == null) {
+ shouldReset = true;
+ }
+ nthInterval = this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1);
+ if (shouldReset) {
+ this.pollingInterval = this.basePollingInterval;
+ } else if (this.pollingInterval < nthInterval) {
+ this.pollingInterval *= 2;
+ }
+ return this.initRefresh();
+ };
+
+
+ /*
+ Render note in main comments area.
+
+ Note: for rendering inline notes use renderDiscussionNote
+ */
+
+ Notes.prototype.renderNote = function(note) {
+ var $notesList, votesBlock;
+ if (!note.valid) {
+ if (note.award) {
+ new Flash('You have already awarded this emoji!', 'alert');
+ }
+ return;
+ }
+ if (note.award) {
+ votesBlock = $('.js-awards-block').eq(0);
+ gl.awardsHandler.addAwardToEmojiBar(votesBlock, note.name);
+ return gl.awardsHandler.scrollToAwards();
+ } else if (this.isNewNote(note)) {
+ this.note_ids.push(note.id);
+ $notesList = $('ul.main-notes-list');
+ $notesList.append(note.html).syntaxHighlight();
+ gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false);
+ this.initTaskList();
+ return this.updateNotesCount(1);
+ }
+ };
+
+
+ /*
+ Check if note does not exists on page
+ */
+
+ Notes.prototype.isNewNote = function(note) {
+ return $.inArray(note.id, this.note_ids) === -1;
+ };
+
+ Notes.prototype.isParallelView = function() {
+ return this.view === 'parallel';
+ };
+
+
+ /*
+ Render note in discussion area.
+
+ Note: for rendering inline notes use renderDiscussionNote
+ */
+
+ Notes.prototype.renderDiscussionNote = function(note) {
+ var discussionContainer, form, note_html, row;
+ if (!this.isNewNote(note)) {
+ return;
+ }
+ this.note_ids.push(note.id);
+ form = $("#new-discussion-note-form-" + note.discussion_id);
+ if ((note.original_discussion_id != null) && form.length === 0) {
+ form = $("#new-discussion-note-form-" + note.original_discussion_id);
+ }
+ row = form.closest("tr");
+ note_html = $(note.html);
+ note_html.syntaxHighlight();
+ discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
+ if ((note.original_discussion_id != null) && discussionContainer.length === 0) {
+ discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']");
+ }
+ if (discussionContainer.length === 0) {
+ row.after(note.diff_discussion_html);
+ row.next().find(".note").remove();
+ discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
+ discussionContainer.append(note_html);
+ if ($('body').attr('data-page').indexOf('projects:merge_request') === 0) {
+ $('ul.main-notes-list').append(note.discussion_html).syntaxHighlight();
+ }
+ } else {
+ discussionContainer.append(note_html);
+ }
+ gl.utils.localTimeAgo($('.js-timeago', note_html), false);
+ return this.updateNotesCount(1);
+ };
+
+
+ /*
+ Called in response the main target form has been successfully submitted.
+
+ Removes any errors.
+ Resets text and preview.
+ Resets buttons.
+ */
+
+ Notes.prototype.resetMainTargetForm = function(e) {
+ var form;
+ form = $(".js-main-target-form");
+ form.find(".js-errors").remove();
+ form.find(".js-md-write-button").click();
+ form.find(".js-note-text").val("").trigger("input");
+ form.find(".js-note-text").data("autosave").reset();
+ return this.updateTargetButtons(e);
+ };
+
+ Notes.prototype.reenableTargetFormSubmitButton = function() {
+ var form;
+ form = $(".js-main-target-form");
+ return form.find(".js-note-text").trigger("input");
+ };
+
+
+ /*
+ Shows the main form and does some setup on it.
+
+ Sets some hidden fields in the form.
+ */
+
+ Notes.prototype.setupMainTargetNoteForm = function() {
+ var form;
+ form = $(".js-new-note-form");
+ this.formClone = form.clone();
+ this.setupNoteForm(form);
+ form.removeClass("js-new-note-form");
+ form.addClass("js-main-target-form");
+ form.find("#note_line_code").remove();
+ form.find("#note_position").remove();
+ form.find("#note_type").remove();
+ return this.parentTimeline = form.parents('.timeline');
+ };
+
+
+ /*
+ General note form setup.
+
+ deactivates the submit button when text is empty
+ hides the preview button when text is empty
+ setup GFM auto complete
+ show the form
+ */
+
+ Notes.prototype.setupNoteForm = function(form) {
+ var textarea;
+ new GLForm(form);
+ textarea = form.find(".js-note-text");
+ return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]);
+ };
+
+
+ /*
+ Called in response to the new note form being submitted
+
+ Adds new note to list.
+ */
+
+ Notes.prototype.addNote = function(xhr, note, status) {
+ return this.renderNote(note);
+ };
+
+ Notes.prototype.addNoteError = function(xhr, note, status) {
+ return new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', this.parentTimeline);
+ };
+
+
+ /*
+ Called in response to the new note form being submitted
+
+ Adds new note to list.
+ */
+
+ Notes.prototype.addDiscussionNote = function(xhr, note, status) {
+ this.renderDiscussionNote(note);
+ return this.removeDiscussionNoteForm($(xhr.target));
+ };
+
+
+ /*
+ Called in response to the edit note form being submitted
+
+ Updates the current note field.
+ */
+
+ Notes.prototype.updateNote = function(_xhr, note, _status) {
+ var $html, $note_li;
+ $html = $(note.html);
+ gl.utils.localTimeAgo($('.js-timeago', $html));
+ $html.syntaxHighlight();
+ $html.find('.js-task-list-container').taskList('enable');
+ $note_li = $('.note-row-' + note.id);
+ return $note_li.replaceWith($html);
+ };
+
+
+ /*
+ Called in response to clicking the edit note link
+
+ Replaces the note text with the note edit form
+ Adds a data attribute to the form with the original content of the note for cancellations
+ */
+
+ Notes.prototype.showEditForm = function(e, scrollTo, myLastNote) {
+ var $noteText, done, form, note;
+ e.preventDefault();
+ note = $(this).closest(".note");
+ note.addClass("is-editting");
+ form = note.find(".note-edit-form");
+ form.addClass('current-note-edit-form');
+ note.find(".js-note-attachment-delete").show();
+ done = function($noteText) {
+ var noteTextVal;
+ noteTextVal = $noteText.val();
+ form.find('form.edit-note').data('original-note', noteTextVal);
+ return $noteText.val('').val(noteTextVal);
+ };
+ new GLForm(form);
+ if ((scrollTo != null) && (myLastNote != null)) {
+ $('html, body').scrollTop($(document).height());
+ return $('html, body').animate({
+ scrollTop: myLastNote.offset().top - 150
+ }, 500, function() {
+ var $noteText;
+ $noteText = form.find(".js-note-text");
+ $noteText.focus();
+ return done($noteText);
+ });
+ } else {
+ $noteText = form.find('.js-note-text');
+ $noteText.focus();
+ return done($noteText);
+ }
+ };
+
+
+ /*
+ Called in response to clicking the edit note link
+
+ Hides edit form and restores the original note text to the editor textarea.
+ */
+
+ Notes.prototype.cancelEdit = function(e) {
+ var note;
+ e.preventDefault();
+ note = $(e.target).closest('.note');
+ return this.removeNoteEditForm(note);
+ };
+
+ Notes.prototype.removeNoteEditForm = function(note) {
+ var form;
+ form = note.find(".current-note-edit-form");
+ note.removeClass("is-editting");
+ form.removeClass("current-note-edit-form");
+ return form.find(".js-note-text").val(form.find('form.edit-note').data('original-note'));
+ };
+
+
+ /*
+ Called in response to deleting a note of any kind.
+
+ Removes the actual note from view.
+ Removes the whole discussion if the last note is being removed.
+ */
+
+ Notes.prototype.removeNote = function(e) {
+ var noteId;
+ noteId = $(e.currentTarget).closest(".note").attr("id");
+ $(".note[id='" + noteId + "']").each((function(_this) {
+ return function(i, el) {
+ var note, notes;
+ note = $(el);
+ notes = note.closest(".notes");
+ if (notes.find(".note").length === 1) {
+ notes.closest(".timeline-entry").remove();
+ notes.closest("tr").remove();
+ }
+ return note.remove();
+ };
+ })(this));
+ return this.updateNotesCount(-1);
+ };
+
+
+ /*
+ Called in response to clicking the delete attachment link
+
+ Removes the attachment wrapper view, including image tag if it exists
+ Resets the note editing form
+ */
+
+ Notes.prototype.removeAttachment = function() {
+ var note;
+ note = $(this).closest(".note");
+ note.find(".note-attachment").remove();
+ note.find(".note-body > .note-text").show();
+ note.find(".note-header").show();
+ return note.find(".current-note-edit-form").remove();
+ };
+
+
+ /*
+ Called when clicking on the "reply" button for a diff line.
+
+ Shows the note form below the notes.
+ */
+
+ Notes.prototype.replyToDiscussionNote = function(e) {
+ var form, replyLink;
+ form = this.formClone.clone();
+ replyLink = $(e.target).closest(".js-discussion-reply-button");
+ replyLink.hide();
+ replyLink.after(form);
+ return this.setupDiscussionNoteForm(replyLink, form);
+ };
+
+
+ /*
+ Shows the diff or discussion form and does some setup on it.
+
+ Sets some hidden fields in the form.
+
+ Note: dataHolder must have the "discussionId", "lineCode", "noteableType"
+ and "noteableId" data attributes set.
+ */
+
+ Notes.prototype.setupDiscussionNoteForm = function(dataHolder, form) {
+ form.attr('id', "new-discussion-note-form-" + (dataHolder.data("discussionId")));
+ form.attr("data-line-code", dataHolder.data("lineCode"));
+ form.find("#note_type").val(dataHolder.data("noteType"));
+ form.find("#line_type").val(dataHolder.data("lineType"));
+ form.find("#note_commit_id").val(dataHolder.data("commitId"));
+ form.find("#note_line_code").val(dataHolder.data("lineCode"));
+ form.find("#note_position").val(dataHolder.attr("data-position"));
+ form.find("#note_noteable_type").val(dataHolder.data("noteableType"));
+ form.find("#note_noteable_id").val(dataHolder.data("noteableId"));
+ form.find('.js-note-discard').show().removeClass('js-note-discard').addClass('js-close-discussion-note-form').text(form.find('.js-close-discussion-note-form').data('cancel-text'));
+ this.setupNoteForm(form);
+ form.find(".js-note-text").focus();
+ return form.removeClass('js-main-target-form').addClass("discussion-form js-discussion-note-form");
+ };
+
+
+ /*
+ Called when clicking on the "add a comment" button on the side of a diff line.
+
+ Inserts a temporary row for the form below the line.
+ Sets up the form and shows it.
+ */
+
+ Notes.prototype.addDiffNote = function(e) {
+ var $link, addForm, hasNotes, lineType, newForm, nextRow, noteForm, notesContent, replyButton, row, rowCssToAdd, targetContent;
+ e.preventDefault();
+ $link = $(e.currentTarget);
+ row = $link.closest("tr");
+ nextRow = row.next();
+ hasNotes = nextRow.is(".notes_holder");
+ addForm = false;
+ targetContent = ".notes_content";
+ rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"></td></tr>";
+ if (this.isParallelView()) {
+ lineType = $link.data("lineType");
+ targetContent += "." + lineType;
+ rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\"></td><td class=\"notes_content parallel old\"></td><td class=\"notes_line\"></td><td class=\"notes_content parallel new\"></td></tr>";
+ }
+ if (hasNotes) {
+ notesContent = nextRow.find(targetContent);
+ if (notesContent.length) {
+ replyButton = notesContent.find(".js-discussion-reply-button:visible");
+ if (replyButton.length) {
+ e.target = replyButton[0];
+ $.proxy(this.replyToDiscussionNote, replyButton[0], e).call();
+ } else {
+ noteForm = notesContent.find(".js-discussion-note-form");
+ if (noteForm.length === 0) {
+ addForm = true;
+ }
+ }
+ }
+ } else {
+ row.after(rowCssToAdd);
+ addForm = true;
+ }
+ if (addForm) {
+ newForm = this.formClone.clone();
+ newForm.appendTo(row.next().find(targetContent));
+ return this.setupDiscussionNoteForm($link, newForm);
+ }
+ };
+
+
+ /*
+ Called in response to "cancel" on a diff note form.
+
+ Shows the reply button again.
+ Removes the form and if necessary it's temporary row.
+ */
+
+ Notes.prototype.removeDiscussionNoteForm = function(form) {
+ var glForm, row;
+ row = form.closest("tr");
+ glForm = form.data('gl-form');
+ glForm.destroy();
+ form.find(".js-note-text").data("autosave").reset();
+ form.prev(".js-discussion-reply-button").show();
+ if (row.is(".js-temp-notes-holder")) {
+ return row.remove();
+ } else {
+ return form.remove();
+ }
+ };
+
+ Notes.prototype.cancelDiscussionForm = function(e) {
+ var form;
+ e.preventDefault();
+ form = $(e.target).closest(".js-discussion-note-form");
+ return this.removeDiscussionNoteForm(form);
+ };
+
+
+ /*
+ Called after an attachment file has been selected.
+
+ Updates the file name for the selected attachment.
+ */
+
+ Notes.prototype.updateFormAttachment = function() {
+ var filename, form;
+ form = $(this).closest("form");
+ filename = $(this).val().replace(/^.*[\\\/]/, "");
+ return form.find(".js-attachment-filename").text(filename);
+ };
+
+
+ /*
+ Called when the tab visibility changes
+ */
+
+ Notes.prototype.visibilityChange = function() {
+ return this.refresh();
+ };
+
+ Notes.prototype.updateCloseButton = function(e) {
+ var closebtn, form, textarea;
+ textarea = $(e.target);
+ form = textarea.parents('form');
+ closebtn = form.find('.js-note-target-close');
+ return closebtn.text(closebtn.data('original-text'));
+ };
+
+ Notes.prototype.updateTargetButtons = function(e) {
+ var closebtn, closetext, discardbtn, form, reopenbtn, reopentext, textarea;
+ textarea = $(e.target);
+ form = textarea.parents('form');
+ reopenbtn = form.find('.js-note-target-reopen');
+ closebtn = form.find('.js-note-target-close');
+ discardbtn = form.find('.js-note-discard');
+ if (textarea.val().trim().length > 0) {
+ reopentext = reopenbtn.data('alternative-text');
+ closetext = closebtn.data('alternative-text');
+ if (reopenbtn.text() !== reopentext) {
+ reopenbtn.text(reopentext);
+ }
+ if (closebtn.text() !== closetext) {
+ closebtn.text(closetext);
+ }
+ if (reopenbtn.is(':not(.btn-comment-and-reopen)')) {
+ reopenbtn.addClass('btn-comment-and-reopen');
+ }
+ if (closebtn.is(':not(.btn-comment-and-close)')) {
+ closebtn.addClass('btn-comment-and-close');
+ }
+ if (discardbtn.is(':hidden')) {
+ return discardbtn.show();
+ }
+ } else {
+ reopentext = reopenbtn.data('original-text');
+ closetext = closebtn.data('original-text');
+ if (reopenbtn.text() !== reopentext) {
+ reopenbtn.text(reopentext);
+ }
+ if (closebtn.text() !== closetext) {
+ closebtn.text(closetext);
+ }
+ if (reopenbtn.is('.btn-comment-and-reopen')) {
+ reopenbtn.removeClass('btn-comment-and-reopen');
+ }
+ if (closebtn.is('.btn-comment-and-close')) {
+ closebtn.removeClass('btn-comment-and-close');
+ }
+ if (discardbtn.is(':visible')) {
+ return discardbtn.hide();
+ }
+ }
+ };
+
+ Notes.prototype.initTaskList = function() {
+ this.enableTaskList();
+ return $(document).on('tasklist:changed', '.note .js-task-list-container', this.updateTaskList);
+ };
+
+ Notes.prototype.enableTaskList = function() {
+ return $('.note .js-task-list-container').taskList('enable');
+ };
+
+ Notes.prototype.updateTaskList = function() {
+ return $('form', this).submit();
+ };
+
+ Notes.prototype.updateNotesCount = function(updateCount) {
+ return this.notesCountBadge.text(parseInt(this.notesCountBadge.text()) + updateCount);
+ };
+
+ return Notes;
+
+})();
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
deleted file mode 100644
index d4de712f88c..00000000000
--- a/app/assets/javascripts/notes.js.coffee
+++ /dev/null
@@ -1,694 +0,0 @@
-#= require autosave
-#= require autosize
-#= require dropzone
-#= require dropzone_input
-#= require gfm_auto_complete
-#= require jquery.atwho
-#= require task_list
-
-class @Notes
- @interval: null
-
- constructor: (notes_url, note_ids, last_fetched_at, view) ->
- @notes_url = notes_url
- @note_ids = note_ids
- @last_fetched_at = last_fetched_at
- @view = view
- @noteable_url = document.URL
- @notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge")
- @basePollingInterval = 15000
- @maxPollingSteps = 4
-
- @cleanBinding()
- @addBinding()
- @setPollingInterval()
- @setupMainTargetNoteForm()
- @initTaskList()
-
- addBinding: ->
- # add note to UI after creation
- $(document).on "ajax:success", ".js-main-target-form", @addNote
- $(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote
-
- # catch note ajax errors
- $(document).on "ajax:error", ".js-main-target-form", @addNoteError
-
- # change note in UI after update
- $(document).on "ajax:success", "form.edit-note", @updateNote
-
- # Edit note link
- $(document).on "click", ".js-note-edit", @showEditForm
- $(document).on "click", ".note-edit-cancel", @cancelEdit
-
- # Reopen and close actions for Issue/MR combined with note form submit
- $(document).on "click", ".js-comment-button", @updateCloseButton
- $(document).on "keyup input", ".js-note-text", @updateTargetButtons
-
- # remove a note (in general)
- $(document).on "click", ".js-note-delete", @removeNote
-
- # delete note attachment
- $(document).on "click", ".js-note-attachment-delete", @removeAttachment
-
- # reset main target form after submit
- $(document).on "ajax:complete", ".js-main-target-form", @reenableTargetFormSubmitButton
- $(document).on "ajax:success", ".js-main-target-form", @resetMainTargetForm
-
- # reset main target form when clicking discard
- $(document).on "click", ".js-note-discard", @resetMainTargetForm
-
- # update the file name when an attachment is selected
- $(document).on "change", ".js-note-attachment-input", @updateFormAttachment
-
- # reply to diff/discussion notes
- $(document).on "click", ".js-discussion-reply-button", @replyToDiscussionNote
-
- # add diff note
- $(document).on "click", ".js-add-diff-note-button", @addDiffNote
-
- # hide diff note form
- $(document).on "click", ".js-close-discussion-note-form", @cancelDiscussionForm
-
- # fetch notes when tab becomes visible
- $(document).on "visibilitychange", @visibilityChange
-
- # when issue status changes, we need to refresh data
- $(document).on "issuable:change", @refresh
-
- # when a key is clicked on the notes
- $(document).on "keydown", ".js-note-text", @keydownNoteText
-
- cleanBinding: ->
- $(document).off "ajax:success", ".js-main-target-form"
- $(document).off "ajax:success", ".js-discussion-note-form"
- $(document).off "ajax:success", "form.edit-note"
- $(document).off "click", ".js-note-edit"
- $(document).off "click", ".note-edit-cancel"
- $(document).off "click", ".js-note-delete"
- $(document).off "click", ".js-note-attachment-delete"
- $(document).off "ajax:complete", ".js-main-target-form"
- $(document).off "ajax:success", ".js-main-target-form"
- $(document).off "click", ".js-discussion-reply-button"
- $(document).off "click", ".js-add-diff-note-button"
- $(document).off "visibilitychange"
- $(document).off "keyup", ".js-note-text"
- $(document).off "click", ".js-note-target-reopen"
- $(document).off "click", ".js-note-target-close"
- $(document).off "click", ".js-note-discard"
- $(document).off "keydown", ".js-note-text"
-
- $('.note .js-task-list-container').taskList('disable')
- $(document).off 'tasklist:changed', '.note .js-task-list-container'
-
- keydownNoteText: (e) =>
- return if isMetaKey e
-
- $textarea = $(e.target)
-
- # Edit previous note when UP arrow is hit
- switch e.which
- when 38
- return unless $textarea.val() is ''
-
- myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
- if myLastNote.length
- myLastNoteEditBtn = myLastNote.find('.js-note-edit')
- myLastNoteEditBtn.trigger('click', [true, myLastNote])
-
- # Cancel creating diff note or editing any note when ESCAPE is hit
- when 27
- discussionNoteForm = $textarea.closest('.js-discussion-note-form')
- if discussionNoteForm.length
- if $textarea.val() isnt ''
- return unless confirm('Are you sure you want to cancel creating this comment?')
-
- @removeDiscussionNoteForm(discussionNoteForm)
- return
-
- editNote = $textarea.closest('.note')
- if editNote.length
- originalText = $textarea.closest('form').data('original-note')
- newText = $textarea.val()
- if originalText isnt newText
- return unless confirm('Are you sure you want to cancel editing this comment?')
-
- @removeNoteEditForm(editNote)
-
-
- isMetaKey = (e) ->
- (e.metaKey or e.ctrlKey or e.altKey or e.shiftKey)
-
- initRefresh: ->
- clearInterval(Notes.interval)
- Notes.interval = setInterval =>
- @refresh()
- , @pollingInterval
-
- refresh: =>
- if not document.hidden and document.URL.indexOf(@noteable_url) is 0
- @getContent()
-
- getContent: ->
- return if @refreshing
-
- @refreshing = true
-
- $.ajax
- url: @notes_url
- data: "last_fetched_at=" + @last_fetched_at
- dataType: "json"
- success: (data) =>
- notes = data.notes
- @last_fetched_at = data.last_fetched_at
- @setPollingInterval(data.notes.length)
- $.each notes, (i, note) =>
- if note.discussion_html?
- @renderDiscussionNote(note)
- else
- @renderNote(note)
- .always () =>
- @refreshing = false
-
- ###
- Increase @pollingInterval up to 120 seconds on every function call,
- if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
- will reset to @basePollingInterval.
-
- Note: this function is used to gradually increase the polling interval
- if there aren't new notes coming from the server
- ###
- setPollingInterval: (shouldReset = true) ->
- nthInterval = @basePollingInterval * Math.pow(2, @maxPollingSteps - 1)
- if shouldReset
- @pollingInterval = @basePollingInterval
- else if @pollingInterval < nthInterval
- @pollingInterval *= 2
-
- @initRefresh()
-
- ###
- Render note in main comments area.
-
- Note: for rendering inline notes use renderDiscussionNote
- ###
- renderNote: (note) ->
- unless note.valid
- if note.award
- new Flash('You have already awarded this emoji!', 'alert')
- return
-
- if note.award
- votesBlock = $('.js-awards-block').eq 0
- gl.awardsHandler.addAwardToEmojiBar votesBlock, note.name
- gl.awardsHandler.scrollToAwards()
-
- # render note if it not present in loaded list
- # or skip if rendered
- else if @isNewNote(note)
- @note_ids.push(note.id)
-
- $notesList = $('ul.main-notes-list')
-
- $notesList
- .append(note.html)
- .syntaxHighlight()
-
- # Update datetime format on the recent note
- gl.utils.localTimeAgo($notesList.find("#note_#{note.id} .js-timeago"), false)
-
- @initTaskList()
- @updateNotesCount(1)
-
-
- ###
- Check if note does not exists on page
- ###
- isNewNote: (note) ->
- $.inArray(note.id, @note_ids) == -1
-
- isParallelView: ->
- @view == 'parallel'
-
- ###
- Render note in discussion area.
-
- Note: for rendering inline notes use renderDiscussionNote
- ###
- renderDiscussionNote: (note) ->
- return unless @isNewNote(note)
-
- @note_ids.push(note.id)
- form = $("#new-discussion-note-form-#{note.discussion_id}")
- if note.original_discussion_id? and form.length is 0
- form = $("#new-discussion-note-form-#{note.original_discussion_id}")
- row = form.closest("tr")
- note_html = $(note.html)
- note_html.syntaxHighlight()
-
- # is this the first note of discussion?
- discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
- if note.original_discussion_id? and discussionContainer.length is 0
- discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']")
- if discussionContainer.length is 0
- # insert the note and the reply button after the temp row
- row.after note.diff_discussion_html
-
- # remove the note (will be added again below)
- row.next().find(".note").remove()
-
- # Before that, the container didn't exist
- discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
-
- # Add note to 'Changes' page discussions
- discussionContainer.append note_html
-
- # Init discussion on 'Discussion' page if it is merge request page
- if $('body').attr('data-page').indexOf('projects:merge_request') is 0
- $('ul.main-notes-list')
- .append(note.discussion_html)
- .syntaxHighlight()
- else
- # append new note to all matching discussions
- discussionContainer.append note_html
-
- gl.utils.localTimeAgo($('.js-timeago', note_html), false)
-
- @updateNotesCount(1)
-
- ###
- Called in response the main target form has been successfully submitted.
-
- Removes any errors.
- Resets text and preview.
- Resets buttons.
- ###
- resetMainTargetForm: (e) =>
- form = $(".js-main-target-form")
-
- # remove validation errors
- form.find(".js-errors").remove()
-
- # reset text and preview
- form.find(".js-md-write-button").click()
- form.find(".js-note-text").val("").trigger "input"
-
- form.find(".js-note-text").data("autosave").reset()
-
- @updateTargetButtons(e)
-
- reenableTargetFormSubmitButton: ->
- form = $(".js-main-target-form")
-
- form.find(".js-note-text").trigger "input"
-
- ###
- Shows the main form and does some setup on it.
-
- Sets some hidden fields in the form.
- ###
- setupMainTargetNoteForm: ->
- # find the form
- form = $(".js-new-note-form")
-
- # Set a global clone of the form for later cloning
- @formClone = form.clone()
-
- # show the form
- @setupNoteForm(form)
-
- # fix classes
- form.removeClass "js-new-note-form"
- form.addClass "js-main-target-form"
-
- form.find("#note_line_code").remove()
- form.find("#note_position").remove()
- form.find("#note_type").remove()
-
- @parentTimeline = form.parents('.timeline')
-
- ###
- General note form setup.
-
- deactivates the submit button when text is empty
- hides the preview button when text is empty
- setup GFM auto complete
- show the form
- ###
- setupNoteForm: (form) ->
- new GLForm form
-
- textarea = form.find(".js-note-text")
-
- new Autosave textarea, [
- "Note"
- form.find("#note_noteable_type").val()
- form.find("#note_noteable_id").val()
- form.find("#note_commit_id").val()
- form.find("#note_type").val()
- form.find("#note_line_code").val()
- form.find("#note_position").val()
- ]
-
- ###
- Called in response to the new note form being submitted
-
- Adds new note to list.
- ###
- addNote: (xhr, note, status) =>
- @renderNote(note)
-
- addNoteError: (xhr, note, status) =>
- new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', @parentTimeline)
-
- ###
- Called in response to the new note form being submitted
-
- Adds new note to list.
- ###
- addDiscussionNote: (xhr, note, status) =>
- @renderDiscussionNote(note)
-
- # cleanup after successfully creating a diff/discussion note
- @removeDiscussionNoteForm($(xhr.target))
-
- ###
- Called in response to the edit note form being submitted
-
- Updates the current note field.
- ###
- updateNote: (_xhr, note, _status) =>
- # Convert returned HTML to a jQuery object so we can modify it further
- $html = $(note.html)
-
- gl.utils.localTimeAgo($('.js-timeago', $html))
-
- $html.syntaxHighlight()
- $html.find('.js-task-list-container').taskList('enable')
-
- # Find the note's `li` element by ID and replace it with the updated HTML
- $note_li = $('.note-row-' + note.id)
- $note_li.replaceWith($html)
-
- ###
- Called in response to clicking the edit note link
-
- Replaces the note text with the note edit form
- Adds a data attribute to the form with the original content of the note for cancellations
- ###
- showEditForm: (e, scrollTo, myLastNote) ->
- e.preventDefault()
- note = $(this).closest(".note")
- note.addClass "is-editting"
- form = note.find(".note-edit-form")
-
- form.addClass('current-note-edit-form')
-
- # Show the attachment delete link
- note.find(".js-note-attachment-delete").show()
-
- done = ($noteText) ->
- # Neat little trick to put the cursor at the end
- noteTextVal = $noteText.val()
- # Store the original note text in a data attribute to retrieve if a user cancels edit.
- form.find('form.edit-note').data 'original-note', noteTextVal
- $noteText.val('').val(noteTextVal);
-
- new GLForm form
- if scrollTo? and myLastNote?
- # scroll to the bottom
- # so the open of the last element doesn't make a jump
- $('html, body').scrollTop($(document).height());
- $('html, body').animate({
- scrollTop: myLastNote.offset().top - 150
- }, 500, ->
- $noteText = form.find(".js-note-text")
- $noteText.focus()
- done($noteText)
- );
- else
- $noteText = form.find('.js-note-text')
- $noteText.focus()
- done($noteText)
-
- ###
- Called in response to clicking the edit note link
-
- Hides edit form and restores the original note text to the editor textarea.
- ###
- cancelEdit: (e) =>
- e.preventDefault()
- note = $(e.target).closest('.note')
- @removeNoteEditForm(note)
-
- removeNoteEditForm: (note) ->
- form = note.find(".current-note-edit-form")
- note.removeClass "is-editting"
- form.removeClass("current-note-edit-form")
- # Replace markdown textarea text with original note text.
- form.find(".js-note-text").val(form.find('form.edit-note').data('original-note'))
-
- ###
- Called in response to deleting a note of any kind.
-
- Removes the actual note from view.
- Removes the whole discussion if the last note is being removed.
- ###
- removeNote: (e) =>
- noteId = $(e.currentTarget)
- .closest(".note")
- .attr("id")
-
- # A same note appears in the "Discussion" and in the "Changes" tab, we have
- # to remove all. Using $(".note[id='noteId']") ensure we get all the notes,
- # where $("#noteId") would return only one.
- $(".note[id='#{noteId}']").each (i, el) =>
- note = $(el)
- notes = note.closest(".notes")
-
- # check if this is the last note for this line
- if notes.find(".note").length is 1
-
- # "Discussions" tab
- notes.closest(".timeline-entry").remove()
-
- # "Changes" tab / commit view
- notes.closest("tr").remove()
-
- note.remove()
-
- # Decrement the "Discussions" counter only once
- @updateNotesCount(-1)
-
- ###
- Called in response to clicking the delete attachment link
-
- Removes the attachment wrapper view, including image tag if it exists
- Resets the note editing form
- ###
- removeAttachment: ->
- note = $(this).closest(".note")
- note.find(".note-attachment").remove()
- note.find(".note-body > .note-text").show()
- note.find(".note-header").show()
- note.find(".current-note-edit-form").remove()
-
- ###
- Called when clicking on the "reply" button for a diff line.
-
- Shows the note form below the notes.
- ###
- replyToDiscussionNote: (e) =>
- form = @formClone.clone()
- replyLink = $(e.target).closest(".js-discussion-reply-button")
- replyLink.hide()
-
- # insert the form after the button
- replyLink.after form
-
- # show the form
- @setupDiscussionNoteForm(replyLink, form)
-
- ###
- Shows the diff or discussion form and does some setup on it.
-
- Sets some hidden fields in the form.
-
- Note: dataHolder must have the "discussionId", "lineCode", "noteableType"
- and "noteableId" data attributes set.
- ###
- setupDiscussionNoteForm: (dataHolder, form) =>
- # setup note target
- form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}"
- form.attr "data-line-code", dataHolder.data("lineCode")
- form.find("#note_type").val dataHolder.data("noteType")
- form.find("#line_type").val dataHolder.data("lineType")
- form.find("#note_commit_id").val dataHolder.data("commitId")
- form.find("#note_line_code").val dataHolder.data("lineCode")
- form.find("#note_position").val dataHolder.attr("data-position")
- form.find("#note_noteable_type").val dataHolder.data("noteableType")
- form.find("#note_noteable_id").val dataHolder.data("noteableId")
- form.find('.js-note-discard')
- .show()
- .removeClass('js-note-discard')
- .addClass('js-close-discussion-note-form')
- .text(form.find('.js-close-discussion-note-form').data('cancel-text'))
- @setupNoteForm form
- form.find(".js-note-text").focus()
- form
- .removeClass('js-main-target-form')
- .addClass("discussion-form js-discussion-note-form")
-
- ###
- Called when clicking on the "add a comment" button on the side of a diff line.
-
- Inserts a temporary row for the form below the line.
- Sets up the form and shows it.
- ###
- addDiffNote: (e) =>
- e.preventDefault()
- $link = $(e.currentTarget)
- row = $link.closest("tr")
- nextRow = row.next()
- hasNotes = nextRow.is(".notes_holder")
- addForm = false
- targetContent = ".notes_content"
- rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"></td></tr>"
-
- # In parallel view, look inside the correct left/right pane
- if @isParallelView()
- lineType = $link.data("lineType")
- targetContent += "." + lineType
- rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\"></td><td class=\"notes_content parallel old\"></td><td class=\"notes_line\"></td><td class=\"notes_content parallel new\"></td></tr>"
-
- if hasNotes
- notesContent = nextRow.find(targetContent)
- if notesContent.length
- replyButton = notesContent.find(".js-discussion-reply-button:visible")
- if replyButton.length
- e.target = replyButton[0]
- $.proxy(@replyToDiscussionNote, replyButton[0], e).call()
- else
- # In parallel view, the form may not be present in one of the panes
- noteForm = notesContent.find(".js-discussion-note-form")
- if noteForm.length == 0
- addForm = true
- else
- # add a notes row and insert the form
- row.after rowCssToAdd
- addForm = true
-
- if addForm
- newForm = @formClone.clone()
- newForm.appendTo row.next().find(targetContent)
-
- # show the form
- @setupDiscussionNoteForm $link, newForm
-
- ###
- Called in response to "cancel" on a diff note form.
-
- Shows the reply button again.
- Removes the form and if necessary it's temporary row.
- ###
- removeDiscussionNoteForm: (form)->
- row = form.closest("tr")
-
- glForm = form.data 'gl-form'
- glForm.destroy()
-
- form.find(".js-note-text").data("autosave").reset()
-
- # show the reply button (will only work for replies)
- form.prev(".js-discussion-reply-button").show()
- if row.is(".js-temp-notes-holder")
- # remove temporary row for diff lines
- row.remove()
- else
- # only remove the form
- form.remove()
-
- cancelDiscussionForm: (e) =>
- e.preventDefault()
- form = $(e.target).closest(".js-discussion-note-form")
- @removeDiscussionNoteForm(form)
-
- ###
- Called after an attachment file has been selected.
-
- Updates the file name for the selected attachment.
- ###
- updateFormAttachment: ->
- form = $(this).closest("form")
-
- # get only the basename
- filename = $(this).val().replace(/^.*[\\\/]/, "")
- form.find(".js-attachment-filename").text filename
-
- ###
- Called when the tab visibility changes
- ###
- visibilityChange: =>
- @refresh()
-
- updateCloseButton: (e) =>
- textarea = $(e.target)
- form = textarea.parents('form')
- closebtn = form.find('.js-note-target-close')
- closebtn.text(closebtn.data('original-text'))
-
- updateTargetButtons: (e) =>
- textarea = $(e.target)
- form = textarea.parents('form')
- reopenbtn = form.find('.js-note-target-reopen')
- closebtn = form.find('.js-note-target-close')
- discardbtn = form.find('.js-note-discard')
-
- if textarea.val().trim().length > 0
- reopentext = reopenbtn.data('alternative-text')
- closetext = closebtn.data('alternative-text')
-
- if reopenbtn.text() isnt reopentext
- reopenbtn.text(reopentext)
-
- if closebtn.text() isnt closetext
- closebtn.text(closetext)
-
- if reopenbtn.is(':not(.btn-comment-and-reopen)')
- reopenbtn.addClass('btn-comment-and-reopen')
-
- if closebtn.is(':not(.btn-comment-and-close)')
- closebtn.addClass('btn-comment-and-close')
-
- if discardbtn.is(':hidden')
- discardbtn.show()
- else
- reopentext = reopenbtn.data('original-text')
- closetext = closebtn.data('original-text')
-
- if reopenbtn.text() isnt reopentext
- reopenbtn.text(reopentext)
-
- if closebtn.text() isnt closetext
- closebtn.text(closetext)
-
- if reopenbtn.is('.btn-comment-and-reopen')
- reopenbtn.removeClass('btn-comment-and-reopen')
-
- if closebtn.is('.btn-comment-and-close')
- closebtn.removeClass('btn-comment-and-close')
-
- if discardbtn.is(':visible')
- discardbtn.hide()
-
- initTaskList: ->
- @enableTaskList()
- $(document).on 'tasklist:changed', '.note .js-task-list-container', @updateTaskList
-
- enableTaskList: ->
- $('.note .js-task-list-container').taskList('enable')
-
- updateTaskList: ->
- $('form', this).submit()
-
- updateNotesCount: (updateCount) ->
- @notesCountBadge.text(parseInt(@notesCountBadge.text()) + updateCount)
diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js
new file mode 100644
index 00000000000..a08bb596d5d
--- /dev/null
+++ b/app/assets/javascripts/notifications_dropdown.js
@@ -0,0 +1,27 @@
+this.NotificationsDropdown = (function() {
+ function NotificationsDropdown() {
+ $(document).off('click', '.update-notification').on('click', '.update-notification', function(e) {
+ var form, label, notificationLevel;
+ e.preventDefault();
+ if ($(this).is('.is-active') && $(this).data('notification-level') === 'custom') {
+ return;
+ }
+ notificationLevel = $(this).data('notification-level');
+ label = $(this).data('notification-title');
+ form = $(this).parents('.notification-form:first');
+ form.find('.js-notification-loading').toggleClass('fa-bell fa-spin fa-spinner');
+ form.find('#notification_setting_level').val(notificationLevel);
+ return form.submit();
+ });
+ $(document).off('ajax:success', '.notification-form').on('ajax:success', '.notification-form', function(e, data) {
+ if (data.saved) {
+ return $(e.currentTarget).closest('.notification-dropdown').replaceWith(data.html);
+ } else {
+ return new Flash('Failed to save new settings', 'alert');
+ }
+ });
+ }
+
+ return NotificationsDropdown;
+
+})();
diff --git a/app/assets/javascripts/notifications_dropdown.js.coffee b/app/assets/javascripts/notifications_dropdown.js.coffee
deleted file mode 100644
index 0bbd082c156..00000000000
--- a/app/assets/javascripts/notifications_dropdown.js.coffee
+++ /dev/null
@@ -1,25 +0,0 @@
-class @NotificationsDropdown
- constructor: ->
- $(document)
- .off 'click', '.update-notification'
- .on 'click', '.update-notification', (e) ->
- e.preventDefault()
-
- return if $(this).is('.is-active') and $(this).data('notification-level') is 'custom'
-
- notificationLevel = $(@).data 'notification-level'
- label = $(@).data 'notification-title'
- form = $(this).parents('.notification-form:first')
- form.find('.js-notification-loading').toggleClass 'fa-bell fa-spin fa-spinner'
- form.find('#notification_setting_level').val(notificationLevel)
- form.submit()
-
- $(document)
- .off 'ajax:success', '.notification-form'
- .on 'ajax:success', '.notification-form', (e, data) ->
- if data.saved
- $(e.currentTarget)
- .closest('.notification-dropdown')
- .replaceWith(data.html)
- else
- new Flash('Failed to save new settings', 'alert')
diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js
new file mode 100644
index 00000000000..1463a9cd0b4
--- /dev/null
+++ b/app/assets/javascripts/notifications_form.js
@@ -0,0 +1,55 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.NotificationsForm = (function() {
+ function NotificationsForm() {
+ this.toggleCheckbox = bind(this.toggleCheckbox, this);
+ this.removeEventListeners();
+ this.initEventListeners();
+ }
+
+ NotificationsForm.prototype.removeEventListeners = function() {
+ return $(document).off('change', '.js-custom-notification-event');
+ };
+
+ NotificationsForm.prototype.initEventListeners = function() {
+ return $(document).on('change', '.js-custom-notification-event', this.toggleCheckbox);
+ };
+
+ NotificationsForm.prototype.toggleCheckbox = function(e) {
+ var $checkbox, $parent;
+ $checkbox = $(e.currentTarget);
+ $parent = $checkbox.closest('.checkbox');
+ return this.saveEvent($checkbox, $parent);
+ };
+
+ NotificationsForm.prototype.showCheckboxLoadingSpinner = function($parent) {
+ return $parent.addClass('is-loading').find('.custom-notification-event-loading').removeClass('fa-check').addClass('fa-spin fa-spinner').removeClass('is-done');
+ };
+
+ NotificationsForm.prototype.saveEvent = function($checkbox, $parent) {
+ var form;
+ form = $parent.parents('form:first');
+ return $.ajax({
+ url: form.attr('action'),
+ method: form.attr('method'),
+ dataType: 'json',
+ data: form.serialize(),
+ beforeSend: (function(_this) {
+ return function() {
+ return _this.showCheckboxLoadingSpinner($parent);
+ };
+ })(this)
+ }).done(function(data) {
+ $checkbox.enable();
+ if (data.saved) {
+ $parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
+ return setTimeout(function() {
+ return $parent.removeClass('is-loading').find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
+ }, 2000);
+ }
+ });
+ };
+
+ return NotificationsForm;
+
+})();
diff --git a/app/assets/javascripts/notifications_form.js.coffee b/app/assets/javascripts/notifications_form.js.coffee
deleted file mode 100644
index 3432428702a..00000000000
--- a/app/assets/javascripts/notifications_form.js.coffee
+++ /dev/null
@@ -1,49 +0,0 @@
-class @NotificationsForm
- constructor: ->
- @removeEventListeners()
- @initEventListeners()
-
- removeEventListeners: ->
- $(document).off 'change', '.js-custom-notification-event'
-
- initEventListeners: ->
- $(document).on 'change', '.js-custom-notification-event', @toggleCheckbox
-
- toggleCheckbox: (e) =>
- $checkbox = $(e.currentTarget)
- $parent = $checkbox.closest('.checkbox')
- @saveEvent($checkbox, $parent)
-
- showCheckboxLoadingSpinner: ($parent) ->
- $parent
- .addClass 'is-loading'
- .find '.custom-notification-event-loading'
- .removeClass 'fa-check'
- .addClass 'fa-spin fa-spinner'
- .removeClass 'is-done'
-
- saveEvent: ($checkbox, $parent) ->
- form = $parent.parents('form:first')
-
- $.ajax(
- url: form.attr('action')
- method: form.attr('method')
- dataType: 'json'
- data: form.serialize()
-
- beforeSend: =>
- @showCheckboxLoadingSpinner($parent)
- ).done (data) ->
- $checkbox.enable()
-
- if data.saved
- $parent
- .find '.custom-notification-event-loading'
- .toggleClass 'fa-spin fa-spinner fa-check is-done'
-
- setTimeout(->
- $parent
- .removeClass 'is-loading'
- .find '.custom-notification-event-loading'
- .toggleClass 'fa-spin fa-spinner fa-check is-done'
- , 2000)
diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js
new file mode 100644
index 00000000000..2c6335d6073
--- /dev/null
+++ b/app/assets/javascripts/pager.js
@@ -0,0 +1,60 @@
+this.Pager = {
+ init: function(limit, preload, disable, callback) {
+ this.limit = limit != null ? limit : 0;
+ this.disable = disable != null ? disable : false;
+ this.callback = callback != null ? callback : $.noop;
+ this.loading = $('.loading').first();
+ if (preload) {
+ this.offset = 0;
+ this.getOld();
+ } else {
+ this.offset = this.limit;
+ }
+ return this.initLoadMore();
+ },
+ getOld: function() {
+ this.loading.show();
+ return $.ajax({
+ type: "GET",
+ url: $(".content_list").data('href') || location.href,
+ data: "limit=" + this.limit + "&offset=" + this.offset,
+ complete: (function(_this) {
+ return function() {
+ return _this.loading.hide();
+ };
+ })(this),
+ success: function(data) {
+ Pager.append(data.count, data.html);
+ return Pager.callback();
+ },
+ dataType: "json"
+ });
+ },
+ append: function(count, html) {
+ $(".content_list").append(html);
+ if (count > 0) {
+ return this.offset += count;
+ } else {
+ return this.disable = true;
+ }
+ },
+ initLoadMore: function() {
+ $(document).unbind('scroll');
+ return $(document).endlessScroll({
+ bottomPixels: 400,
+ fireDelay: 1000,
+ fireOnce: true,
+ ceaseFire: function() {
+ return Pager.disable;
+ },
+ callback: (function(_this) {
+ return function(i) {
+ if (!_this.loading.is(':visible')) {
+ _this.loading.show();
+ return Pager.getOld();
+ }
+ };
+ })(this)
+ });
+ }
+};
diff --git a/app/assets/javascripts/pager.js.coffee b/app/assets/javascripts/pager.js.coffee
deleted file mode 100644
index 8049c5c30e2..00000000000
--- a/app/assets/javascripts/pager.js.coffee
+++ /dev/null
@@ -1,44 +0,0 @@
-@Pager =
- init: (@limit = 0, preload, @disable = false, @callback = $.noop) ->
- @loading = $('.loading').first()
-
- if preload
- @offset = 0
- @getOld()
- else
- @offset = @limit
- @initLoadMore()
-
- getOld: ->
- @loading.show()
- $.ajax
- type: "GET"
- url: $(".content_list").data('href') || location.href
- data: "limit=" + @limit + "&offset=" + @offset
- complete: =>
- @loading.hide()
- success: (data) ->
- Pager.append(data.count, data.html)
- Pager.callback()
- dataType: "json"
-
- append: (count, html) ->
- $(".content_list").append html
- if count > 0
- @offset += count
- else
- @disable = true
-
- initLoadMore: ->
- $(document).unbind('scroll')
- $(document).endlessScroll
- bottomPixels: 400
- fireDelay: 1000
- fireOnce: true
- ceaseFire: ->
- Pager.disable
-
- callback: (i) =>
- unless @loading.is(':visible')
- @loading.show()
- Pager.getOld()
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
new file mode 100644
index 00000000000..359aed99484
--- /dev/null
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -0,0 +1,166 @@
+var GitLabCrop,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+GitLabCrop = (function() {
+ var FILENAMEREGEX;
+
+ FILENAMEREGEX = /^.*[\\\/]/;
+
+ function GitLabCrop(input, opts) {
+ var ref, ref1, ref2, ref3, ref4;
+ if (opts == null) {
+ opts = {};
+ }
+ this.onUploadImageBtnClick = bind(this.onUploadImageBtnClick, this);
+ this.onModalHide = bind(this.onModalHide, this);
+ this.onModalShow = bind(this.onModalShow, this);
+ this.onPickImageClick = bind(this.onPickImageClick, this);
+ this.fileInput = $(input);
+ this.fileInput.attr('name', (this.fileInput.attr('name')) + "-trigger").attr('id', (this.fileInput.attr('id')) + "-trigger");
+ this.exportWidth = (ref = opts.exportWidth) != null ? ref : 200, this.exportHeight = (ref1 = opts.exportHeight) != null ? ref1 : 200, this.cropBoxWidth = (ref2 = opts.cropBoxWidth) != null ? ref2 : 200, this.cropBoxHeight = (ref3 = opts.cropBoxHeight) != null ? ref3 : 200, this.form = (ref4 = opts.form) != null ? ref4 : this.fileInput.parents('form'), this.filename = opts.filename, this.previewImage = opts.previewImage, this.modalCrop = opts.modalCrop, this.pickImageEl = opts.pickImageEl, this.uploadImageBtn = opts.uploadImageBtn, this.modalCropImg = opts.modalCropImg;
+ this.filename = this.getElement(this.filename);
+ this.previewImage = this.getElement(this.previewImage);
+ this.pickImageEl = this.getElement(this.pickImageEl);
+ this.modalCrop = _.isString(this.modalCrop) ? $(this.modalCrop) : this.modalCrop;
+ this.uploadImageBtn = _.isString(this.uploadImageBtn) ? $(this.uploadImageBtn) : this.uploadImageBtn;
+ this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg;
+ this.cropActionsBtn = this.modalCrop.find('[data-method]');
+ this.bindEvents();
+ }
+
+ GitLabCrop.prototype.getElement = function(selector) {
+ return $(selector, this.form);
+ };
+
+ GitLabCrop.prototype.bindEvents = function() {
+ var _this;
+ _this = this;
+ this.fileInput.on('change', function(e) {
+ return _this.onFileInputChange(e, this);
+ });
+ this.pickImageEl.on('click', this.onPickImageClick);
+ this.modalCrop.on('shown.bs.modal', this.onModalShow);
+ this.modalCrop.on('hidden.bs.modal', this.onModalHide);
+ this.uploadImageBtn.on('click', this.onUploadImageBtnClick);
+ this.cropActionsBtn.on('click', function(e) {
+ var btn;
+ btn = this;
+ return _this.onActionBtnClick(btn);
+ });
+ return this.croppedImageBlob = null;
+ };
+
+ GitLabCrop.prototype.onPickImageClick = function() {
+ return this.fileInput.trigger('click');
+ };
+
+ GitLabCrop.prototype.onModalShow = function() {
+ var _this;
+ _this = this;
+ return this.modalCropImg.cropper({
+ viewMode: 1,
+ center: false,
+ aspectRatio: 1,
+ modal: true,
+ scalable: false,
+ rotatable: false,
+ zoomable: true,
+ dragMode: 'move',
+ guides: false,
+ zoomOnTouch: false,
+ zoomOnWheel: false,
+ cropBoxMovable: false,
+ cropBoxResizable: false,
+ toggleDragModeOnDblclick: false,
+ built: function() {
+ var $image, container, cropBoxHeight, cropBoxWidth;
+ $image = $(this);
+ container = $image.cropper('getContainerData');
+ cropBoxWidth = _this.cropBoxWidth;
+ cropBoxHeight = _this.cropBoxHeight;
+ return $image.cropper('setCropBoxData', {
+ width: cropBoxWidth,
+ height: cropBoxHeight,
+ left: (container.width - cropBoxWidth) / 2,
+ top: (container.height - cropBoxHeight) / 2
+ });
+ }
+ });
+ };
+
+ GitLabCrop.prototype.onModalHide = function() {
+ return this.modalCropImg.attr('src', '').cropper('destroy');
+ };
+
+ GitLabCrop.prototype.onUploadImageBtnClick = function(e) {
+ e.preventDefault();
+ this.setBlob();
+ this.setPreview();
+ this.modalCrop.modal('hide');
+ return this.fileInput.val('');
+ };
+
+ GitLabCrop.prototype.onActionBtnClick = function(btn) {
+ var data, result;
+ data = $(btn).data();
+ if (this.modalCropImg.data('cropper') && data.method) {
+ return result = this.modalCropImg.cropper(data.method, data.option);
+ }
+ };
+
+ GitLabCrop.prototype.onFileInputChange = function(e, input) {
+ return this.readFile(input);
+ };
+
+ GitLabCrop.prototype.readFile = function(input) {
+ var _this, reader;
+ _this = this;
+ reader = new FileReader;
+ reader.onload = function() {
+ _this.modalCropImg.attr('src', reader.result);
+ return _this.modalCrop.modal('show');
+ };
+ return reader.readAsDataURL(input.files[0]);
+ };
+
+ GitLabCrop.prototype.dataURLtoBlob = function(dataURL) {
+ var array, binary, i, k, len, v;
+ binary = atob(dataURL.split(',')[1]);
+ array = [];
+ for (k = i = 0, len = binary.length; i < len; k = ++i) {
+ v = binary[k];
+ array.push(binary.charCodeAt(k));
+ }
+ return new Blob([new Uint8Array(array)], {
+ type: 'image/png'
+ });
+ };
+
+ GitLabCrop.prototype.setPreview = function() {
+ var filename;
+ this.previewImage.attr('src', this.dataURL);
+ filename = this.fileInput.val().replace(FILENAMEREGEX, '');
+ return this.filename.text(filename);
+ };
+
+ GitLabCrop.prototype.setBlob = function() {
+ this.dataURL = this.modalCropImg.cropper('getCroppedCanvas', {
+ width: 200,
+ height: 200
+ }).toDataURL('image/png');
+ return this.croppedImageBlob = this.dataURLtoBlob(this.dataURL);
+ };
+
+ GitLabCrop.prototype.getBlob = function() {
+ return this.croppedImageBlob;
+ };
+
+ return GitLabCrop;
+
+})();
+
+$.fn.glCrop = function(opts) {
+ return this.each(function() {
+ return $(this).data('glcrop', new GitLabCrop(this, opts));
+ });
+};
diff --git a/app/assets/javascripts/profile/gl_crop.js.coffee b/app/assets/javascripts/profile/gl_crop.js.coffee
deleted file mode 100644
index df9bfdfa6cc..00000000000
--- a/app/assets/javascripts/profile/gl_crop.js.coffee
+++ /dev/null
@@ -1,152 +0,0 @@
-class GitLabCrop
- # Matches everything but the file name
- FILENAMEREGEX = /^.*[\\\/]/
-
- constructor: (input, opts = {}) ->
- @fileInput = $(input)
-
- # We should rename to avoid spec to fail
- # Form will submit the proper input filed with a file using FormData
- @fileInput
- .attr('name', "#{@fileInput.attr('name')}-trigger")
- .attr('id', "#{@fileInput.attr('id')}-trigger")
-
- # Set defaults
- {
- @exportWidth = 200
- @exportHeight = 200
- @cropBoxWidth = 200
- @cropBoxHeight = 200
- @form = @fileInput.parents('form')
-
- # Required params
- @filename
- @previewImage
- @modalCrop
- @pickImageEl
- @uploadImageBtn
- @modalCropImg
- } = opts
-
- # Ensure needed elements are jquery objects
- # If selector is provided we will convert them to a jQuery Object
- @filename = @getElement(@filename)
- @previewImage = @getElement(@previewImage)
- @pickImageEl = @getElement(@pickImageEl)
-
- # Modal elements usually are outside the @form element
- @modalCrop = if _.isString(@modalCrop) then $(@modalCrop) else @modalCrop
- @uploadImageBtn = if _.isString(@uploadImageBtn) then $(@uploadImageBtn) else @uploadImageBtn
- @modalCropImg = if _.isString(@modalCropImg) then $(@modalCropImg) else @modalCropImg
-
- @cropActionsBtn = @modalCrop.find('[data-method]')
-
- @bindEvents()
-
- getElement: (selector) ->
- $(selector, @form)
-
- bindEvents: ->
- _this = @
- @fileInput.on 'change', (e) ->
- _this.onFileInputChange(e, @)
-
- @pickImageEl.on 'click', @onPickImageClick
- @modalCrop.on 'shown.bs.modal', @onModalShow
- @modalCrop.on 'hidden.bs.modal', @onModalHide
- @uploadImageBtn.on 'click', @onUploadImageBtnClick
- @cropActionsBtn.on 'click', (e) ->
- btn = @
- _this.onActionBtnClick(btn)
- @croppedImageBlob = null
-
- onPickImageClick: =>
- @fileInput.trigger('click')
-
- onModalShow: =>
- _this = @
- @modalCropImg.cropper(
- viewMode: 1
- center: false
- aspectRatio: 1
- modal: true
- scalable: false
- rotatable: false
- zoomable: true
- dragMode: 'move'
- guides: false
- zoomOnTouch: false
- zoomOnWheel: false
- cropBoxMovable: false
- cropBoxResizable: false
- toggleDragModeOnDblclick: false
- built: ->
- $image = $(@)
- container = $image.cropper 'getContainerData'
- cropBoxWidth = _this.cropBoxWidth;
- cropBoxHeight = _this.cropBoxHeight;
-
- $image.cropper('setCropBoxData',
- width: cropBoxWidth,
- height: cropBoxHeight,
- left: (container.width - cropBoxWidth) / 2,
- top: (container.height - cropBoxHeight) / 2
- )
- )
-
-
- onModalHide: =>
- @modalCropImg
- .attr('src', '') # Remove attached image
- .cropper('destroy') # Destroy cropper instance
-
- onUploadImageBtnClick: (e) =>
- e.preventDefault()
- @setBlob()
- @setPreview()
- @modalCrop.modal('hide')
- @fileInput.val('')
-
- onActionBtnClick: (btn) ->
- data = $(btn).data()
-
- if @modalCropImg.data('cropper') && data.method
- result = @modalCropImg.cropper data.method, data.option
-
- onFileInputChange: (e, input) ->
- @readFile(input)
-
- readFile: (input) ->
- _this = @
- reader = new FileReader
- reader.onload = ->
- _this.modalCropImg.attr('src', reader.result)
- _this.modalCrop.modal('show')
-
- reader.readAsDataURL(input.files[0])
-
- dataURLtoBlob: (dataURL) ->
- binary = atob(dataURL.split(',')[1])
- array = []
- for v, k in binary
- array.push(binary.charCodeAt(k))
- new Blob([new Uint8Array(array)], type: 'image/png')
-
- setPreview: ->
- @previewImage.attr('src', @dataURL)
- filename = @fileInput.val().replace(FILENAMEREGEX, '')
- @filename.text(filename)
-
- setBlob: ->
- @dataURL = @modalCropImg.cropper('getCroppedCanvas',
- width: 200
- height: 200
- ).toDataURL('image/png')
- @croppedImageBlob = @dataURLtoBlob(@dataURL)
-
- getBlob: ->
- @croppedImageBlob
-
-$.fn.glCrop = (opts) ->
- return @.each ->
- $(@).data('glcrop', new GitLabCrop(@, opts))
diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js
new file mode 100644
index 00000000000..96731d15e8a
--- /dev/null
+++ b/app/assets/javascripts/profile/profile.js
@@ -0,0 +1,99 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.Profile = (function() {
+ function Profile(opts) {
+ var cropOpts, ref;
+ if (opts == null) {
+ opts = {};
+ }
+ this.onSubmitForm = bind(this.onSubmitForm, this);
+ this.form = (ref = opts.form) != null ? ref : $('.edit-user');
+ $('.js-preferences-form').on('change.preference', 'input[type=radio]', function() {
+ return $(this).parents('form').submit();
+ });
+ $('#user_notification_email').on('change', function() {
+ return $(this).parents('form').submit();
+ });
+ $('.update-username').on('ajax:before', function() {
+ $('.loading-username').show();
+ $(this).find('.update-success').hide();
+ return $(this).find('.update-failed').hide();
+ });
+ $('.update-username').on('ajax:complete', function() {
+ $('.loading-username').hide();
+ $(this).find('.btn-save').enable();
+ return $(this).find('.loading-gif').hide();
+ });
+ $('.update-notifications').on('ajax:success', function(e, data) {
+ if (data.saved) {
+ return new Flash("Notification settings saved", "notice");
+ } else {
+ return new Flash("Failed to save new settings", "alert");
+ }
+ });
+ this.bindEvents();
+ cropOpts = {
+ filename: '.js-avatar-filename',
+ previewImage: '.avatar-image .avatar',
+ modalCrop: '.modal-profile-crop',
+ pickImageEl: '.js-choose-user-avatar-button',
+ uploadImageBtn: '.js-upload-user-avatar',
+ modalCropImg: '.modal-profile-crop-image'
+ };
+ this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop');
+ }
+
+ Profile.prototype.bindEvents = function() {
+ return this.form.on('submit', this.onSubmitForm);
+ };
+
+ Profile.prototype.onSubmitForm = function(e) {
+ e.preventDefault();
+ return this.saveForm();
+ };
+
+ Profile.prototype.saveForm = function() {
+ var avatarBlob, formData, self;
+ self = this;
+ formData = new FormData(this.form[0]);
+ avatarBlob = this.avatarGlCrop.getBlob();
+ if (avatarBlob != null) {
+ formData.append('user[avatar]', avatarBlob, 'avatar.png');
+ }
+ return $.ajax({
+ url: this.form.attr('action'),
+ type: this.form.attr('method'),
+ data: formData,
+ dataType: "json",
+ processData: false,
+ contentType: false,
+ success: function(response) {
+ return new Flash(response.message, 'notice');
+ },
+ error: function(jqXHR) {
+ return new Flash(jqXHR.responseJSON.message, 'alert');
+ },
+ complete: function() {
+ window.scrollTo(0, 0);
+ return self.form.find(':input[disabled]').enable();
+ }
+ });
+ };
+
+ return Profile;
+
+})();
+
+$(function() {
+ $(document).on('focusout.ssh_key', '#key_key', function() {
+ var $title, comment;
+ $title = $('#key_title');
+ comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/);
+ if (comment && comment.length > 1 && $title.val() === '') {
+ return $title.val(comment[1]).change();
+ }
+ });
+ if (gl.utils.getPagePath() === 'profiles') {
+ return new Profile();
+ }
+});
diff --git a/app/assets/javascripts/profile/profile.js.coffee b/app/assets/javascripts/profile/profile.js.coffee
deleted file mode 100644
index f3b05f2c646..00000000000
--- a/app/assets/javascripts/profile/profile.js.coffee
+++ /dev/null
@@ -1,83 +0,0 @@
-class @Profile
- constructor: (opts = {}) ->
- {
- @form = $('.edit-user')
- } = opts
-
- # Automatically submit the Preferences form when any of its radio buttons change
- $('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
- $(this).parents('form').submit()
-
- # Automatically submit email form when it changes
- $('#user_notification_email').on 'change', ->
- $(this).parents('form').submit()
-
- $('.update-username').on 'ajax:before', ->
- $('.loading-username').show()
- $(this).find('.update-success').hide()
- $(this).find('.update-failed').hide()
-
- $('.update-username').on 'ajax:complete', ->
- $('.loading-username').hide()
- $(this).find('.btn-save').enable()
- $(this).find('.loading-gif').hide()
-
- $('.update-notifications').on 'ajax:success', (e, data) ->
- if data.saved
- new Flash("Notification settings saved", "notice")
- else
- new Flash("Failed to save new settings", "alert")
-
- @bindEvents()
-
- cropOpts =
- filename: '.js-avatar-filename'
- previewImage: '.avatar-image .avatar'
- modalCrop: '.modal-profile-crop'
- pickImageEl: '.js-choose-user-avatar-button'
- uploadImageBtn: '.js-upload-user-avatar'
- modalCropImg: '.modal-profile-crop-image'
-
- @avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data 'glcrop'
-
- bindEvents: ->
- @form.on 'submit', @onSubmitForm
-
- onSubmitForm: (e) =>
- e.preventDefault()
- @saveForm()
-
- saveForm: ->
- self = @
- formData = new FormData(@form[0])
-
- avatarBlob = @avatarGlCrop.getBlob()
- formData.append('user[avatar]', avatarBlob, 'avatar.png') if avatarBlob?
-
- $.ajax
- url: @form.attr('action')
- type: @form.attr('method')
- data: formData
- dataType: "json"
- processData: false
- contentType: false
- success: (response) ->
- new Flash(response.message, 'notice')
- error: (jqXHR) ->
- new Flash(jqXHR.responseJSON.message, 'alert')
- complete: ->
- window.scrollTo 0, 0
- # Enable submit button after requests ends
- self.form.find(':input[disabled]').enable()
-
-$ ->
- # Extract the SSH Key title from its comment
- $(document).on 'focusout.ssh_key', '#key_key', ->
- $title = $('#key_title')
- comment = $(@).val().match(/^\S+ \S+ (.+)\n?$/)
-
- if comment && comment.length > 1 && $title.val() == ''
- $title.val(comment[1]).change()
-
- if gl.utils.getPagePath() == 'profiles'
- new Profile()
diff --git a/app/assets/javascripts/profile/profile_bundle.js b/app/assets/javascripts/profile/profile_bundle.js
new file mode 100644
index 00000000000..334007d6b38
--- /dev/null
+++ b/app/assets/javascripts/profile/profile_bundle.js
@@ -0,0 +1,3 @@
+
+/*= require_tree . */
+
diff --git a/app/assets/javascripts/profile/profile_bundle.js.coffee b/app/assets/javascripts/profile/profile_bundle.js.coffee
deleted file mode 100644
index 91cacfece46..00000000000
--- a/app/assets/javascripts/profile/profile_bundle.js.coffee
+++ /dev/null
@@ -1,2 +0,0 @@
-#
-#= require_tree .
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
new file mode 100644
index 00000000000..83ce8a8a9a4
--- /dev/null
+++ b/app/assets/javascripts/project.js
@@ -0,0 +1,100 @@
+this.Project = (function() {
+ function Project() {
+ $('ul.clone-options-dropdown a').click(function() {
+ var url;
+ if ($(this).hasClass('active')) {
+ return;
+ }
+ $('.active').not($(this)).removeClass('active');
+ $(this).toggleClass('active');
+ url = $("#project_clone").val();
+ $('#project_clone').val(url);
+ return $('.clone').text(url);
+ });
+ this.initRefSwitcher();
+ $('.project-refs-select').on('change', function() {
+ return $(this).parents('form').submit();
+ });
+ $('.hide-no-ssh-message').on('click', function(e) {
+ var path;
+ path = '/';
+ $.cookie('hide_no_ssh_message', 'false', {
+ path: path
+ });
+ $(this).parents('.no-ssh-key-message').remove();
+ return e.preventDefault();
+ });
+ $('.hide-no-password-message').on('click', function(e) {
+ var path;
+ path = '/';
+ $.cookie('hide_no_password_message', 'false', {
+ path: path
+ });
+ $(this).parents('.no-password-message').remove();
+ return e.preventDefault();
+ });
+ this.projectSelectDropdown();
+ }
+
+ Project.prototype.projectSelectDropdown = function() {
+ new ProjectSelect();
+ $('.project-item-select').on('click', (function(_this) {
+ return function(e) {
+ return _this.changeProject($(e.currentTarget).val());
+ };
+ })(this));
+ return $('.js-projects-dropdown-toggle').on('click', function(e) {
+ e.preventDefault();
+ return $('.js-projects-dropdown').select2('open');
+ });
+ };
+
+ Project.prototype.changeProject = function(url) {
+ return window.location = url;
+ };
+
+ Project.prototype.initRefSwitcher = function() {
+ return $('.js-project-refs-dropdown').each(function() {
+ var $dropdown, selected;
+ $dropdown = $(this);
+ selected = $dropdown.data('selected');
+ return $dropdown.glDropdown({
+ data: function(term, callback) {
+ return $.ajax({
+ url: $dropdown.data('refs-url'),
+ data: {
+ ref: $dropdown.data('ref')
+ }
+ }).done(function(refs) {
+ return callback(refs);
+ });
+ },
+ selectable: true,
+ filterable: true,
+ filterByText: true,
+ fieldName: 'ref',
+ renderRow: function(ref) {
+ var link;
+ if (ref.header != null) {
+ return $('<li />').addClass('dropdown-header').text(ref.header);
+ } else {
+ link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
+ return $('<li />').append(link);
+ }
+ },
+ id: function(obj, $el) {
+ return $el.attr('data-ref');
+ },
+ toggleLabel: function(obj, $el) {
+ return $el.text().trim();
+ },
+ clicked: function(e) {
+ return $dropdown.closest('form').submit();
+ }
+ });
+ });
+ };
+
+ return Project;
+
+})();
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
deleted file mode 100644
index 3288c801388..00000000000
--- a/app/assets/javascripts/project.js.coffee
+++ /dev/null
@@ -1,91 +0,0 @@
-class @Project
- constructor: ->
- # Git protocol switcher
- $('ul.clone-options-dropdown a').click ->
- return if $(@).hasClass('active')
-
-
- # Remove the active class for all buttons (ssh, http, kerberos if shown)
- $('.active').not($(@)).removeClass('active');
- # Add the active class for the clicked button
- $(@).toggleClass('active')
-
- url = $("#project_clone").val()
-
- # Update the input field
- $('#project_clone').val(url)
-
- # Update the command line instructions
- $('.clone').text(url)
-
- # Ref switcher
- @initRefSwitcher()
- $('.project-refs-select').on 'change', ->
- $(@).parents('form').submit()
-
- $('.hide-no-ssh-message').on 'click', (e) ->
- path = '/'
- $.cookie('hide_no_ssh_message', 'false', { path: path })
- $(@).parents('.no-ssh-key-message').remove()
- e.preventDefault()
-
- $('.hide-no-password-message').on 'click', (e) ->
- path = '/'
- $.cookie('hide_no_password_message', 'false', { path: path })
- $(@).parents('.no-password-message').remove()
- e.preventDefault()
-
- @projectSelectDropdown()
-
- projectSelectDropdown: ->
- new ProjectSelect()
-
- $('.project-item-select').on 'click', (e) =>
- @changeProject $(e.currentTarget).val()
-
- $('.js-projects-dropdown-toggle').on 'click', (e) ->
- e.preventDefault()
-
- $('.js-projects-dropdown').select2('open')
-
- changeProject: (url) ->
- window.location = url
-
- initRefSwitcher: ->
- $('.js-project-refs-dropdown').each ->
- $dropdown = $(@)
- selected = $dropdown.data('selected')
-
- $dropdown.glDropdown(
- data: (term, callback) ->
- $.ajax(
- url: $dropdown.data('refs-url')
- data:
- ref: $dropdown.data('ref')
- ).done (refs) ->
- callback(refs)
- selectable: true
- filterable: true
- filterByText: true
- fieldName: 'ref'
- renderRow: (ref) ->
- if ref.header?
- $('<li />')
- .addClass('dropdown-header')
- .text(ref.header)
- else
- link = $('<a />')
- .attr('href', '#')
- .addClass(if ref is selected then 'is-active' else '')
- .text(ref)
- .attr('data-ref', escape(ref))
-
- $('<li />')
- .append(link)
- id: (obj, $el) ->
- $el.attr('data-ref')
- toggleLabel: (obj, $el) ->
- $el.text().trim()
- clicked: (e) ->
- $dropdown.closest('form').submit()
- )
diff --git a/app/assets/javascripts/project_avatar.js b/app/assets/javascripts/project_avatar.js
new file mode 100644
index 00000000000..bce38f3e110
--- /dev/null
+++ b/app/assets/javascripts/project_avatar.js
@@ -0,0 +1,18 @@
+this.ProjectAvatar = (function() {
+ function ProjectAvatar() {
+ $('.js-choose-project-avatar-button').bind('click', function() {
+ var form;
+ form = $(this).closest('form');
+ return form.find('.js-project-avatar-input').click();
+ });
+ $('.js-project-avatar-input').bind('change', function() {
+ var filename, form;
+ form = $(this).closest('form');
+ filename = $(this).val().replace(/^.*[\\\/]/, '');
+ return form.find('.js-avatar-filename').text(filename);
+ });
+ }
+
+ return ProjectAvatar;
+
+})();
diff --git a/app/assets/javascripts/project_avatar.js.coffee b/app/assets/javascripts/project_avatar.js.coffee
deleted file mode 100644
index 8bec6e2ccca..00000000000
--- a/app/assets/javascripts/project_avatar.js.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-class @ProjectAvatar
- constructor: ->
- $('.js-choose-project-avatar-button').bind 'click', ->
- form = $(this).closest('form')
- form.find('.js-project-avatar-input').click()
- $('.js-project-avatar-input').bind 'change', ->
- form = $(this).closest('form')
- filename = $(this).val().replace(/^.*[\\\/]/, '')
- form.find('.js-avatar-filename').text(filename)
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
new file mode 100644
index 00000000000..335bf6d5f27
--- /dev/null
+++ b/app/assets/javascripts/project_find_file.js
@@ -0,0 +1,167 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.ProjectFindFile = (function() {
+ var highlighter;
+
+ function ProjectFindFile(element1, options) {
+ this.element = element1;
+ this.options = options;
+ this.goToBlob = bind(this.goToBlob, this);
+ this.goToTree = bind(this.goToTree, this);
+ this.selectRowDown = bind(this.selectRowDown, this);
+ this.selectRowUp = bind(this.selectRowUp, this);
+ this.filePaths = {};
+ this.inputElement = this.element.find(".file-finder-input");
+ this.initEvent();
+ this.inputElement.focus();
+ this.load(this.options.url);
+ }
+
+ ProjectFindFile.prototype.initEvent = function() {
+ this.inputElement.off("keyup");
+ this.inputElement.on("keyup", (function(_this) {
+ return function(event) {
+ var oldValue, ref, target, value;
+ target = $(event.target);
+ value = target.val();
+ oldValue = (ref = target.data("oldValue")) != null ? ref : "";
+ if (value !== oldValue) {
+ target.data("oldValue", value);
+ _this.findFile();
+ return _this.element.find("tr.tree-item").eq(0).addClass("selected").focus();
+ }
+ };
+ })(this));
+ return this.element.find(".tree-content-holder .tree-table").on("click", function(event) {
+ var path;
+ if (event.target.nodeName !== "A") {
+ path = this.element.find(".tree-item-file-name a", this).attr("href");
+ if (path) {
+ return location.href = path;
+ }
+ }
+ });
+ };
+
+ ProjectFindFile.prototype.findFile = function() {
+ var result, searchText;
+ searchText = this.inputElement.val();
+ result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
+ return this.renderList(result, searchText);
+ };
+
+ ProjectFindFile.prototype.load = function(url) {
+ return $.ajax({
+ url: url,
+ method: "get",
+ dataType: "json",
+ success: (function(_this) {
+ return function(data) {
+ _this.element.find(".loading").hide();
+ _this.filePaths = data;
+ _this.findFile();
+ return _this.element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus();
+ };
+ })(this)
+ });
+ };
+
+ ProjectFindFile.prototype.renderList = function(filePaths, searchText) {
+ var blobItemUrl, filePath, html, i, j, len, matches, results;
+ this.element.find(".tree-table > tbody").empty();
+ results = [];
+ for (i = j = 0, len = filePaths.length; j < len; i = ++j) {
+ filePath = filePaths[i];
+ if (i === 20) {
+ break;
+ }
+ if (searchText) {
+ matches = fuzzaldrinPlus.match(filePath, searchText);
+ }
+ blobItemUrl = this.options.blobUrlTemplate + "/" + filePath;
+ html = this.makeHtml(filePath, matches, blobItemUrl);
+ results.push(this.element.find(".tree-table > tbody").append(html));
+ }
+ return results;
+ };
+
+ highlighter = function(element, text, matches) {
+ var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
+ lastIndex = 0;
+ highlightText = "";
+ matchedChars = [];
+ for (j = 0, len = matches.length; j < len; j++) {
+ matchIndex = matches[j];
+ unmatched = text.substring(lastIndex, matchIndex);
+ if (unmatched) {
+ if (matchedChars.length) {
+ element.append(matchedChars.join("").bold());
+ }
+ matchedChars = [];
+ element.append(document.createTextNode(unmatched));
+ }
+ matchedChars.push(text[matchIndex]);
+ lastIndex = matchIndex + 1;
+ }
+ if (matchedChars.length) {
+ element.append(matchedChars.join("").bold());
+ }
+ return element.append(document.createTextNode(text.substring(lastIndex)));
+ };
+
+ ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) {
+ var $tr;
+ $tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>");
+ if (matches) {
+ $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl));
+ } else {
+ $tr.find("a").attr("href", blobItemUrl).text(filePath);
+ }
+ return $tr;
+ };
+
+ ProjectFindFile.prototype.selectRow = function(type) {
+ var next, rows, selectedRow;
+ rows = this.element.find(".files-slider tr.tree-item");
+ selectedRow = this.element.find(".files-slider tr.tree-item.selected");
+ if (rows && rows.length > 0) {
+ if (selectedRow && selectedRow.length > 0) {
+ if (type === "UP") {
+ next = selectedRow.prev();
+ } else if (type === "DOWN") {
+ next = selectedRow.next();
+ }
+ if (next.length > 0) {
+ selectedRow.removeClass("selected");
+ selectedRow = next;
+ }
+ } else {
+ selectedRow = rows.eq(0);
+ }
+ return selectedRow.addClass("selected").focus();
+ }
+ };
+
+ ProjectFindFile.prototype.selectRowUp = function() {
+ return this.selectRow("UP");
+ };
+
+ ProjectFindFile.prototype.selectRowDown = function() {
+ return this.selectRow("DOWN");
+ };
+
+ ProjectFindFile.prototype.goToTree = function() {
+ return location.href = this.options.treeUrl;
+ };
+
+ ProjectFindFile.prototype.goToBlob = function() {
+ var path;
+ path = this.element.find(".tree-item.selected .tree-item-file-name a").attr("href");
+ if (path) {
+ return location.href = path;
+ }
+ };
+
+ return ProjectFindFile;
+
+})();
diff --git a/app/assets/javascripts/project_find_file.js.coffee b/app/assets/javascripts/project_find_file.js.coffee
deleted file mode 100644
index 0dd32352c34..00000000000
--- a/app/assets/javascripts/project_find_file.js.coffee
+++ /dev/null
@@ -1,125 +0,0 @@
-class @ProjectFindFile
- constructor: (@element, @options)->
- @filePaths = {}
- @inputElement = @element.find(".file-finder-input")
-
- # init event
- @initEvent()
-
- # focus text input box
- @inputElement.focus()
-
- # load file list
- @load(@options.url)
-
- # init event
- initEvent: ->
- @inputElement.off "keyup"
- @inputElement.on "keyup", (event) =>
- target = $(event.target)
- value = target.val()
- oldValue = target.data("oldValue") ? ""
-
- if value != oldValue
- target.data("oldValue", value)
- @findFile()
- @element.find("tr.tree-item").eq(0).addClass("selected").focus()
-
- @element.find(".tree-content-holder .tree-table").on "click", (event) ->
- if (event.target.nodeName != "A")
- path = @element.find(".tree-item-file-name a", this).attr("href")
- location.href = path if path
-
- # find file
- findFile: ->
- searchText = @inputElement.val()
- result = if searchText.length > 0 then fuzzaldrinPlus.filter(@filePaths, searchText) else @filePaths
- @renderList result, searchText
-
- # files pathes load
- load: (url) ->
- $.ajax
- url: url
- method: "get"
- dataType: "json"
- success: (data) =>
- @element.find(".loading").hide()
- @filePaths = data
- @findFile()
- @element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus()
-
- # render result
- renderList: (filePaths, searchText) ->
- @element.find(".tree-table > tbody").empty()
-
- for filePath, i in filePaths
- break if i == 20
-
- if searchText
- matches = fuzzaldrinPlus.match(filePath, searchText)
-
- blobItemUrl = "#{@options.blobUrlTemplate}/#{filePath}"
-
- html = @makeHtml filePath, matches, blobItemUrl
- @element.find(".tree-table > tbody").append(html)
-
- # highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
- highlighter = (element, text, matches) ->
- lastIndex = 0
- highlightText = ""
- matchedChars = []
-
- for matchIndex in matches
- unmatched = text.substring(lastIndex, matchIndex)
-
- if unmatched
- element.append(matchedChars.join("").bold()) if matchedChars.length
- matchedChars = []
- element.append(document.createTextNode(unmatched))
-
- matchedChars.push(text[matchIndex])
- lastIndex = matchIndex + 1
-
- element.append(matchedChars.join("").bold()) if matchedChars.length
- element.append(document.createTextNode(text.substring(lastIndex)))
-
- # make tbody row html
- makeHtml: (filePath, matches, blobItemUrl) ->
- $tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>")
- if matches
- $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl))
- else
- $tr.find("a").attr("href", blobItemUrl).text(filePath)
-
- return $tr
-
- selectRow: (type) ->
- rows = @element.find(".files-slider tr.tree-item")
- selectedRow = @element.find(".files-slider tr.tree-item.selected")
-
- if rows && rows.length > 0
- if selectedRow && selectedRow.length > 0
- if type == "UP"
- next = selectedRow.prev()
- else if type == "DOWN"
- next = selectedRow.next()
-
- if next.length > 0
- selectedRow.removeClass "selected"
- selectedRow = next
- else
- selectedRow = rows.eq(0)
- selectedRow.addClass("selected").focus()
-
- selectRowUp: =>
- @selectRow "UP"
-
- selectRowDown: =>
- @selectRow "DOWN"
-
- goToTree: =>
- location.href = @options.treeUrl
-
- goToBlob: =>
- path = @element.find(".tree-item.selected .tree-item-file-name a").attr("href")
- location.href = path if path
diff --git a/app/assets/javascripts/project_fork.js b/app/assets/javascripts/project_fork.js
new file mode 100644
index 00000000000..603688dc87b
--- /dev/null
+++ b/app/assets/javascripts/project_fork.js
@@ -0,0 +1,11 @@
+this.ProjectFork = (function() {
+ function ProjectFork() {
+ $('.fork-thumbnail a').on('click', function() {
+ $('.fork-namespaces').hide();
+ return $('.save-project-loader').show();
+ });
+ }
+
+ return ProjectFork;
+
+})();
diff --git a/app/assets/javascripts/project_fork.js.coffee b/app/assets/javascripts/project_fork.js.coffee
deleted file mode 100644
index e15a1c4ef76..00000000000
--- a/app/assets/javascripts/project_fork.js.coffee
+++ /dev/null
@@ -1,5 +0,0 @@
-class @ProjectFork
- constructor: ->
- $('.fork-thumbnail a').on 'click', ->
- $('.fork-namespaces').hide()
- $('.save-project-loader').show()
diff --git a/app/assets/javascripts/project_import.js b/app/assets/javascripts/project_import.js
new file mode 100644
index 00000000000..9ee0b6b1143
--- /dev/null
+++ b/app/assets/javascripts/project_import.js
@@ -0,0 +1,10 @@
+this.ProjectImport = (function() {
+ function ProjectImport() {
+ setTimeout(function() {
+ return Turbolinks.visit(location.href);
+ }, 5000);
+ }
+
+ return ProjectImport;
+
+})();
diff --git a/app/assets/javascripts/project_import.js.coffee b/app/assets/javascripts/project_import.js.coffee
deleted file mode 100644
index 6633564a079..00000000000
--- a/app/assets/javascripts/project_import.js.coffee
+++ /dev/null
@@ -1,5 +0,0 @@
-class @ProjectImport
- constructor: ->
- setTimeout ->
- Turbolinks.visit(location.href)
- , 5000
diff --git a/app/assets/javascripts/project_members.js b/app/assets/javascripts/project_members.js
new file mode 100644
index 00000000000..17653d45602
--- /dev/null
+++ b/app/assets/javascripts/project_members.js
@@ -0,0 +1,10 @@
+this.ProjectMembers = (function() {
+ function ProjectMembers() {
+ $('li.project_member').bind('ajax:success', function() {
+ return $(this).fadeOut();
+ });
+ }
+
+ return ProjectMembers;
+
+})();
diff --git a/app/assets/javascripts/project_members.js.coffee b/app/assets/javascripts/project_members.js.coffee
deleted file mode 100644
index 896ba7e53ee..00000000000
--- a/app/assets/javascripts/project_members.js.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-class @ProjectMembers
- constructor: ->
- $('li.project_member').bind 'ajax:success', ->
- $(this).fadeOut()
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js
new file mode 100644
index 00000000000..4f33a564cbd
--- /dev/null
+++ b/app/assets/javascripts/project_new.js
@@ -0,0 +1,37 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.ProjectNew = (function() {
+ function ProjectNew() {
+ this.toggleSettings = bind(this.toggleSettings, this);
+ $('.project-edit-container').on('ajax:before', (function(_this) {
+ return function() {
+ $('.project-edit-container').hide();
+ return $('.save-project-loader').show();
+ };
+ })(this));
+ this.toggleSettings();
+ this.toggleSettingsOnclick();
+ }
+
+ ProjectNew.prototype.toggleSettings = function() {
+ this._showOrHide('#project_builds_enabled', '.builds-feature');
+ return this._showOrHide('#project_merge_requests_enabled', '.merge-requests-feature');
+ };
+
+ ProjectNew.prototype.toggleSettingsOnclick = function() {
+ return $('#project_builds_enabled, #project_merge_requests_enabled').on('click', this.toggleSettings);
+ };
+
+ ProjectNew.prototype._showOrHide = function(checkElement, container) {
+ var $container;
+ $container = $(container);
+ if ($(checkElement).prop('checked')) {
+ return $container.show();
+ } else {
+ return $container.hide();
+ }
+ };
+
+ return ProjectNew;
+
+})();
diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee
deleted file mode 100644
index e48343a19b5..00000000000
--- a/app/assets/javascripts/project_new.js.coffee
+++ /dev/null
@@ -1,23 +0,0 @@
-class @ProjectNew
- constructor: ->
- $('.project-edit-container').on 'ajax:before', =>
- $('.project-edit-container').hide()
- $('.save-project-loader').show()
- @toggleSettings()
- @toggleSettingsOnclick()
-
-
- toggleSettings: =>
- @_showOrHide('#project_builds_enabled', '.builds-feature')
- @_showOrHide('#project_merge_requests_enabled', '.merge-requests-feature')
-
- toggleSettingsOnclick: ->
- $('#project_builds_enabled, #project_merge_requests_enabled').on 'click', @toggleSettings
-
- _showOrHide: (checkElement, container) ->
- $container = $(container)
-
- if $(checkElement).prop('checked')
- $container.show()
- else
- $container.hide()
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
new file mode 100644
index 00000000000..bbe5b0b42cc
--- /dev/null
+++ b/app/assets/javascripts/project_select.js
@@ -0,0 +1,99 @@
+this.ProjectSelect = (function() {
+ function ProjectSelect() {
+ $('.js-projects-dropdown-toggle').each(function(i, dropdown) {
+ var $dropdown;
+ $dropdown = $(dropdown);
+ return $dropdown.glDropdown({
+ filterable: true,
+ filterRemote: true,
+ search: {
+ fields: ['name_with_namespace']
+ },
+ data: function(term, callback) {
+ var finalCallback, projectsCallback;
+ finalCallback = function(projects) {
+ return callback(projects);
+ };
+ if (this.includeGroups) {
+ projectsCallback = function(projects) {
+ var groupsCallback;
+ groupsCallback = function(groups) {
+ var data;
+ data = groups.concat(projects);
+ return finalCallback(data);
+ };
+ return Api.groups(term, false, groupsCallback);
+ };
+ } else {
+ projectsCallback = finalCallback;
+ }
+ if (this.groupId) {
+ return Api.groupProjects(this.groupId, term, projectsCallback);
+ } else {
+ return Api.projects(term, this.orderBy, projectsCallback);
+ }
+ },
+ url: function(project) {
+ return project.web_url;
+ },
+ text: function(project) {
+ return project.name_with_namespace;
+ }
+ });
+ });
+ $('.ajax-project-select').each(function(i, select) {
+ var placeholder;
+ this.groupId = $(select).data('group-id');
+ this.includeGroups = $(select).data('include-groups');
+ this.orderBy = $(select).data('order-by') || 'id';
+ placeholder = "Search for project";
+ if (this.includeGroups) {
+ placeholder += " or group";
+ }
+ return $(select).select2({
+ placeholder: placeholder,
+ minimumInputLength: 0,
+ query: (function(_this) {
+ return function(query) {
+ var finalCallback, projectsCallback;
+ finalCallback = function(projects) {
+ var data;
+ data = {
+ results: projects
+ };
+ return query.callback(data);
+ };
+ if (_this.includeGroups) {
+ projectsCallback = function(projects) {
+ var groupsCallback;
+ groupsCallback = function(groups) {
+ var data;
+ data = groups.concat(projects);
+ return finalCallback(data);
+ };
+ return Api.groups(query.term, false, groupsCallback);
+ };
+ } else {
+ projectsCallback = finalCallback;
+ }
+ if (_this.groupId) {
+ return Api.groupProjects(_this.groupId, query.term, projectsCallback);
+ } else {
+ return Api.projects(query.term, _this.orderBy, projectsCallback);
+ }
+ };
+ })(this),
+ id: function(project) {
+ return project.web_url;
+ },
+ text: function(project) {
+ return project.name_with_namespace || project.name;
+ },
+ dropdownCssClass: "ajax-project-dropdown"
+ });
+ });
+ }
+
+ return ProjectSelect;
+
+})();
diff --git a/app/assets/javascripts/project_select.js.coffee b/app/assets/javascripts/project_select.js.coffee
deleted file mode 100644
index 704bd8dee53..00000000000
--- a/app/assets/javascripts/project_select.js.coffee
+++ /dev/null
@@ -1,72 +0,0 @@
-class @ProjectSelect
- constructor: ->
- $('.js-projects-dropdown-toggle').each (i, dropdown) ->
- $dropdown = $(dropdown)
-
- $dropdown.glDropdown(
- filterable: true
- filterRemote: true
- search:
- fields: ['name_with_namespace']
- data: (term, callback) ->
- finalCallback = (projects) ->
- callback projects
-
- if @includeGroups
- projectsCallback = (projects) ->
- groupsCallback = (groups) ->
- data = groups.concat(projects)
- finalCallback(data)
-
- Api.groups term, false, groupsCallback
- else
- projectsCallback = finalCallback
-
- if @groupId
- Api.groupProjects @groupId, term, projectsCallback
- else
- Api.projects term, @orderBy, projectsCallback
- url: (project) ->
- project.web_url
- text: (project) ->
- project.name_with_namespace
- )
-
- $('.ajax-project-select').each (i, select) ->
- @groupId = $(select).data('group-id')
- @includeGroups = $(select).data('include-groups')
- @orderBy = $(select).data('order-by') || 'id'
-
- placeholder = "Search for project"
- placeholder += " or group" if @includeGroups
-
- $(select).select2
- placeholder: placeholder
- minimumInputLength: 0
- query: (query) =>
- finalCallback = (projects) ->
- data = { results: projects }
- query.callback(data)
-
- if @includeGroups
- projectsCallback = (projects) ->
- groupsCallback = (groups) ->
- data = groups.concat(projects)
- finalCallback(data)
-
- Api.groups query.term, false, groupsCallback
- else
- projectsCallback = finalCallback
-
- if @groupId
- Api.groupProjects @groupId, query.term, projectsCallback
- else
- Api.projects query.term, @orderBy, projectsCallback
-
- id: (project) ->
- project.web_url
-
- text: (project) ->
- project.name_with_namespace || project.name
-
- dropdownCssClass: "ajax-project-dropdown"
diff --git a/app/assets/javascripts/project_show.js b/app/assets/javascripts/project_show.js
new file mode 100644
index 00000000000..ad67c226f69
--- /dev/null
+++ b/app/assets/javascripts/project_show.js
@@ -0,0 +1,6 @@
+this.ProjectShow = (function() {
+ function ProjectShow() {}
+
+ return ProjectShow;
+
+})();
diff --git a/app/assets/javascripts/project_show.js.coffee b/app/assets/javascripts/project_show.js.coffee
deleted file mode 100644
index 1fdf28f2528..00000000000
--- a/app/assets/javascripts/project_show.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-class @ProjectShow
- constructor: ->
- # I kept class for future
diff --git a/app/assets/javascripts/projects_list.js b/app/assets/javascripts/projects_list.js
new file mode 100644
index 00000000000..1d9b32aa1bc
--- /dev/null
+++ b/app/assets/javascripts/projects_list.js
@@ -0,0 +1,45 @@
+this.ProjectsList = {
+ init: function() {
+ $(".projects-list-filter").off('keyup');
+ this.initSearch();
+ return this.initPagination();
+ },
+ initSearch: function() {
+ var debounceFilter, projectsListFilter;
+ projectsListFilter = $('.projects-list-filter');
+ debounceFilter = _.debounce(ProjectsList.filterResults, 500);
+ return projectsListFilter.on('keyup', function(e) {
+ if (projectsListFilter.val() !== '') {
+ return debounceFilter();
+ }
+ });
+ },
+ filterResults: function() {
+ var form, project_filter_url, search;
+ $('.projects-list-holder').fadeTo(250, 0.5);
+ form = null;
+ form = $("form#project-filter-form");
+ search = $(".projects-list-filter").val();
+ project_filter_url = form.attr('action') + '?' + form.serialize();
+ return $.ajax({
+ type: "GET",
+ url: form.attr('action'),
+ data: form.serialize(),
+ complete: function() {
+ return $('.projects-list-holder').fadeTo(250, 1);
+ },
+ success: function(data) {
+ $('.projects-list-holder').replaceWith(data.html);
+ return history.replaceState({
+ page: project_filter_url
+ }, document.title, project_filter_url);
+ },
+ dataType: "json"
+ });
+ },
+ initPagination: function() {
+ return $('.projects-list-holder .pagination').on('ajax:success', function(e, data) {
+ return $('.projects-list-holder').replaceWith(data.html);
+ });
+ }
+};
diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee
deleted file mode 100644
index a7d78d9e461..00000000000
--- a/app/assets/javascripts/projects_list.js.coffee
+++ /dev/null
@@ -1,36 +0,0 @@
-@ProjectsList =
- init: ->
- $(".projects-list-filter").off('keyup')
- this.initSearch()
- this.initPagination()
-
- initSearch: ->
- projectsListFilter = $('.projects-list-filter')
- debounceFilter = _.debounce ProjectsList.filterResults, 500
- projectsListFilter.on 'keyup', (e) ->
- debounceFilter() if projectsListFilter.val() isnt ''
-
- filterResults: ->
- $('.projects-list-holder').fadeTo(250, 0.5)
-
- form = null
- form = $("form#project-filter-form")
- search = $(".projects-list-filter").val()
- project_filter_url = form.attr('action') + '?' + form.serialize()
-
- $.ajax
- type: "GET"
- url: form.attr('action')
- data: form.serialize()
- complete: ->
- $('.projects-list-holder').fadeTo(250, 1)
- success: (data) ->
- $('.projects-list-holder').replaceWith(data.html)
- # Change url so if user reload a page - search results are saved
- history.replaceState {page: project_filter_url}, document.title, project_filter_url
- dataType: "json"
-
- initPagination: ->
- $('.projects-list-holder .pagination').on('ajax:success', (e, data) ->
- $('.projects-list-holder').replaceWith(data.html)
- )
diff --git a/app/assets/javascripts/protected_branch_select.js b/app/assets/javascripts/protected_branch_select.js
new file mode 100644
index 00000000000..b36f7256315
--- /dev/null
+++ b/app/assets/javascripts/protected_branch_select.js
@@ -0,0 +1,69 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.ProtectedBranchSelect = (function() {
+ function ProtectedBranchSelect(currentProject) {
+ this.toggleCreateNewButton = bind(this.toggleCreateNewButton, this);
+ this.getProtectedBranches = bind(this.getProtectedBranches, this);
+ $('.dropdown-footer').hide();
+ this.dropdown = $('.js-protected-branch-select').glDropdown({
+ data: this.getProtectedBranches,
+ filterable: true,
+ remote: false,
+ search: {
+ fields: ['title']
+ },
+ selectable: true,
+ toggleLabel: function(selected) {
+ if (selected && 'id' in selected) {
+ return selected.title;
+ } else {
+ return 'Protected Branch';
+ }
+ },
+ fieldName: 'protected_branch[name]',
+ text: function(protected_branch) {
+ return _.escape(protected_branch.title);
+ },
+ id: function(protected_branch) {
+ return _.escape(protected_branch.id);
+ },
+ onFilter: this.toggleCreateNewButton,
+ clicked: function() {
+ return $('.protect-branch-btn').attr('disabled', false);
+ }
+ });
+ $('.create-new-protected-branch').on('click', (function(_this) {
+ return function(event) {
+ _this.dropdown.data('glDropdown').remote.execute();
+ return _this.dropdown.data('glDropdown').selectRowAtIndex(event, 0);
+ };
+ })(this));
+ }
+
+ ProtectedBranchSelect.prototype.getProtectedBranches = function(term, callback) {
+ if (this.selectedBranch) {
+ return callback(gon.open_branches.concat(this.selectedBranch));
+ } else {
+ return callback(gon.open_branches);
+ }
+ };
+
+ ProtectedBranchSelect.prototype.toggleCreateNewButton = function(branchName) {
+ this.selectedBranch = {
+ title: branchName,
+ id: branchName,
+ text: branchName
+ };
+ if (branchName === '') {
+ $('.protected-branch-select-footer-list').addClass('hidden');
+ return $('.dropdown-footer').hide();
+ } else {
+ $('.create-new-protected-branch').text("Create Protected Branch: " + branchName);
+ $('.protected-branch-select-footer-list').removeClass('hidden');
+ return $('.dropdown-footer').show();
+ }
+ };
+
+ return ProtectedBranchSelect;
+
+})();
diff --git a/app/assets/javascripts/protected_branch_select.js.coffee b/app/assets/javascripts/protected_branch_select.js.coffee
deleted file mode 100644
index 6d45770ace9..00000000000
--- a/app/assets/javascripts/protected_branch_select.js.coffee
+++ /dev/null
@@ -1,40 +0,0 @@
-class @ProtectedBranchSelect
- constructor: (currentProject) ->
- $('.dropdown-footer').hide();
- @dropdown = $('.js-protected-branch-select').glDropdown(
- data: @getProtectedBranches
- filterable: true
- remote: false
- search:
- fields: ['title']
- selectable: true
- toggleLabel: (selected) -> if (selected and 'id' of selected) then selected.title else 'Protected Branch'
- fieldName: 'protected_branch[name]'
- text: (protected_branch) -> _.escape(protected_branch.title)
- id: (protected_branch) -> _.escape(protected_branch.id)
- onFilter: @toggleCreateNewButton
- clicked: () -> $('.protect-branch-btn').attr('disabled', false)
- )
-
- $('.create-new-protected-branch').on 'click', (event) =>
- # Refresh the dropdown's data, which ends up calling `getProtectedBranches`
- @dropdown.data('glDropdown').remote.execute()
- @dropdown.data('glDropdown').selectRowAtIndex(event, 0)
-
- getProtectedBranches: (term, callback) =>
- if @selectedBranch
- callback(gon.open_branches.concat(@selectedBranch))
- else
- callback(gon.open_branches)
-
- toggleCreateNewButton: (branchName) =>
- @selectedBranch = { title: branchName, id: branchName, text: branchName }
-
- if branchName is ''
- $('.protected-branch-select-footer-list').addClass('hidden')
- $('.dropdown-footer').hide();
- else
- $('.create-new-protected-branch').text("Create Protected Branch: #{branchName}")
- $('.protected-branch-select-footer-list').removeClass('hidden')
- $('.dropdown-footer').show();
-
diff --git a/app/assets/javascripts/protected_branches.js b/app/assets/javascripts/protected_branches.js
new file mode 100644
index 00000000000..48396697aea
--- /dev/null
+++ b/app/assets/javascripts/protected_branches.js
@@ -0,0 +1,32 @@
+$(function() {
+ return $(".protected-branches-list :checkbox").change(function(e) {
+ var can_push, id, name, obj, url;
+ name = $(this).attr("name");
+ if (name === "developers_can_push" || name === "developers_can_merge") {
+ id = $(this).val();
+ can_push = $(this).is(":checked");
+ url = $(this).data("url");
+ return $.ajax({
+ type: "PATCH",
+ url: url,
+ dataType: "json",
+ data: {
+ id: id,
+ protected_branch: (
+ obj = {},
+ obj["" + name] = can_push,
+ obj
+ )
+ },
+ success: function() {
+ var row;
+ row = $(e.target);
+ return row.closest('tr').effect('highlight');
+ },
+ error: function() {
+ return new Flash("Failed to update branch!", "alert");
+ }
+ });
+ }
+ });
+});
diff --git a/app/assets/javascripts/protected_branches.js.coffee b/app/assets/javascripts/protected_branches.js.coffee
deleted file mode 100644
index 14afef2e2ee..00000000000
--- a/app/assets/javascripts/protected_branches.js.coffee
+++ /dev/null
@@ -1,22 +0,0 @@
-$ ->
- $(".protected-branches-list :checkbox").change (e) ->
- name = $(this).attr("name")
- if name == "developers_can_push" || name == "developers_can_merge"
- id = $(this).val()
- can_push = $(this).is(":checked")
- url = $(this).data("url")
- $.ajax
- type: "PATCH"
- url: url
- dataType: "json"
- data:
- id: id
- protected_branch:
- "#{name}": can_push
-
- success: ->
- row = $(e.target)
- row.closest('tr').effect('highlight')
-
- error: ->
- new Flash("Failed to update branch!", "alert")
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
new file mode 100644
index 00000000000..1d5ed61b124
--- /dev/null
+++ b/app/assets/javascripts/right_sidebar.js
@@ -0,0 +1,198 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.Sidebar = (function() {
+ function Sidebar(currentUser) {
+ this.toggleTodo = bind(this.toggleTodo, this);
+ this.sidebar = $('aside');
+ this.addEventListeners();
+ }
+
+ Sidebar.prototype.addEventListeners = function() {
+ this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
+ $('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
+ $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
+ $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
+ $(document).off('click', '.js-sidebar-toggle').on('click', '.js-sidebar-toggle', function(e, triggered) {
+ var $allGutterToggleIcons, $this, $thisIcon;
+ e.preventDefault();
+ $this = $(this);
+ $thisIcon = $this.find('i');
+ $allGutterToggleIcons = $('.js-sidebar-toggle i');
+ if ($thisIcon.hasClass('fa-angle-double-right')) {
+ $allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left');
+ $('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
+ $('.page-with-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
+ } else {
+ $allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
+ $('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
+ $('.page-with-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
+ }
+ if (!triggered) {
+ return $.cookie("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'), {
+ path: '/'
+ });
+ }
+ });
+ return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
+ };
+
+ Sidebar.prototype.toggleTodo = function(e) {
+ var $btnText, $this, $todoLoading, ajaxType, url;
+ $this = $(e.currentTarget);
+ $todoLoading = $('.js-issuable-todo-loading');
+ $btnText = $('.js-issuable-todo-text', $this);
+ ajaxType = $this.attr('data-delete-path') ? 'DELETE' : 'POST';
+ if ($this.attr('data-delete-path')) {
+ url = "" + ($this.attr('data-delete-path'));
+ } else {
+ url = "" + ($this.data('url'));
+ }
+ return $.ajax({
+ url: url,
+ type: ajaxType,
+ dataType: 'json',
+ data: {
+ issuable_id: $this.data('issuable-id'),
+ issuable_type: $this.data('issuable-type')
+ },
+ beforeSend: (function(_this) {
+ return function() {
+ return _this.beforeTodoSend($this, $todoLoading);
+ };
+ })(this)
+ }).done((function(_this) {
+ return function(data) {
+ return _this.todoUpdateDone(data, $this, $btnText, $todoLoading);
+ };
+ })(this));
+ };
+
+ Sidebar.prototype.beforeTodoSend = function($btn, $todoLoading) {
+ $btn.disable();
+ return $todoLoading.removeClass('hidden');
+ };
+
+ Sidebar.prototype.todoUpdateDone = function(data, $btn, $btnText, $todoLoading) {
+ var $todoPendingCount;
+ $todoPendingCount = $('.todos-pending-count');
+ $todoPendingCount.text(data.count);
+ $btn.enable();
+ $todoLoading.addClass('hidden');
+ if (data.count === 0) {
+ $todoPendingCount.addClass('hidden');
+ } else {
+ $todoPendingCount.removeClass('hidden');
+ }
+ if (data.delete_path != null) {
+ $btn.attr('aria-label', $btn.data('mark-text')).attr('data-delete-path', data.delete_path);
+ return $btnText.text($btn.data('mark-text'));
+ } else {
+ $btn.attr('aria-label', $btn.data('todo-text')).removeAttr('data-delete-path');
+ return $btnText.text($btn.data('todo-text'));
+ }
+ };
+
+ Sidebar.prototype.sidebarDropdownLoading = function(e) {
+ var $loading, $sidebarCollapsedIcon, i, img;
+ $sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
+ img = $sidebarCollapsedIcon.find('img');
+ i = $sidebarCollapsedIcon.find('i');
+ $loading = $('<i class="fa fa-spinner fa-spin"></i>');
+ if (img.length) {
+ img.before($loading);
+ return img.hide();
+ } else if (i.length) {
+ i.before($loading);
+ return i.hide();
+ }
+ };
+
+ Sidebar.prototype.sidebarDropdownLoaded = function(e) {
+ var $sidebarCollapsedIcon, i, img;
+ $sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
+ img = $sidebarCollapsedIcon.find('img');
+ $sidebarCollapsedIcon.find('i.fa-spin').remove();
+ i = $sidebarCollapsedIcon.find('i');
+ if (img.length) {
+ return img.show();
+ } else {
+ return i.show();
+ }
+ };
+
+ Sidebar.prototype.sidebarCollapseClicked = function(e) {
+ var $block, sidebar;
+ if ($(e.currentTarget).hasClass('dont-change-state')) {
+ return;
+ }
+ sidebar = e.data;
+ e.preventDefault();
+ $block = $(this).closest('.block');
+ return sidebar.openDropdown($block);
+ };
+
+ Sidebar.prototype.openDropdown = function(blockOrName) {
+ var $block;
+ $block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName;
+ $block.find('.edit-link').trigger('click');
+ if (!this.isOpen()) {
+ this.setCollapseAfterUpdate($block);
+ return this.toggleSidebar('open');
+ }
+ };
+
+ Sidebar.prototype.setCollapseAfterUpdate = function($block) {
+ $block.addClass('collapse-after-update');
+ return $('.page-with-sidebar').addClass('with-overlay');
+ };
+
+ Sidebar.prototype.onSidebarDropdownHidden = function(e) {
+ var $block, sidebar;
+ sidebar = e.data;
+ e.preventDefault();
+ $block = $(this).closest('.block');
+ return sidebar.sidebarDropdownHidden($block);
+ };
+
+ Sidebar.prototype.sidebarDropdownHidden = function($block) {
+ if ($block.hasClass('collapse-after-update')) {
+ $block.removeClass('collapse-after-update');
+ $('.page-with-sidebar').removeClass('with-overlay');
+ return this.toggleSidebar('hide');
+ }
+ };
+
+ Sidebar.prototype.triggerOpenSidebar = function() {
+ return this.sidebar.find('.js-sidebar-toggle').trigger('click');
+ };
+
+ Sidebar.prototype.toggleSidebar = function(action) {
+ if (action == null) {
+ action = 'toggle';
+ }
+ if (action === 'toggle') {
+ this.triggerOpenSidebar();
+ }
+ if (action === 'open') {
+ if (!this.isOpen()) {
+ this.triggerOpenSidebar();
+ }
+ }
+ if (action === 'hide') {
+ if (this.isOpen()) {
+ return this.triggerOpenSidebar();
+ }
+ }
+ };
+
+ Sidebar.prototype.isOpen = function() {
+ return this.sidebar.is('.right-sidebar-expanded');
+ };
+
+ Sidebar.prototype.getBlock = function(name) {
+ return this.sidebar.find(".block." + name);
+ };
+
+ return Sidebar;
+
+})();
diff --git a/app/assets/javascripts/right_sidebar.js.coffee b/app/assets/javascripts/right_sidebar.js.coffee
deleted file mode 100644
index 0c95301e380..00000000000
--- a/app/assets/javascripts/right_sidebar.js.coffee
+++ /dev/null
@@ -1,175 +0,0 @@
-class @Sidebar
- constructor: (currentUser) ->
- @sidebar = $('aside')
-
- @addEventListeners()
-
- addEventListeners: ->
- @sidebar.on('click', '.sidebar-collapsed-icon', @, @sidebarCollapseClicked)
- $('.dropdown').on('hidden.gl.dropdown', @, @onSidebarDropdownHidden)
- $('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading)
- $('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded)
-
-
- $(document)
- .off 'click', '.js-sidebar-toggle'
- .on 'click', '.js-sidebar-toggle', (e, triggered) ->
- e.preventDefault()
- $this = $(this)
- $thisIcon = $this.find 'i'
- $allGutterToggleIcons = $('.js-sidebar-toggle i')
- if $thisIcon.hasClass('fa-angle-double-right')
- $allGutterToggleIcons
- .removeClass('fa-angle-double-right')
- .addClass('fa-angle-double-left')
- $('aside.right-sidebar')
- .removeClass('right-sidebar-expanded')
- .addClass('right-sidebar-collapsed')
- $('.page-with-sidebar')
- .removeClass('right-sidebar-expanded')
- .addClass('right-sidebar-collapsed')
- else
- $allGutterToggleIcons
- .removeClass('fa-angle-double-left')
- .addClass('fa-angle-double-right')
- $('aside.right-sidebar')
- .removeClass('right-sidebar-collapsed')
- .addClass('right-sidebar-expanded')
- $('.page-with-sidebar')
- .removeClass('right-sidebar-collapsed')
- .addClass('right-sidebar-expanded')
- if not triggered
- $.cookie("collapsed_gutter",
- $('.right-sidebar')
- .hasClass('right-sidebar-collapsed'), { path: '/' })
-
- $(document)
- .off 'click', '.js-issuable-todo'
- .on 'click', '.js-issuable-todo', @toggleTodo
-
- toggleTodo: (e) =>
- $this = $(e.currentTarget)
- $todoLoading = $('.js-issuable-todo-loading')
- $btnText = $('.js-issuable-todo-text', $this)
- ajaxType = if $this.attr('data-delete-path') then 'DELETE' else 'POST'
-
- if $this.attr('data-delete-path')
- url = "#{$this.attr('data-delete-path')}"
- else
- url = "#{$this.data('url')}"
-
- $.ajax(
- url: url
- type: ajaxType
- dataType: 'json'
- data:
- issuable_id: $this.data('issuable-id')
- issuable_type: $this.data('issuable-type')
- beforeSend: =>
- @beforeTodoSend($this, $todoLoading)
- ).done (data) =>
- @todoUpdateDone(data, $this, $btnText, $todoLoading)
-
- beforeTodoSend: ($btn, $todoLoading) ->
- $btn.disable()
- $todoLoading.removeClass 'hidden'
-
- todoUpdateDone: (data, $btn, $btnText, $todoLoading) ->
- $todoPendingCount = $('.todos-pending-count')
- $todoPendingCount.text data.count
-
- $btn.enable()
- $todoLoading.addClass 'hidden'
-
- if data.count is 0
- $todoPendingCount.addClass 'hidden'
- else
- $todoPendingCount.removeClass 'hidden'
-
- if data.delete_path?
- $btn
- .attr 'aria-label', $btn.data('mark-text')
- .attr 'data-delete-path', data.delete_path
- $btnText.text $btn.data('mark-text')
- else
- $btn
- .attr 'aria-label', $btn.data('todo-text')
- .removeAttr 'data-delete-path'
- $btnText.text $btn.data('todo-text')
-
- sidebarDropdownLoading: (e) ->
- $sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
- img = $sidebarCollapsedIcon.find('img')
- i = $sidebarCollapsedIcon.find('i')
- $loading = $('<i class="fa fa-spinner fa-spin"></i>')
- if img.length
- img.before($loading)
- img.hide()
- else if i.length
- i.before($loading)
- i.hide()
-
- sidebarDropdownLoaded: (e) ->
- $sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
- img = $sidebarCollapsedIcon.find('img')
- $sidebarCollapsedIcon.find('i.fa-spin').remove()
- i = $sidebarCollapsedIcon.find('i')
- if img.length
- img.show()
- else
- i.show()
-
- sidebarCollapseClicked: (e) ->
-
- return if $(e.currentTarget).hasClass('dont-change-state')
-
- sidebar = e.data
- e.preventDefault()
- $block = $(@).closest('.block')
- sidebar.openDropdown($block);
-
- openDropdown: (blockOrName) ->
- $block = if _.isString(blockOrName) then @getBlock(blockOrName) else blockOrName
-
- $block.find('.edit-link').trigger('click')
-
- if not @isOpen()
- @setCollapseAfterUpdate($block)
- @toggleSidebar('open')
-
- setCollapseAfterUpdate: ($block) ->
- $block.addClass('collapse-after-update')
- $('.page-with-sidebar').addClass('with-overlay')
-
- onSidebarDropdownHidden: (e) ->
- sidebar = e.data
- e.preventDefault()
- $block = $(@).closest('.block')
- sidebar.sidebarDropdownHidden($block)
-
- sidebarDropdownHidden: ($block) ->
- if $block.hasClass('collapse-after-update')
- $block.removeClass('collapse-after-update')
- $('.page-with-sidebar').removeClass('with-overlay')
- @toggleSidebar('hide')
-
- triggerOpenSidebar: ->
- @sidebar
- .find('.js-sidebar-toggle')
- .trigger('click')
-
- toggleSidebar: (action = 'toggle') ->
- if action is 'toggle'
- @triggerOpenSidebar()
-
- if action is 'open'
- @triggerOpenSidebar() if not @isOpen()
-
- if action is 'hide'
- @triggerOpenSidebar() if @isOpen()
-
- isOpen: ->
- @sidebar.is('.right-sidebar-expanded')
-
- getBlock: (name) ->
- @sidebar.find(".block.#{name}")
diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js
new file mode 100644
index 00000000000..f0dec3faf11
--- /dev/null
+++ b/app/assets/javascripts/search.js
@@ -0,0 +1,90 @@
+this.Search = (function() {
+ function Search() {
+ var $groupDropdown, $projectDropdown;
+ $groupDropdown = $('.js-search-group-dropdown');
+ $projectDropdown = $('.js-search-project-dropdown');
+ this.eventListeners();
+ $groupDropdown.glDropdown({
+ selectable: true,
+ filterable: true,
+ fieldName: 'group_id',
+ data: function(term, callback) {
+ return Api.groups(term, null, function(data) {
+ data.unshift({
+ name: 'Any'
+ });
+ data.splice(1, 0, 'divider');
+ return callback(data);
+ });
+ },
+ id: function(obj) {
+ return obj.id;
+ },
+ text: function(obj) {
+ return obj.name;
+ },
+ toggleLabel: function(obj) {
+ return ($groupDropdown.data('default-label')) + " " + obj.name;
+ },
+ clicked: (function(_this) {
+ return function() {
+ return _this.submitSearch();
+ };
+ })(this)
+ });
+ $projectDropdown.glDropdown({
+ selectable: true,
+ filterable: true,
+ fieldName: 'project_id',
+ data: function(term, callback) {
+ return Api.projects(term, 'id', function(data) {
+ data.unshift({
+ name_with_namespace: 'Any'
+ });
+ data.splice(1, 0, 'divider');
+ return callback(data);
+ });
+ },
+ id: function(obj) {
+ return obj.id;
+ },
+ text: function(obj) {
+ return obj.name_with_namespace;
+ },
+ toggleLabel: function(obj) {
+ return ($projectDropdown.data('default-label')) + " " + obj.name_with_namespace;
+ },
+ clicked: (function(_this) {
+ return function() {
+ return _this.submitSearch();
+ };
+ })(this)
+ });
+ }
+
+ Search.prototype.eventListeners = function() {
+ $(document).off('keyup', '.js-search-input').on('keyup', '.js-search-input', this.searchKeyUp);
+ return $(document).off('click', '.js-search-clear').on('click', '.js-search-clear', this.clearSearchField);
+ };
+
+ Search.prototype.submitSearch = function() {
+ return $('.js-search-form').submit();
+ };
+
+ Search.prototype.searchKeyUp = function() {
+ var $input;
+ $input = $(this);
+ if ($input.val() === '') {
+ return $('.js-search-clear').addClass('hidden');
+ } else {
+ return $('.js-search-clear').removeClass('hidden');
+ }
+ };
+
+ Search.prototype.clearSearchField = function() {
+ return $('.js-search-input').val('').trigger('keyup').focus();
+ };
+
+ return Search;
+
+})();
diff --git a/app/assets/javascripts/search.js.coffee b/app/assets/javascripts/search.js.coffee
deleted file mode 100644
index 661e1195f60..00000000000
--- a/app/assets/javascripts/search.js.coffee
+++ /dev/null
@@ -1,75 +0,0 @@
-class @Search
- constructor: ->
- $groupDropdown = $('.js-search-group-dropdown')
- $projectDropdown = $('.js-search-project-dropdown')
- @eventListeners()
-
- $groupDropdown.glDropdown(
- selectable: true
- filterable: true
- fieldName: 'group_id'
- data: (term, callback) ->
- Api.groups term, null, (data) ->
- data.unshift(
- name: 'Any'
- )
- data.splice 1, 0, 'divider'
-
- callback(data)
- id: (obj) ->
- obj.id
- text: (obj) ->
- obj.name
- toggleLabel: (obj) ->
- "#{$groupDropdown.data('default-label')} #{obj.name}"
- clicked: =>
- @submitSearch()
- )
-
- $projectDropdown.glDropdown(
- selectable: true
- filterable: true
- fieldName: 'project_id'
- data: (term, callback) ->
- Api.projects term, 'id', (data) ->
- data.unshift(
- name_with_namespace: 'Any'
- )
- data.splice 1, 0, 'divider'
-
- callback(data)
- id: (obj) ->
- obj.id
- text: (obj) ->
- obj.name_with_namespace
- toggleLabel: (obj) ->
- "#{$projectDropdown.data('default-label')} #{obj.name_with_namespace}"
- clicked: =>
- @submitSearch()
- )
-
- eventListeners: ->
- $(document)
- .off 'keyup', '.js-search-input'
- .on 'keyup', '.js-search-input', @searchKeyUp
-
- $(document)
- .off 'click', '.js-search-clear'
- .on 'click', '.js-search-clear', @clearSearchField
-
- submitSearch: ->
- $('.js-search-form').submit()
-
- searchKeyUp: ->
- $input = $(@)
-
- if $input.val() is ''
- $('.js-search-clear').addClass 'hidden'
- else
- $('.js-search-clear').removeClass 'hidden'
-
- clearSearchField: ->
- $('.js-search-input')
- .val ''
- .trigger 'keyup'
- .focus()
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
new file mode 100644
index 00000000000..243d6d0e349
--- /dev/null
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -0,0 +1,357 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.SearchAutocomplete = (function() {
+ var KEYCODE;
+
+ KEYCODE = {
+ ESCAPE: 27,
+ BACKSPACE: 8,
+ ENTER: 13
+ };
+
+ function SearchAutocomplete(opts) {
+ var ref, ref1, ref2, ref3, ref4;
+ if (opts == null) {
+ opts = {};
+ }
+ this.onSearchInputBlur = bind(this.onSearchInputBlur, this);
+ this.onClearInputClick = bind(this.onClearInputClick, this);
+ this.onSearchInputFocus = bind(this.onSearchInputFocus, this);
+ this.onSearchInputClick = bind(this.onSearchInputClick, this);
+ this.onSearchInputKeyUp = bind(this.onSearchInputKeyUp, this);
+ this.onSearchInputKeyDown = bind(this.onSearchInputKeyDown, this);
+ this.wrap = (ref = opts.wrap) != null ? ref : $('.search'), this.optsEl = (ref1 = opts.optsEl) != null ? ref1 : this.wrap.find('.search-autocomplete-opts'), this.autocompletePath = (ref2 = opts.autocompletePath) != null ? ref2 : this.optsEl.data('autocomplete-path'), this.projectId = (ref3 = opts.projectId) != null ? ref3 : this.optsEl.data('autocomplete-project-id') || '', this.projectRef = (ref4 = opts.projectRef) != null ? ref4 : this.optsEl.data('autocomplete-project-ref') || '';
+ this.dropdown = this.wrap.find('.dropdown');
+ this.dropdownContent = this.dropdown.find('.dropdown-content');
+ this.locationBadgeEl = this.getElement('.location-badge');
+ this.scopeInputEl = this.getElement('#scope');
+ this.searchInput = this.getElement('.search-input');
+ this.projectInputEl = this.getElement('#search_project_id');
+ this.groupInputEl = this.getElement('#group_id');
+ this.searchCodeInputEl = this.getElement('#search_code');
+ this.repositoryInputEl = this.getElement('#repository_ref');
+ this.clearInput = this.getElement('.js-clear-input');
+ this.saveOriginalState();
+ if (gon.current_user_id) {
+ this.createAutocomplete();
+ }
+ this.searchInput.addClass('disabled');
+ this.saveTextLength();
+ this.bindEvents();
+ }
+
+ SearchAutocomplete.prototype.getElement = function(selector) {
+ return this.wrap.find(selector);
+ };
+
+ SearchAutocomplete.prototype.saveOriginalState = function() {
+ return this.originalState = this.serializeState();
+ };
+
+ SearchAutocomplete.prototype.saveTextLength = function() {
+ return this.lastTextLength = this.searchInput.val().length;
+ };
+
+ SearchAutocomplete.prototype.createAutocomplete = function() {
+ return this.searchInput.glDropdown({
+ filterInputBlur: false,
+ filterable: true,
+ filterRemote: true,
+ highlight: true,
+ enterCallback: false,
+ filterInput: 'input#search',
+ search: {
+ fields: ['text']
+ },
+ data: this.getData.bind(this),
+ selectable: true,
+ clicked: this.onClick.bind(this)
+ });
+ };
+
+ SearchAutocomplete.prototype.getData = function(term, callback) {
+ var _this, contents, jqXHR;
+ _this = this;
+ if (!term) {
+ if (contents = this.getCategoryContents()) {
+ this.searchInput.data('glDropdown').filter.options.callback(contents);
+ this.enableAutocomplete();
+ }
+ return;
+ }
+ if (this.loadingSuggestions) {
+ return;
+ }
+ this.loadingSuggestions = true;
+ return jqXHR = $.get(this.autocompletePath, {
+ project_id: this.projectId,
+ project_ref: this.projectRef,
+ term: term
+ }, function(response) {
+ var data, firstCategory, i, lastCategory, len, suggestion;
+ if (!response.length) {
+ _this.disableAutocomplete();
+ return;
+ }
+ data = [];
+ firstCategory = true;
+ for (i = 0, len = response.length; i < len; i++) {
+ suggestion = response[i];
+ if (lastCategory !== suggestion.category) {
+ if (!firstCategory) {
+ data.push('separator');
+ }
+ if (firstCategory) {
+ firstCategory = false;
+ }
+ data.push({
+ header: suggestion.category
+ });
+ lastCategory = suggestion.category;
+ }
+ data.push({
+ id: (suggestion.category.toLowerCase()) + "-" + suggestion.id,
+ category: suggestion.category,
+ text: suggestion.label,
+ url: suggestion.url
+ });
+ }
+ if (data.length) {
+ data.push('separator');
+ data.push({
+ text: "Result name contains \"" + term + "\"",
+ url: "/search?search=" + term + "&project_id=" + (_this.projectInputEl.val()) + "&group_id=" + (_this.groupInputEl.val())
+ });
+ }
+ return callback(data);
+ }).always(function() {
+ return _this.loadingSuggestions = false;
+ });
+ };
+
+ SearchAutocomplete.prototype.getCategoryContents = function() {
+ var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, utils;
+ userId = gon.current_user_id;
+ utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions;
+ if (utils.isInGroupsPage() && groupOptions) {
+ options = groupOptions[utils.getGroupSlug()];
+ } else if (utils.isInProjectPage() && projectOptions) {
+ options = projectOptions[utils.getProjectSlug()];
+ } else if (dashboardOptions) {
+ options = dashboardOptions;
+ }
+ issuesPath = options.issuesPath, mrPath = options.mrPath, name = options.name;
+ items = [
+ {
+ header: "" + name
+ }, {
+ text: 'Issues assigned to me',
+ url: issuesPath + "/?assignee_id=" + userId
+ }, {
+ text: "Issues I've created",
+ url: issuesPath + "/?author_id=" + userId
+ }, 'separator', {
+ text: 'Merge requests assigned to me',
+ url: mrPath + "/?assignee_id=" + userId
+ }, {
+ text: "Merge requests I've created",
+ url: mrPath + "/?author_id=" + userId
+ }
+ ];
+ if (!name) {
+ items.splice(0, 1);
+ }
+ return items;
+ };
+
+ SearchAutocomplete.prototype.serializeState = function() {
+ return {
+ search_project_id: this.projectInputEl.val(),
+ group_id: this.groupInputEl.val(),
+ search_code: this.searchCodeInputEl.val(),
+ repository_ref: this.repositoryInputEl.val(),
+ scope: this.scopeInputEl.val(),
+ _location: this.locationBadgeEl.text()
+ };
+ };
+
+ SearchAutocomplete.prototype.bindEvents = function() {
+ this.searchInput.on('keydown', this.onSearchInputKeyDown);
+ this.searchInput.on('keyup', this.onSearchInputKeyUp);
+ this.searchInput.on('click', this.onSearchInputClick);
+ this.searchInput.on('focus', this.onSearchInputFocus);
+ this.searchInput.on('blur', this.onSearchInputBlur);
+ this.clearInput.on('click', this.onClearInputClick);
+ return this.locationBadgeEl.on('click', (function(_this) {
+ return function() {
+ return _this.searchInput.focus();
+ };
+ })(this));
+ };
+
+ SearchAutocomplete.prototype.enableAutocomplete = function() {
+ var _this;
+ if (!gon.current_user_id) {
+ return;
+ }
+ if (!this.dropdown.hasClass('open')) {
+ _this = this;
+ this.loadingSuggestions = false;
+ this.dropdown.addClass('open').trigger('shown.bs.dropdown');
+ return this.searchInput.removeClass('disabled');
+ }
+ };
+
+ SearchAutocomplete.prototype.onSearchInputKeyDown = function() {
+ return this.saveTextLength();
+ };
+
+ SearchAutocomplete.prototype.onSearchInputKeyUp = function(e) {
+ switch (e.keyCode) {
+ case KEYCODE.BACKSPACE:
+ if (this.lastTextLength === 0 && this.badgePresent()) {
+ this.removeLocationBadge();
+ }
+ if (this.lastTextLength === 1) {
+ this.disableAutocomplete();
+ }
+ if (this.lastTextLength > 1) {
+ this.enableAutocomplete();
+ }
+ break;
+ case KEYCODE.ESCAPE:
+ this.restoreOriginalState();
+ break;
+ default:
+ if (this.searchInput.val() === '') {
+ this.disableAutocomplete();
+ } else {
+ if (e.keyCode !== KEYCODE.ENTER) {
+ this.enableAutocomplete();
+ }
+ }
+ }
+ this.wrap.toggleClass('has-value', !!e.target.value);
+ };
+
+ SearchAutocomplete.prototype.onSearchInputClick = function(e) {
+ return e.stopImmediatePropagation();
+ };
+
+ SearchAutocomplete.prototype.onSearchInputFocus = function() {
+ this.isFocused = true;
+ this.wrap.addClass('search-active');
+ if (this.getValue() === '') {
+ return this.getData();
+ }
+ };
+
+ SearchAutocomplete.prototype.getValue = function() {
+ return this.searchInput.val();
+ };
+
+ SearchAutocomplete.prototype.onClearInputClick = function(e) {
+ e.preventDefault();
+ return this.searchInput.val('').focus();
+ };
+
+ SearchAutocomplete.prototype.onSearchInputBlur = function(e) {
+ this.isFocused = false;
+ this.wrap.removeClass('search-active');
+ if (this.searchInput.val() === '') {
+ return this.restoreOriginalState();
+ }
+ };
+
+ SearchAutocomplete.prototype.addLocationBadge = function(item) {
+ var badgeText, category, value;
+ category = item.category != null ? item.category + ": " : '';
+ value = item.value != null ? item.value : '';
+ badgeText = "" + category + value;
+ this.locationBadgeEl.text(badgeText).show();
+ return this.wrap.addClass('has-location-badge');
+ };
+
+ SearchAutocomplete.prototype.hasLocationBadge = function() {
+ return this.wrap.is('.has-location-badge');
+ };
+
+ SearchAutocomplete.prototype.restoreOriginalState = function() {
+ var i, input, inputs, len;
+ inputs = Object.keys(this.originalState);
+ for (i = 0, len = inputs.length; i < len; i++) {
+ input = inputs[i];
+ this.getElement("#" + input).val(this.originalState[input]);
+ }
+ if (this.originalState._location === '') {
+ return this.locationBadgeEl.hide();
+ } else {
+ return this.addLocationBadge({
+ value: this.originalState._location
+ });
+ }
+ };
+
+ SearchAutocomplete.prototype.badgePresent = function() {
+ return this.locationBadgeEl.length;
+ };
+
+ SearchAutocomplete.prototype.resetSearchState = function() {
+ var i, input, inputs, len, results;
+ inputs = Object.keys(this.originalState);
+ results = [];
+ for (i = 0, len = inputs.length; i < len; i++) {
+ input = inputs[i];
+ if (input === '_location') {
+ break;
+ }
+ results.push(this.getElement("#" + input).val(''));
+ }
+ return results;
+ };
+
+ SearchAutocomplete.prototype.removeLocationBadge = function() {
+ this.locationBadgeEl.hide();
+ this.resetSearchState();
+ this.wrap.removeClass('has-location-badge');
+ return this.disableAutocomplete();
+ };
+
+ SearchAutocomplete.prototype.disableAutocomplete = function() {
+ this.searchInput.addClass('disabled');
+ this.dropdown.removeClass('open');
+ return this.restoreMenu();
+ };
+
+ SearchAutocomplete.prototype.restoreMenu = function() {
+ var html;
+ html = "<ul> <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li> </ul>";
+ return this.dropdownContent.html(html);
+ };
+
+ SearchAutocomplete.prototype.onClick = function(item, $el, e) {
+ if (location.pathname.indexOf(item.url) !== -1) {
+ e.preventDefault();
+ if (!this.badgePresent) {
+ if (item.category === 'Projects') {
+ this.projectInputEl.val(item.id);
+ this.addLocationBadge({
+ value: 'This project'
+ });
+ }
+ if (item.category === 'Groups') {
+ this.groupInputEl.val(item.id);
+ this.addLocationBadge({
+ value: 'This group'
+ });
+ }
+ }
+ $el.removeClass('is-active');
+ this.disableAutocomplete();
+ return this.searchInput.val('').focus();
+ }
+ };
+
+ return SearchAutocomplete;
+
+})();
diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee
deleted file mode 100644
index 72b1d3dfb1e..00000000000
--- a/app/assets/javascripts/search_autocomplete.js.coffee
+++ /dev/null
@@ -1,334 +0,0 @@
-class @SearchAutocomplete
-
- KEYCODE =
- ESCAPE: 27
- BACKSPACE: 8
- ENTER: 13
-
- constructor: (opts = {}) ->
- {
- @wrap = $('.search')
-
- @optsEl = @wrap.find('.search-autocomplete-opts')
- @autocompletePath = @optsEl.data('autocomplete-path')
- @projectId = @optsEl.data('autocomplete-project-id') || ''
- @projectRef = @optsEl.data('autocomplete-project-ref') || ''
-
- } = opts
-
- # Dropdown Element
- @dropdown = @wrap.find('.dropdown')
- @dropdownContent = @dropdown.find('.dropdown-content')
-
- @locationBadgeEl = @getElement('.location-badge')
- @scopeInputEl = @getElement('#scope')
- @searchInput = @getElement('.search-input')
- @projectInputEl = @getElement('#search_project_id')
- @groupInputEl = @getElement('#group_id')
- @searchCodeInputEl = @getElement('#search_code')
- @repositoryInputEl = @getElement('#repository_ref')
- @clearInput = @getElement('.js-clear-input')
-
- @saveOriginalState()
-
- # Only when user is logged in
- @createAutocomplete() if gon.current_user_id
-
- @searchInput.addClass('disabled')
-
- @saveTextLength()
-
- @bindEvents()
-
- # Finds an element inside wrapper element
- getElement: (selector) ->
- @wrap.find(selector)
-
- saveOriginalState: ->
- @originalState = @serializeState()
-
- saveTextLength: ->
- @lastTextLength = @searchInput.val().length
-
- createAutocomplete: ->
- @searchInput.glDropdown
- filterInputBlur: false
- filterable: true
- filterRemote: true
- highlight: true
- enterCallback: false
- filterInput: 'input#search'
- search:
- fields: ['text']
- data: @getData.bind(@)
- selectable: true
- clicked: @onClick.bind(@)
-
- getData: (term, callback) ->
- _this = @
-
- unless term
- if contents = @getCategoryContents()
- @searchInput.data('glDropdown').filter.options.callback contents
- @enableAutocomplete()
-
- return
-
- # Prevent multiple ajax calls
- return if @loadingSuggestions
-
- @loadingSuggestions = true
-
- jqXHR = $.get(@autocompletePath, {
- project_id: @projectId
- project_ref: @projectRef
- term: term
- }, (response) ->
- # Hide dropdown menu if no suggestions returns
- if !response.length
- _this.disableAutocomplete()
- return
-
- data = []
-
- # List results
- firstCategory = true
- for suggestion in response
-
- # Add group header before list each group
- if lastCategory isnt suggestion.category
- data.push 'separator' if !firstCategory
-
- firstCategory = false if firstCategory
-
- data.push
- header: suggestion.category
-
- lastCategory = suggestion.category
-
- data.push
- id: "#{suggestion.category.toLowerCase()}-#{suggestion.id}"
- category: suggestion.category
- text: suggestion.label
- url: suggestion.url
-
- # Add option to proceed with the search
- if data.length
- data.push('separator')
- data.push
- text: "Result name contains \"#{term}\""
- url: "/search?\
- search=#{term}\
- &project_id=#{_this.projectInputEl.val()}\
- &group_id=#{_this.groupInputEl.val()}"
-
- callback(data)
- ).always ->
- _this.loadingSuggestions = false
-
-
- getCategoryContents: ->
-
- userId = gon.current_user_id
- { utils, projectOptions, groupOptions, dashboardOptions } = gl
-
- if utils.isInGroupsPage() and groupOptions
- options = groupOptions[utils.getGroupSlug()]
-
- else if utils.isInProjectPage() and projectOptions
- options = projectOptions[utils.getProjectSlug()]
-
- else if dashboardOptions
- options = dashboardOptions
-
- { issuesPath, mrPath, name } = options
-
- items = [
- { header: "#{name}" }
- { text: 'Issues assigned to me', url: "#{issuesPath}/?assignee_id=#{userId}" }
- { text: "Issues I've created", url: "#{issuesPath}/?author_id=#{userId}" }
- 'separator'
- { text: 'Merge requests assigned to me', url: "#{mrPath}/?assignee_id=#{userId}" }
- { text: "Merge requests I've created", url: "#{mrPath}/?author_id=#{userId}" }
- ]
-
- items.splice 0, 1 unless name
-
- return items
-
-
- serializeState: ->
- {
- # Search Criteria
- search_project_id: @projectInputEl.val()
- group_id: @groupInputEl.val()
- search_code: @searchCodeInputEl.val()
- repository_ref: @repositoryInputEl.val()
- scope: @scopeInputEl.val()
-
- # Location badge
- _location: @locationBadgeEl.text()
- }
-
- bindEvents: ->
- @searchInput.on 'keydown', @onSearchInputKeyDown
- @searchInput.on 'keyup', @onSearchInputKeyUp
- @searchInput.on 'click', @onSearchInputClick
- @searchInput.on 'focus', @onSearchInputFocus
- @searchInput.on 'blur', @onSearchInputBlur
- @clearInput.on 'click', @onClearInputClick
- @locationBadgeEl.on 'click', =>
- @searchInput.focus()
-
- enableAutocomplete: ->
- # No need to enable anything if user is not logged in
- return if !gon.current_user_id
-
- unless @dropdown.hasClass('open')
- _this = @
- @loadingSuggestions = false
-
- @dropdown
- .addClass('open')
- .trigger('shown.bs.dropdown')
- @searchInput.removeClass('disabled')
-
- onSearchInputKeyDown: =>
- # Saves last length of the entered text
- @saveTextLength()
-
- onSearchInputKeyUp: (e) =>
- switch e.keyCode
- when KEYCODE.BACKSPACE
- # when trying to remove the location badge
- if @lastTextLength is 0 and @badgePresent()
- @removeLocationBadge()
-
- # When removing the last character and no badge is present
- if @lastTextLength is 1
- @disableAutocomplete()
-
- # When removing any character from existin value
- if @lastTextLength > 1
- @enableAutocomplete()
-
- when KEYCODE.ESCAPE
- @restoreOriginalState()
-
- else
- # Handle the case when deleting the input value other than backspace
- # e.g. Pressing ctrl + backspace or ctrl + x
- if @searchInput.val() is ''
- @disableAutocomplete()
- else
- # We should display the menu only when input is not empty
- @enableAutocomplete() if e.keyCode isnt KEYCODE.ENTER
-
- @wrap.toggleClass 'has-value', !!e.target.value
-
- # Avoid falsy value to be returned
- return
-
- onSearchInputClick: (e) =>
- # Prevents closing the dropdown menu
- e.stopImmediatePropagation()
-
- onSearchInputFocus: =>
- @isFocused = true
- @wrap.addClass('search-active')
-
- @getData() if @getValue() is ''
-
-
- getValue: -> return @searchInput.val()
-
-
- onClearInputClick: (e) =>
- e.preventDefault()
- @searchInput.val('').focus()
-
- onSearchInputBlur: (e) =>
- @isFocused = false
- @wrap.removeClass('search-active')
-
- # If input is blank then restore state
- if @searchInput.val() is ''
- @restoreOriginalState()
-
- addLocationBadge: (item) ->
- category = if item.category? then "#{item.category}: " else ''
- value = if item.value? then item.value else ''
-
- badgeText = "#{category}#{value}"
- @locationBadgeEl.text(badgeText).show()
- @wrap.addClass('has-location-badge')
-
-
- hasLocationBadge: -> return @wrap.is '.has-location-badge'
-
-
- restoreOriginalState: ->
- inputs = Object.keys @originalState
-
- for input in inputs
- @getElement("##{input}").val(@originalState[input])
-
- if @originalState._location is ''
- @locationBadgeEl.hide()
- else
- @addLocationBadge(
- value: @originalState._location
- )
-
- badgePresent: ->
- @locationBadgeEl.length
-
- resetSearchState: ->
- inputs = Object.keys @originalState
-
- for input in inputs
-
- # _location isnt a input
- break if input is '_location'
-
- @getElement("##{input}").val('')
-
-
- removeLocationBadge: ->
-
- @locationBadgeEl.hide()
- @resetSearchState()
- @wrap.removeClass('has-location-badge')
- @disableAutocomplete()
-
-
- disableAutocomplete: ->
- @searchInput.addClass('disabled')
- @dropdown.removeClass('open')
- @restoreMenu()
-
- restoreMenu: ->
- html = "<ul>
- <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li>
- </ul>"
- @dropdownContent.html(html)
-
- onClick: (item, $el, e) ->
- if location.pathname.indexOf(item.url) isnt -1
- e.preventDefault()
- if not @badgePresent
- if item.category is 'Projects'
- @projectInputEl.val(item.id)
- @addLocationBadge(
- value: 'This project'
- )
-
- if item.category is 'Groups'
- @groupInputEl.val(item.id)
- @addLocationBadge(
- value: 'This group'
- )
-
- $el.removeClass('is-active')
- @disableAutocomplete()
- @searchInput.val('').focus()
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
new file mode 100644
index 00000000000..9f87b15fc4d
--- /dev/null
+++ b/app/assets/javascripts/shortcuts.js
@@ -0,0 +1,94 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.Shortcuts = (function() {
+ function Shortcuts(skipResetBindings) {
+ this.onToggleHelp = bind(this.onToggleHelp, this);
+ this.enabledHelp = [];
+ if (!skipResetBindings) {
+ Mousetrap.reset();
+ }
+ Mousetrap.bind('?', this.onToggleHelp);
+ Mousetrap.bind('s', Shortcuts.focusSearch);
+ Mousetrap.bind('f', (function(_this) {
+ return function(e) {
+ return _this.focusFilter(e);
+ };
+ })(this));
+ Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], this.toggleMarkdownPreview);
+ if (typeof findFileURL !== "undefined" && findFileURL !== null) {
+ Mousetrap.bind('t', function() {
+ return Turbolinks.visit(findFileURL);
+ });
+ }
+ }
+
+ Shortcuts.prototype.onToggleHelp = function(e) {
+ e.preventDefault();
+ return Shortcuts.toggleHelp(this.enabledHelp);
+ };
+
+ Shortcuts.prototype.toggleMarkdownPreview = function(e) {
+ return $(document).triggerHandler('markdown-preview:toggle', [e]);
+ };
+
+ Shortcuts.toggleHelp = function(location) {
+ var $modal;
+ $modal = $('#modal-shortcuts');
+ if ($modal.length) {
+ $modal.modal('toggle');
+ return;
+ }
+ return $.ajax({
+ url: gon.shortcuts_path,
+ dataType: 'script',
+ success: function(e) {
+ var i, l, len, results;
+ if (location && location.length > 0) {
+ results = [];
+ for (i = 0, len = location.length; i < len; i++) {
+ l = location[i];
+ results.push($(l).show());
+ }
+ return results;
+ } else {
+ $('.hidden-shortcut').show();
+ return $('.js-more-help-button').remove();
+ }
+ }
+ });
+ };
+
+ Shortcuts.prototype.focusFilter = function(e) {
+ if (this.filterInput == null) {
+ this.filterInput = $('input[type=search]', '.nav-controls');
+ }
+ this.filterInput.focus();
+ return e.preventDefault();
+ };
+
+ Shortcuts.focusSearch = function(e) {
+ $('#search').focus();
+ return e.preventDefault();
+ };
+
+ return Shortcuts;
+
+})();
+
+$(document).on('click.more_help', '.js-more-help-button', function(e) {
+ $(this).remove();
+ $('.hidden-shortcut').show();
+ return e.preventDefault();
+});
+
+Mousetrap.stopCallback = (function() {
+ var defaultStopCallback;
+ defaultStopCallback = Mousetrap.stopCallback;
+ return function(e, element, combo) {
+ if (['ctrl+shift+p', 'command+shift+p'].indexOf(combo) !== -1) {
+ return false;
+ } else {
+ return defaultStopCallback.apply(this, arguments);
+ }
+ };
+})();
diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee
deleted file mode 100644
index 8c8689bacee..00000000000
--- a/app/assets/javascripts/shortcuts.js.coffee
+++ /dev/null
@@ -1,60 +0,0 @@
-class @Shortcuts
- constructor: (skipResetBindings) ->
- @enabledHelp = []
- Mousetrap.reset() if not skipResetBindings
- Mousetrap.bind '?', @onToggleHelp
- Mousetrap.bind 's', Shortcuts.focusSearch
- Mousetrap.bind 'f', (e) => @focusFilter e
- Mousetrap.bind ['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview
- Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL?
-
- onToggleHelp: (e) =>
- e.preventDefault()
- Shortcuts.toggleHelp(@enabledHelp)
-
- toggleMarkdownPreview: (e) ->
- $(document).triggerHandler('markdown-preview:toggle', [e])
-
- @toggleHelp: (location) ->
- $modal = $('#modal-shortcuts')
-
- if $modal.length
- $modal.modal('toggle')
- return
-
- $.ajax(
- url: gon.shortcuts_path,
- dataType: 'script',
- success: (e) ->
- if location and location.length > 0
- $(l).show() for l in location
- else
- $('.hidden-shortcut').show()
- $('.js-more-help-button').remove()
- )
-
- focusFilter: (e) ->
- @filterInput ?= $('input[type=search]', '.nav-controls')
- @filterInput.focus()
- e.preventDefault()
-
- @focusSearch: (e) ->
- $('#search').focus()
- e.preventDefault()
-
-
-$(document).on 'click.more_help', '.js-more-help-button', (e) ->
- $(@).remove()
- $('.hidden-shortcut').show()
- e.preventDefault()
-
-Mousetrap.stopCallback = (->
- defaultStopCallback = Mousetrap.stopCallback
-
- return (e, element, combo) ->
- # allowed shortcuts if textarea, input, contenteditable are focused
- if ['ctrl+shift+p', 'command+shift+p'].indexOf(combo) != -1
- return false
- else
- return defaultStopCallback.apply(@, arguments)
-)()
diff --git a/app/assets/javascripts/shortcuts_blob.coffee b/app/assets/javascripts/shortcuts_blob.coffee
deleted file mode 100644
index 6d21e5d1150..00000000000
--- a/app/assets/javascripts/shortcuts_blob.coffee
+++ /dev/null
@@ -1,10 +0,0 @@
-#= require shortcuts
-
-class @ShortcutsBlob extends Shortcuts
- constructor: (skipResetBindings) ->
- super skipResetBindings
- Mousetrap.bind('y', ShortcutsBlob.copyToClipboard)
-
- @copyToClipboard: ->
- clipboardButton = $('.btn-clipboard')
- clipboardButton.click() if clipboardButton
diff --git a/app/assets/javascripts/shortcuts_blob.js b/app/assets/javascripts/shortcuts_blob.js
new file mode 100644
index 00000000000..94e84dea51d
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_blob.js
@@ -0,0 +1,24 @@
+
+/*= require shortcuts */
+var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+this.ShortcutsBlob = (function(superClass) {
+ extend(ShortcutsBlob, superClass);
+
+ function ShortcutsBlob(skipResetBindings) {
+ ShortcutsBlob.__super__.constructor.call(this, skipResetBindings);
+ Mousetrap.bind('y', ShortcutsBlob.copyToClipboard);
+ }
+
+ ShortcutsBlob.copyToClipboard = function() {
+ var clipboardButton;
+ clipboardButton = $('.btn-clipboard');
+ if (clipboardButton) {
+ return clipboardButton.click();
+ }
+ };
+
+ return ShortcutsBlob;
+
+})(Shortcuts);
diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/shortcuts_dashboard_navigation.js
new file mode 100644
index 00000000000..990a353fa41
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js
@@ -0,0 +1,35 @@
+
+/*= require shortcuts */
+var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+this.ShortcutsDashboardNavigation = (function(superClass) {
+ extend(ShortcutsDashboardNavigation, superClass);
+
+ function ShortcutsDashboardNavigation() {
+ ShortcutsDashboardNavigation.__super__.constructor.call(this);
+ Mousetrap.bind('g a', function() {
+ return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-activity');
+ });
+ Mousetrap.bind('g i', function() {
+ return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-issues');
+ });
+ Mousetrap.bind('g m', function() {
+ return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests');
+ });
+ Mousetrap.bind('g p', function() {
+ return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects');
+ });
+ }
+
+ ShortcutsDashboardNavigation.findAndFollowLink = function(selector) {
+ var link;
+ link = $(selector).attr('href');
+ if (link) {
+ return window.location = link;
+ }
+ };
+
+ return ShortcutsDashboardNavigation;
+
+})(Shortcuts);
diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee b/app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee
deleted file mode 100644
index cca2b8a1fcc..00000000000
--- a/app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee
+++ /dev/null
@@ -1,14 +0,0 @@
-#= require shortcuts
-
-class @ShortcutsDashboardNavigation extends Shortcuts
- constructor: ->
- super()
- Mousetrap.bind('g a', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-activity'))
- Mousetrap.bind('g i', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-issues'))
- Mousetrap.bind('g m', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests'))
- Mousetrap.bind('g p', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects'))
-
- @findAndFollowLink: (selector) ->
- link = $(selector).attr('href')
- if link
- window.location = link
diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/shortcuts_find_file.js
new file mode 100644
index 00000000000..77b5829d2d7
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_find_file.js
@@ -0,0 +1,31 @@
+
+/*= require shortcuts_navigation */
+var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+this.ShortcutsFindFile = (function(superClass) {
+ extend(ShortcutsFindFile, superClass);
+
+ function ShortcutsFindFile(projectFindFile) {
+ var _oldStopCallback;
+ this.projectFindFile = projectFindFile;
+ ShortcutsFindFile.__super__.constructor.call(this);
+ _oldStopCallback = Mousetrap.stopCallback;
+ Mousetrap.stopCallback = (function(_this) {
+ return function(event, element, combo) {
+ if (element === _this.projectFindFile.inputElement[0] && (combo === 'up' || combo === 'down' || combo === 'esc' || combo === 'enter')) {
+ event.preventDefault();
+ return false;
+ }
+ return _oldStopCallback(event, element, combo);
+ };
+ })(this);
+ Mousetrap.bind('up', this.projectFindFile.selectRowUp);
+ Mousetrap.bind('down', this.projectFindFile.selectRowDown);
+ Mousetrap.bind('esc', this.projectFindFile.goToTree);
+ Mousetrap.bind('enter', this.projectFindFile.goToBlob);
+ }
+
+ return ShortcutsFindFile;
+
+})(ShortcutsNavigation);
diff --git a/app/assets/javascripts/shortcuts_find_file.js.coffee b/app/assets/javascripts/shortcuts_find_file.js.coffee
deleted file mode 100644
index 311e80bae19..00000000000
--- a/app/assets/javascripts/shortcuts_find_file.js.coffee
+++ /dev/null
@@ -1,19 +0,0 @@
-#= require shortcuts_navigation
-
-class @ShortcutsFindFile extends ShortcutsNavigation
- constructor: (@projectFindFile) ->
- super()
- _oldStopCallback = Mousetrap.stopCallback
- # override to fire shortcuts action when focus in textbox
- Mousetrap.stopCallback = (event, element, combo) =>
- if element == @projectFindFile.inputElement[0] and (combo == 'up' or combo == 'down' or combo == 'esc' or combo == 'enter')
- # when press up/down key in textbox, cusor prevent to move to home/end
- event.preventDefault()
- return false
-
- return _oldStopCallback(event, element, combo)
-
- Mousetrap.bind('up', @projectFindFile.selectRowUp)
- Mousetrap.bind('down', @projectFindFile.selectRowDown)
- Mousetrap.bind('esc', @projectFindFile.goToTree)
- Mousetrap.bind('enter', @projectFindFile.goToBlob)
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
deleted file mode 100644
index c93bcf3ceec..00000000000
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ /dev/null
@@ -1,53 +0,0 @@
-#= require mousetrap
-#= require shortcuts_navigation
-
-class @ShortcutsIssuable extends ShortcutsNavigation
- constructor: (isMergeRequest) ->
- super()
- Mousetrap.bind('a', @openSidebarDropdown.bind(@, 'assignee'))
- Mousetrap.bind('m', @openSidebarDropdown.bind(@, 'milestone'))
- Mousetrap.bind('r', =>
- @replyWithSelectedText()
- return false
- )
- Mousetrap.bind('e', =>
- @editIssue()
- return false
- )
- Mousetrap.bind('l', @openSidebarDropdown.bind(@, 'labels'))
-
- if isMergeRequest
- @enabledHelp.push('.hidden-shortcut.merge_requests')
- else
- @enabledHelp.push('.hidden-shortcut.issues')
-
- replyWithSelectedText: ->
- if window.getSelection
- selected = window.getSelection().toString()
- replyField = $('.js-main-target-form #note_note')
-
- return if selected.trim() == ""
-
- # Put a '>' character before each non-empty line in the selection
- quote = _.map selected.split("\n"), (val) ->
- "> #{val}\n" if val.trim() != ''
-
- # If replyField already has some content, add a newline before our quote
- separator = replyField.val().trim() != "" and "\n" or ''
-
- replyField.val (_, current) ->
- current + separator + quote.join('') + "\n"
-
- # Trigger autosave for the added text
- replyField.trigger('input')
-
- # Focus the input field
- replyField.focus()
-
- editIssue: ->
- $editBtn = $('.issuable-edit')
- Turbolinks.visit($editBtn.attr('href'))
-
- openSidebarDropdown: (name) ->
- sidebar.openDropdown(name)
- return false
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
new file mode 100644
index 00000000000..5f62cee9306
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -0,0 +1,70 @@
+
+/*= require mousetrap */
+
+/*= require shortcuts_navigation */
+var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+this.ShortcutsIssuable = (function(superClass) {
+ extend(ShortcutsIssuable, superClass);
+
+ function ShortcutsIssuable(isMergeRequest) {
+ ShortcutsIssuable.__super__.constructor.call(this);
+ Mousetrap.bind('a', this.openSidebarDropdown.bind(this, 'assignee'));
+ Mousetrap.bind('m', this.openSidebarDropdown.bind(this, 'milestone'));
+ Mousetrap.bind('r', (function(_this) {
+ return function() {
+ _this.replyWithSelectedText();
+ return false;
+ };
+ })(this));
+ Mousetrap.bind('e', (function(_this) {
+ return function() {
+ _this.editIssue();
+ return false;
+ };
+ })(this));
+ Mousetrap.bind('l', this.openSidebarDropdown.bind(this, 'labels'));
+ if (isMergeRequest) {
+ this.enabledHelp.push('.hidden-shortcut.merge_requests');
+ } else {
+ this.enabledHelp.push('.hidden-shortcut.issues');
+ }
+ }
+
+ ShortcutsIssuable.prototype.replyWithSelectedText = function() {
+ var quote, replyField, selected, separator;
+ if (window.getSelection) {
+ selected = window.getSelection().toString();
+ replyField = $('.js-main-target-form #note_note');
+ if (selected.trim() === "") {
+ return;
+ }
+ quote = _.map(selected.split("\n"), function(val) {
+ if (val.trim() !== '') {
+ return "> " + val + "\n";
+ }
+ });
+ separator = replyField.val().trim() !== "" && "\n" || '';
+ replyField.val(function(_, current) {
+ return current + separator + quote.join('') + "\n";
+ });
+ replyField.trigger('input');
+ return replyField.focus();
+ }
+ };
+
+ ShortcutsIssuable.prototype.editIssue = function() {
+ var $editBtn;
+ $editBtn = $('.issuable-edit');
+ return Turbolinks.visit($editBtn.attr('href'));
+ };
+
+ ShortcutsIssuable.prototype.openSidebarDropdown = function(name) {
+ sidebar.openDropdown(name);
+ return false;
+ };
+
+ return ShortcutsIssuable;
+
+})(ShortcutsNavigation);
diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee
deleted file mode 100644
index f39504e0645..00000000000
--- a/app/assets/javascripts/shortcuts_navigation.coffee
+++ /dev/null
@@ -1,23 +0,0 @@
-#= require shortcuts
-
-class @ShortcutsNavigation extends Shortcuts
- constructor: ->
- super()
- Mousetrap.bind('g p', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project'))
- Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'))
- Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'))
- Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'))
- Mousetrap.bind('g b', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-builds'))
- Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network'))
- Mousetrap.bind('g g', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs'))
- Mousetrap.bind('g i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'))
- Mousetrap.bind('g m', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'))
- Mousetrap.bind('g w', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki'))
- Mousetrap.bind('g s', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets'))
- Mousetrap.bind('i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue'))
- @enabledHelp.push('.hidden-shortcut.project')
-
- @findAndFollowLink: (selector) ->
- link = $(selector).attr('href')
- if link
- window.location = link
diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js
new file mode 100644
index 00000000000..2f8c6567ce1
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_navigation.js
@@ -0,0 +1,60 @@
+
+/*= require shortcuts */
+var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+this.ShortcutsNavigation = (function(superClass) {
+ extend(ShortcutsNavigation, superClass);
+
+ function ShortcutsNavigation() {
+ ShortcutsNavigation.__super__.constructor.call(this);
+ Mousetrap.bind('g p', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-project');
+ });
+ Mousetrap.bind('g e', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity');
+ });
+ Mousetrap.bind('g f', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree');
+ });
+ Mousetrap.bind('g c', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-commits');
+ });
+ Mousetrap.bind('g b', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-builds');
+ });
+ Mousetrap.bind('g n', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-network');
+ });
+ Mousetrap.bind('g g', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs');
+ });
+ Mousetrap.bind('g i', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues');
+ });
+ Mousetrap.bind('g m', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests');
+ });
+ Mousetrap.bind('g w', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki');
+ });
+ Mousetrap.bind('g s', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets');
+ });
+ Mousetrap.bind('i', function() {
+ return ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue');
+ });
+ this.enabledHelp.push('.hidden-shortcut.project');
+ }
+
+ ShortcutsNavigation.findAndFollowLink = function(selector) {
+ var link;
+ link = $(selector).attr('href');
+ if (link) {
+ return window.location = link;
+ }
+ };
+
+ return ShortcutsNavigation;
+
+})(Shortcuts);
diff --git a/app/assets/javascripts/shortcuts_network.js b/app/assets/javascripts/shortcuts_network.js
new file mode 100644
index 00000000000..504f3adc706
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_network.js
@@ -0,0 +1,23 @@
+
+/*= require shortcuts_navigation */
+var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+this.ShortcutsNetwork = (function(superClass) {
+ extend(ShortcutsNetwork, superClass);
+
+ function ShortcutsNetwork(graph) {
+ this.graph = graph;
+ ShortcutsNetwork.__super__.constructor.call(this);
+ Mousetrap.bind(['left', 'h'], this.graph.scrollLeft);
+ Mousetrap.bind(['right', 'l'], this.graph.scrollRight);
+ Mousetrap.bind(['up', 'k'], this.graph.scrollUp);
+ Mousetrap.bind(['down', 'j'], this.graph.scrollDown);
+ Mousetrap.bind(['shift+up', 'shift+k'], this.graph.scrollTop);
+ Mousetrap.bind(['shift+down', 'shift+j'], this.graph.scrollBottom);
+ this.enabledHelp.push('.hidden-shortcut.network');
+ }
+
+ return ShortcutsNetwork;
+
+})(ShortcutsNavigation);
diff --git a/app/assets/javascripts/shortcuts_network.js.coffee b/app/assets/javascripts/shortcuts_network.js.coffee
deleted file mode 100644
index cc95ad7ebfe..00000000000
--- a/app/assets/javascripts/shortcuts_network.js.coffee
+++ /dev/null
@@ -1,12 +0,0 @@
-#= require shortcuts_navigation
-
-class @ShortcutsNetwork extends ShortcutsNavigation
- constructor: (@graph) ->
- super()
- Mousetrap.bind(['left', 'h'], @graph.scrollLeft)
- Mousetrap.bind(['right', 'l'], @graph.scrollRight)
- Mousetrap.bind(['up', 'k'], @graph.scrollUp)
- Mousetrap.bind(['down', 'j'], @graph.scrollDown)
- Mousetrap.bind(['shift+up', 'shift+k'], @graph.scrollTop)
- Mousetrap.bind(['shift+down', 'shift+j'], @graph.scrollBottom)
- @enabledHelp.push('.hidden-shortcut.network')
diff --git a/app/assets/javascripts/sidebar.js b/app/assets/javascripts/sidebar.js
new file mode 100644
index 00000000000..27420e56fa7
--- /dev/null
+++ b/app/assets/javascripts/sidebar.js
@@ -0,0 +1,38 @@
+var collapsed, expanded, toggleSidebar;
+
+collapsed = 'page-sidebar-collapsed';
+
+expanded = 'page-sidebar-expanded';
+
+toggleSidebar = function() {
+ $('.page-with-sidebar').toggleClass(collapsed + " " + expanded);
+ $('.navbar-fixed-top').toggleClass("header-collapsed header-expanded");
+ if ($.cookie('pin_nav') === 'true') {
+ $('.navbar-fixed-top').toggleClass('header-pinned-nav');
+ $('.page-with-sidebar').toggleClass('page-sidebar-pinned');
+ }
+ return setTimeout((function() {
+ var niceScrollBars;
+ niceScrollBars = $('.nav-sidebar').niceScroll();
+ return niceScrollBars.updateScrollBar();
+ }), 300);
+};
+
+$(document).off('click', 'body').on('click', 'body', function(e) {
+ var $nav, $target, $toggle, pageExpanded;
+ if ($.cookie('pin_nav') !== 'true') {
+ $target = $(e.target);
+ $nav = $target.closest('.sidebar-wrapper');
+ pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded');
+ $toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle');
+ if ($nav.length === 0 && pageExpanded && $toggle.length === 0) {
+ $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded');
+ return $('.navbar-fixed-top').toggleClass('header-collapsed header-expanded');
+ }
+ }
+});
+
+$(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', function(e) {
+ e.preventDefault();
+ return toggleSidebar();
+});
diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee
deleted file mode 100644
index 68009e58645..00000000000
--- a/app/assets/javascripts/sidebar.js.coffee
+++ /dev/null
@@ -1,37 +0,0 @@
-collapsed = 'page-sidebar-collapsed'
-expanded = 'page-sidebar-expanded'
-
-toggleSidebar = ->
- $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
- $('.navbar-fixed-top').toggleClass("header-collapsed header-expanded")
-
- if $.cookie('pin_nav') is 'true'
- $('.navbar-fixed-top').toggleClass('header-pinned-nav')
- $('.page-with-sidebar').toggleClass('page-sidebar-pinned')
-
- setTimeout ( ->
- niceScrollBars = $('.nav-sidebar').niceScroll();
- niceScrollBars.updateScrollBar();
- ), 300
-
-$(document)
- .off 'click', 'body'
- .on 'click', 'body', (e) ->
- unless $.cookie('pin_nav') is 'true'
- $target = $(e.target)
- $nav = $target.closest('.sidebar-wrapper')
- pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded')
- $toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle')
-
- if $nav.length is 0 and pageExpanded and $toggle.length is 0
- $('.page-with-sidebar')
- .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
-
- $('.navbar-fixed-top')
- .toggleClass('header-collapsed header-expanded')
-
-$(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) ->
- e.preventDefault()
-
- toggleSidebar()
-)
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
new file mode 100644
index 00000000000..4a824e212b3
--- /dev/null
+++ b/app/assets/javascripts/single_file_diff.js
@@ -0,0 +1,74 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.SingleFileDiff = (function() {
+ var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
+
+ WRAPPER = '<div class="diff-content diff-wrap-lines"></div>';
+
+ LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
+
+ ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
+
+ COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. Click to expand it.</div>';
+
+ function SingleFileDiff(file) {
+ this.file = file;
+ this.toggleDiff = bind(this.toggleDiff, this);
+ this.content = $('.diff-content', this.file);
+ this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
+ this.isOpen = !this.diffForPath;
+ if (this.diffForPath) {
+ this.collapsedContent = this.content;
+ this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
+ this.content = null;
+ this.collapsedContent.after(this.loadingContent);
+ } else {
+ this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
+ this.content.after(this.collapsedContent);
+ }
+ this.collapsedContent.on('click', this.toggleDiff);
+ $('.file-title > a', this.file).on('click', this.toggleDiff);
+ }
+
+ SingleFileDiff.prototype.toggleDiff = function(e) {
+ this.isOpen = !this.isOpen;
+ if (!this.isOpen && !this.hasError) {
+ this.content.hide();
+ return this.collapsedContent.show();
+ } else if (this.content) {
+ this.collapsedContent.hide();
+ return this.content.show();
+ } else {
+ return this.getContentHTML();
+ }
+ };
+
+ SingleFileDiff.prototype.getContentHTML = function() {
+ this.collapsedContent.hide();
+ this.loadingContent.show();
+ $.get(this.diffForPath, (function(_this) {
+ return function(data) {
+ _this.loadingContent.hide();
+ if (data.html) {
+ _this.content = $(data.html);
+ _this.content.syntaxHighlight();
+ } else {
+ _this.hasError = true;
+ _this.content = $(ERROR_HTML);
+ }
+ return _this.collapsedContent.after(_this.content);
+ };
+ })(this));
+ };
+
+ return SingleFileDiff;
+
+})();
+
+$.fn.singleFileDiff = function() {
+ return this.each(function() {
+ if (!$.data(this, 'singleFileDiff')) {
+ return $.data(this, 'singleFileDiff', new SingleFileDiff(this));
+ }
+ });
+};
diff --git a/app/assets/javascripts/single_file_diff.js.coffee b/app/assets/javascripts/single_file_diff.js.coffee
deleted file mode 100644
index f3e225c3728..00000000000
--- a/app/assets/javascripts/single_file_diff.js.coffee
+++ /dev/null
@@ -1,54 +0,0 @@
-class @SingleFileDiff
-
- WRAPPER = '<div class="diff-content diff-wrap-lines"></div>'
- LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'
- ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'
- COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. Click to expand it.</div>'
-
- constructor: (@file) ->
- @content = $('.diff-content', @file)
- @diffForPath = @content.find('[data-diff-for-path]').data 'diff-for-path'
- @isOpen = !@diffForPath
-
- if @diffForPath
- @collapsedContent = @content
- @loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide()
- @content = null
- @collapsedContent.after(@loadingContent)
- else
- @collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide()
- @content.after(@collapsedContent)
-
- @collapsedContent.on 'click', @toggleDiff
-
- $('.file-title > a', @file).on 'click', @toggleDiff
-
- toggleDiff: (e) =>
- @isOpen = !@isOpen
- if not @isOpen and not @hasError
- @content.hide()
- @collapsedContent.show()
- else if @content
- @collapsedContent.hide()
- @content.show()
- else
- @getContentHTML()
-
- getContentHTML: ->
- @collapsedContent.hide()
- @loadingContent.show()
- $.get @diffForPath, (data) =>
- @loadingContent.hide()
- if data.html
- @content = $(data.html)
- @content.syntaxHighlight()
- else
- @hasError = true
- @content = $(ERROR_HTML)
- @collapsedContent.after(@content)
- return
-
-$.fn.singleFileDiff = ->
- return @each ->
- if not $.data this, 'singleFileDiff'
- $.data this, 'singleFileDiff', new SingleFileDiff this
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
new file mode 100644
index 00000000000..4264413199b
--- /dev/null
+++ b/app/assets/javascripts/star.js
@@ -0,0 +1,28 @@
+this.Star = (function() {
+ function Star() {
+ $('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
+ var $starIcon, $starSpan, $this, toggleStar;
+ $this = $(this);
+ $starSpan = $this.find('span');
+ $starIcon = $this.find('i');
+ toggleStar = function(isStarred) {
+ $this.parent().find('.star-count').text(data.star_count);
+ if (isStarred) {
+ $starSpan.removeClass('starred').text('Star');
+ gl.utils.updateTooltipTitle($this, 'Star project');
+ $starIcon.removeClass('fa-star').addClass('fa-star-o');
+ } else {
+ $starSpan.addClass('starred').text('Unstar');
+ gl.utils.updateTooltipTitle($this, 'Unstar project');
+ $starIcon.removeClass('fa-star-o').addClass('fa-star');
+ }
+ };
+ toggleStar($starSpan.hasClass('starred'));
+ }).on('ajax:error', function(e, xhr, status, error) {
+ new Flash('Star toggle failed. Try again later.', 'alert');
+ });
+ }
+
+ return Star;
+
+})();
diff --git a/app/assets/javascripts/star.js.coffee b/app/assets/javascripts/star.js.coffee
deleted file mode 100644
index 01b28171f72..00000000000
--- a/app/assets/javascripts/star.js.coffee
+++ /dev/null
@@ -1,24 +0,0 @@
-class @Star
- constructor: ->
- $('.project-home-panel .toggle-star').on('ajax:success', (e, data, status, xhr) ->
- $this = $(this)
- $starSpan = $this.find('span')
- $starIcon = $this.find('i')
-
- toggleStar = (isStarred) ->
- $this.parent().find('.star-count').text data.star_count
- if isStarred
- $starSpan.removeClass('starred').text 'Star'
- gl.utils.updateTooltipTitle $this, 'Star project'
- $starIcon.removeClass('fa-star').addClass 'fa-star-o'
- else
- $starSpan.addClass('starred').text 'Unstar'
- gl.utils.updateTooltipTitle $this, 'Unstar project'
- $starIcon.removeClass('fa-star-o').addClass 'fa-star'
- return
-
- toggleStar $starSpan.hasClass('starred')
- return
- ).on 'ajax:error', (e, xhr, status, error) ->
- new Flash('Star toggle failed. Try again later.', 'alert')
- return
diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js
new file mode 100644
index 00000000000..8831e69b04e
--- /dev/null
+++ b/app/assets/javascripts/subscription.js
@@ -0,0 +1,38 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.Subscription = (function() {
+ function Subscription(container) {
+ this.toggleSubscription = bind(this.toggleSubscription, this);
+ var $container;
+ $container = $(container);
+ this.url = $container.attr('data-url');
+ this.subscribe_button = $container.find('.js-subscribe-button');
+ this.subscription_status = $container.find('.subscription-status');
+ this.subscribe_button.unbind('click').click(this.toggleSubscription);
+ }
+
+ Subscription.prototype.toggleSubscription = function(event) {
+ var action, btn, current_status;
+ btn = $(event.currentTarget);
+ action = btn.find('span').text();
+ current_status = this.subscription_status.attr('data-status');
+ btn.addClass('disabled');
+ return $.post(this.url, (function(_this) {
+ return function() {
+ var status;
+ btn.removeClass('disabled');
+ status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed';
+ _this.subscription_status.attr('data-status', status);
+ action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe';
+ btn.find('span').text(action);
+ _this.subscription_status.find('>div').toggleClass('hidden');
+ if (btn.attr('data-original-title')) {
+ return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle');
+ }
+ };
+ })(this));
+ };
+
+ return Subscription;
+
+})();
diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee
deleted file mode 100644
index 08d494aba9f..00000000000
--- a/app/assets/javascripts/subscription.js.coffee
+++ /dev/null
@@ -1,26 +0,0 @@
-class @Subscription
- constructor: (container) ->
- $container = $(container)
- @url = $container.attr('data-url')
- @subscribe_button = $container.find('.js-subscribe-button')
- @subscription_status = $container.find('.subscription-status')
- @subscribe_button.unbind('click').click(@toggleSubscription)
-
- toggleSubscription: (event) =>
- btn = $(event.currentTarget)
- action = btn.find('span').text()
- current_status = @subscription_status.attr('data-status')
- btn.addClass('disabled')
-
- $.post @url, =>
- btn.removeClass('disabled')
- status = if current_status == 'subscribed' then 'unsubscribed' else 'subscribed'
- @subscription_status.attr('data-status', status)
- action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe'
- btn.find('span').text(action)
- @subscription_status.find('>div').toggleClass('hidden')
-
- if btn.attr('data-original-title')
- btn.tooltip('hide')
- .attr('data-original-title', action)
- .tooltip('fixTitle')
diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js
new file mode 100644
index 00000000000..2368af8398f
--- /dev/null
+++ b/app/assets/javascripts/subscription_select.js
@@ -0,0 +1,32 @@
+this.SubscriptionSelect = (function() {
+ function SubscriptionSelect() {
+ $('.js-subscription-event').each(function(i, el) {
+ var fieldName;
+ fieldName = $(el).data("field-name");
+ return $(el).glDropdown({
+ selectable: true,
+ fieldName: fieldName,
+ toggleLabel: (function(_this) {
+ return function(selected, el, instance) {
+ var $item, label;
+ label = 'Subscription';
+ $item = instance.dropdown.find('.is-active');
+ if ($item.length) {
+ label = $item.text();
+ }
+ return label;
+ };
+ })(this),
+ clicked: function(item, $el, e) {
+ return e.preventDefault();
+ },
+ id: function(obj, el) {
+ return $(el).data("id");
+ }
+ });
+ });
+ }
+
+ return SubscriptionSelect;
+
+})();
diff --git a/app/assets/javascripts/subscription_select.js.coffee b/app/assets/javascripts/subscription_select.js.coffee
deleted file mode 100644
index e5eb7a50d80..00000000000
--- a/app/assets/javascripts/subscription_select.js.coffee
+++ /dev/null
@@ -1,18 +0,0 @@
-class @SubscriptionSelect
- constructor: ->
- $('.js-subscription-event').each (i, el) ->
- fieldName = $(el).data("field-name")
-
- $(el).glDropdown(
- selectable: true
- fieldName: fieldName
- toggleLabel: (selected, el, instance) =>
- label = 'Subscription'
- $item = instance.dropdown.find('.is-active')
- label = $item.text() if $item.length
- label
- clicked: (item, $el, e)->
- e.preventDefault()
- id: (obj, el) ->
- $(el).data("id")
- )
diff --git a/app/assets/javascripts/syntax_highlight.coffee b/app/assets/javascripts/syntax_highlight.coffee
deleted file mode 100644
index 980f0232d10..00000000000
--- a/app/assets/javascripts/syntax_highlight.coffee
+++ /dev/null
@@ -1,20 +0,0 @@
-# Syntax Highlighter
-#
-# Applies a syntax highlighting color scheme CSS class to any element with the
-# `js-syntax-highlight` class
-#
-# ### Example Markup
-#
-# <div class="js-syntax-highlight"></div>
-#
-$.fn.syntaxHighlight = ->
- if $(this).hasClass('js-syntax-highlight')
- # Given the element itself, apply highlighting
- $(this).addClass(gon.user_color_scheme)
- else
- # Given a parent element, recurse to any of its applicable children
- $children = $(this).find('.js-syntax-highlight')
- $children.syntaxHighlight() if $children.length
-
-$(document).on 'ready page:load', ->
- $('.js-syntax-highlight').syntaxHighlight()
diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js
new file mode 100644
index 00000000000..5e71f3cb7fd
--- /dev/null
+++ b/app/assets/javascripts/syntax_highlight.js
@@ -0,0 +1,15 @@
+$.fn.syntaxHighlight = function() {
+ var $children;
+ if ($(this).hasClass('js-syntax-highlight')) {
+ return $(this).addClass(gon.user_color_scheme);
+ } else {
+ $children = $(this).find('.js-syntax-highlight');
+ if ($children.length) {
+ return $children.syntaxHighlight();
+ }
+ }
+};
+
+$(document).on('ready page:load', function() {
+ return $('.js-syntax-highlight').syntaxHighlight();
+});
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
new file mode 100644
index 00000000000..d69fed72d3a
--- /dev/null
+++ b/app/assets/javascripts/todos.js
@@ -0,0 +1,141 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.Todos = (function() {
+ function Todos(opts) {
+ var ref;
+ if (opts == null) {
+ opts = {};
+ }
+ this.allDoneClicked = bind(this.allDoneClicked, this);
+ this.doneClicked = bind(this.doneClicked, this);
+ this.el = (ref = opts.el) != null ? ref : $('.js-todos-options');
+ this.perPage = this.el.data('perPage');
+ this.clearListeners();
+ this.initBtnListeners();
+ }
+
+ Todos.prototype.clearListeners = function() {
+ $('.done-todo').off('click');
+ $('.js-todos-mark-all').off('click');
+ return $('.todo').off('click');
+ };
+
+ Todos.prototype.initBtnListeners = function() {
+ $('.done-todo').on('click', this.doneClicked);
+ $('.js-todos-mark-all').on('click', this.allDoneClicked);
+ return $('.todo').on('click', this.goToTodoUrl);
+ };
+
+ Todos.prototype.doneClicked = function(e) {
+ var $this;
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ $this = $(e.currentTarget);
+ $this.disable();
+ return $.ajax({
+ type: 'POST',
+ url: $this.attr('href'),
+ dataType: 'json',
+ data: {
+ '_method': 'delete'
+ },
+ success: (function(_this) {
+ return function(data) {
+ _this.redirectIfNeeded(data.count);
+ _this.clearDone($this.closest('li'));
+ return _this.updateBadges(data);
+ };
+ })(this)
+ });
+ };
+
+ Todos.prototype.allDoneClicked = function(e) {
+ var $this;
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ $this = $(e.currentTarget);
+ $this.disable();
+ return $.ajax({
+ type: 'POST',
+ url: $this.attr('href'),
+ dataType: 'json',
+ data: {
+ '_method': 'delete'
+ },
+ success: (function(_this) {
+ return function(data) {
+ $this.remove();
+ $('.js-todos-list').remove();
+ return _this.updateBadges(data);
+ };
+ })(this)
+ });
+ };
+
+ Todos.prototype.clearDone = function($row) {
+ var $ul;
+ $ul = $row.closest('ul');
+ $row.remove();
+ if (!$ul.find('li').length) {
+ return $ul.parents('.panel').remove();
+ }
+ };
+
+ Todos.prototype.updateBadges = function(data) {
+ $('.todos-pending .badge, .todos-pending-count').text(data.count);
+ return $('.todos-done .badge').text(data.done_count);
+ };
+
+ Todos.prototype.getTotalPages = function() {
+ return this.el.data('totalPages');
+ };
+
+ Todos.prototype.getCurrentPage = function() {
+ return this.el.data('currentPage');
+ };
+
+ Todos.prototype.getTodosPerPage = function() {
+ return this.el.data('perPage');
+ };
+
+ Todos.prototype.redirectIfNeeded = function(total) {
+ var currPage, currPages, newPages, pageParams, url;
+ currPages = this.getTotalPages();
+ currPage = this.getCurrentPage();
+ if (!total) {
+ location.reload();
+ return;
+ }
+ if (!currPages) {
+ return;
+ }
+ newPages = Math.ceil(total / this.getTodosPerPage());
+ url = location.href;
+ if (newPages !== currPages) {
+ if (currPages > 1 && currPage === currPages) {
+ pageParams = {
+ page: currPages - 1
+ };
+ url = gl.utils.mergeUrlParams(pageParams, url);
+ }
+ return Turbolinks.visit(url);
+ }
+ };
+
+ Todos.prototype.goToTodoUrl = function(e) {
+ var todoLink;
+ todoLink = $(this).data('url');
+ if (!todoLink) {
+ return;
+ }
+ if (e.metaKey || e.which === 2) {
+ e.preventDefault();
+ return window.open(todoLink, '_blank');
+ } else {
+ return Turbolinks.visit(todoLink);
+ }
+ };
+
+ return Todos;
+
+})();
diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee
deleted file mode 100644
index 10bef96f43d..00000000000
--- a/app/assets/javascripts/todos.js.coffee
+++ /dev/null
@@ -1,110 +0,0 @@
-class @Todos
- constructor: (opts = {}) ->
- {
- @el = $('.js-todos-options')
- } = opts
-
- @perPage = @el.data('perPage')
-
- @clearListeners()
- @initBtnListeners()
-
- clearListeners: ->
- $('.done-todo').off('click')
- $('.js-todos-mark-all').off('click')
- $('.todo').off('click')
-
- initBtnListeners: ->
- $('.done-todo').on('click', @doneClicked)
- $('.js-todos-mark-all').on('click', @allDoneClicked)
- $('.todo').on('click', @goToTodoUrl)
-
- doneClicked: (e) =>
- e.preventDefault()
- e.stopImmediatePropagation()
-
- $this = $(e.currentTarget)
- $this.disable()
-
- $.ajax
- type: 'POST'
- url: $this.attr('href')
- dataType: 'json'
- data: '_method': 'delete'
- success: (data) =>
- @redirectIfNeeded data.count
- @clearDone $this.closest('li')
- @updateBadges data
-
- allDoneClicked: (e) =>
- e.preventDefault()
- e.stopImmediatePropagation()
-
- $this = $(e.currentTarget)
- $this.disable()
-
- $.ajax
- type: 'POST'
- url: $this.attr('href')
- dataType: 'json'
- data: '_method': 'delete'
- success: (data) =>
- $this.remove()
- $('.js-todos-list').remove()
- @updateBadges data
-
- clearDone: ($row) ->
- $ul = $row.closest('ul')
- $row.remove()
-
- if not $ul.find('li').length
- $ul.parents('.panel').remove()
-
- updateBadges: (data) ->
- $('.todos-pending .badge, .todos-pending-count').text data.count
- $('.todos-done .badge').text data.done_count
-
- getTotalPages: ->
- @el.data('totalPages')
-
- getCurrentPage: ->
- @el.data('currentPage')
-
- getTodosPerPage: ->
- @el.data('perPage')
-
- redirectIfNeeded: (total) ->
- currPages = @getTotalPages()
- currPage = @getCurrentPage()
-
- # Refresh if no remaining Todos
- if not total
- location.reload()
- return
-
- # Do nothing if no pagination
- return if not currPages
-
- newPages = Math.ceil(total / @getTodosPerPage())
- url = location.href # Includes query strings
-
- # If new total of pages is different than we have now
- if newPages isnt currPages
- # Redirect to previous page if there's one available
- if currPages > 1 and currPage is currPages
- pageParams =
- page: currPages - 1
- url = gl.utils.mergeUrlParams(pageParams, url)
-
- Turbolinks.visit(url)
-
- goToTodoUrl: (e)->
- todoLink = $(this).data('url')
- return unless todoLink
-
- # Allow Meta-Click or Mouse3-click to open in a new tab
- if e.metaKey or e.which is 2
- e.preventDefault()
- window.open(todoLink,'_blank')
- else
- Turbolinks.visit(todoLink)
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
new file mode 100644
index 00000000000..e3479aa0913
--- /dev/null
+++ b/app/assets/javascripts/tree.js
@@ -0,0 +1,62 @@
+this.TreeView = (function() {
+ function TreeView() {
+ this.initKeyNav();
+ $(".tree-content-holder .tree-item").on('click', function(e) {
+ var $clickedEl, path;
+ $clickedEl = $(e.target);
+ path = $('.tree-item-file-name a', this).attr('href');
+ if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
+ if (e.metaKey || e.which === 2) {
+ e.preventDefault();
+ return window.open(path, '_blank');
+ } else {
+ return Turbolinks.visit(path);
+ }
+ }
+ });
+ $('span.log_loading:first').removeClass('hide');
+ }
+
+ TreeView.prototype.initKeyNav = function() {
+ var li, liSelected;
+ li = $("tr.tree-item");
+ liSelected = null;
+ return $('body').keydown(function(e) {
+ var next, path;
+ if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
+ return false;
+ }
+ if (e.which === 40) {
+ if (liSelected) {
+ next = liSelected.next();
+ if (next.length > 0) {
+ liSelected.removeClass("selected");
+ liSelected = next.addClass("selected");
+ }
+ } else {
+ liSelected = li.eq(0).addClass("selected");
+ }
+ return $(liSelected).focus();
+ } else if (e.which === 38) {
+ if (liSelected) {
+ next = liSelected.prev();
+ if (next.length > 0) {
+ liSelected.removeClass("selected");
+ liSelected = next.addClass("selected");
+ }
+ } else {
+ liSelected = li.last().addClass("selected");
+ }
+ return $(liSelected).focus();
+ } else if (e.which === 13) {
+ path = $('.tree-item.selected .tree-item-file-name a').attr('href');
+ if (path) {
+ return Turbolinks.visit(path);
+ }
+ }
+ });
+ };
+
+ return TreeView;
+
+})();
diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee
deleted file mode 100644
index 83de584f2d9..00000000000
--- a/app/assets/javascripts/tree.js.coffee
+++ /dev/null
@@ -1,50 +0,0 @@
-class @TreeView
- constructor: ->
- @initKeyNav()
-
- # Code browser tree slider
- # Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
- $(".tree-content-holder .tree-item").on 'click', (e) ->
- $clickedEl = $(e.target)
- path = $('.tree-item-file-name a', this).attr('href')
-
- if not $clickedEl.is('a') and not $clickedEl.is('.str-truncated')
- if e.metaKey or e.which is 2
- e.preventDefault()
- window.open path, '_blank'
- else
- Turbolinks.visit path
-
- # Show the "Loading commit data" for only the first element
- $('span.log_loading:first').removeClass('hide')
-
- initKeyNav: ->
- li = $("tr.tree-item")
- liSelected = null
- $('body').keydown (e) ->
- if $("input:focus").length > 0 && (e.which == 38 || e.which == 40)
- return false
-
- if e.which is 40
- if liSelected
- next = liSelected.next()
- if next.length > 0
- liSelected.removeClass "selected"
- liSelected = next.addClass("selected")
- else
- liSelected = li.eq(0).addClass("selected")
-
- $(liSelected).focus()
- else if e.which is 38
- if liSelected
- next = liSelected.prev()
- if next.length > 0
- liSelected.removeClass "selected"
- liSelected = next.addClass("selected")
- else
- liSelected = li.last().addClass("selected")
-
- $(liSelected).focus()
- else if e.which is 13
- path = $('.tree-item.selected .tree-item-file-name a').attr('href')
- if path then Turbolinks.visit(path)
diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js
new file mode 100644
index 00000000000..d6798665795
--- /dev/null
+++ b/app/assets/javascripts/u2f/authenticate.js
@@ -0,0 +1,86 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.U2FAuthenticate = (function() {
+ function U2FAuthenticate(container, u2fParams) {
+ this.container = container;
+ this.renderNotSupported = bind(this.renderNotSupported, this);
+ this.renderAuthenticated = bind(this.renderAuthenticated, this);
+ this.renderError = bind(this.renderError, this);
+ this.renderInProgress = bind(this.renderInProgress, this);
+ this.renderSetup = bind(this.renderSetup, this);
+ this.renderTemplate = bind(this.renderTemplate, this);
+ this.authenticate = bind(this.authenticate, this);
+ this.start = bind(this.start, this);
+ this.appId = u2fParams.app_id;
+ this.challenge = u2fParams.challenge;
+ this.signRequests = u2fParams.sign_requests.map(function(request) {
+ return _(request).omit('challenge');
+ });
+ }
+
+ U2FAuthenticate.prototype.start = function() {
+ if (U2FUtil.isU2FSupported()) {
+ return this.renderSetup();
+ } else {
+ return this.renderNotSupported();
+ }
+ };
+
+ U2FAuthenticate.prototype.authenticate = function() {
+ return u2f.sign(this.appId, this.challenge, this.signRequests, (function(_this) {
+ return function(response) {
+ var error;
+ if (response.errorCode) {
+ error = new U2FError(response.errorCode);
+ return _this.renderError(error);
+ } else {
+ return _this.renderAuthenticated(JSON.stringify(response));
+ }
+ };
+ })(this), 10);
+ };
+
+ U2FAuthenticate.prototype.templates = {
+ "notSupported": "#js-authenticate-u2f-not-supported",
+ "setup": '#js-authenticate-u2f-setup',
+ "inProgress": '#js-authenticate-u2f-in-progress',
+ "error": '#js-authenticate-u2f-error',
+ "authenticated": '#js-authenticate-u2f-authenticated'
+ };
+
+ U2FAuthenticate.prototype.renderTemplate = function(name, params) {
+ var template, templateString;
+ templateString = $(this.templates[name]).html();
+ template = _.template(templateString);
+ return this.container.html(template(params));
+ };
+
+ U2FAuthenticate.prototype.renderSetup = function() {
+ this.renderTemplate('setup');
+ return this.container.find('#js-login-u2f-device').on('click', this.renderInProgress);
+ };
+
+ U2FAuthenticate.prototype.renderInProgress = function() {
+ this.renderTemplate('inProgress');
+ return this.authenticate();
+ };
+
+ U2FAuthenticate.prototype.renderError = function(error) {
+ this.renderTemplate('error', {
+ error_message: error.message()
+ });
+ return this.container.find('#js-u2f-try-again').on('click', this.renderSetup);
+ };
+
+ U2FAuthenticate.prototype.renderAuthenticated = function(deviceResponse) {
+ this.renderTemplate('authenticated');
+ return this.container.find("#js-device-response").val(deviceResponse);
+ };
+
+ U2FAuthenticate.prototype.renderNotSupported = function() {
+ return this.renderTemplate('notSupported');
+ };
+
+ return U2FAuthenticate;
+
+})();
diff --git a/app/assets/javascripts/u2f/authenticate.js.coffee b/app/assets/javascripts/u2f/authenticate.js.coffee
deleted file mode 100644
index 918c0a560fd..00000000000
--- a/app/assets/javascripts/u2f/authenticate.js.coffee
+++ /dev/null
@@ -1,75 +0,0 @@
-# Authenticate U2F (universal 2nd factor) devices for users to authenticate with.
-#
-# State Flow #1: setup -> in_progress -> authenticated -> POST to server
-# State Flow #2: setup -> in_progress -> error -> setup
-
-class @U2FAuthenticate
- constructor: (@container, u2fParams) ->
- @appId = u2fParams.app_id
- @challenge = u2fParams.challenge
-
- # The U2F Javascript API v1.1 requires a single challenge, with
- # _no challenges per-request_. The U2F Javascript API v1.0 requires a
- # challenge per-request, which is done by copying the single challenge
- # into every request.
- #
- # In either case, we don't need the per-request challenges that the server
- # has generated, so we can remove them.
- #
- # Note: The server library fixes this behaviour in (unreleased) version 1.0.0.
- # This can be removed once we upgrade.
- # https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4
- @signRequests = u2fParams.sign_requests.map (request) -> _(request).omit('challenge')
-
- start: () =>
- if U2FUtil.isU2FSupported()
- @renderSetup()
- else
- @renderNotSupported()
-
- authenticate: () =>
- u2f.sign(@appId, @challenge, @signRequests, (response) =>
- if response.errorCode
- error = new U2FError(response.errorCode)
- @renderError(error);
- else
- @renderAuthenticated(JSON.stringify(response))
- , 10)
-
- #############
- # Rendering #
- #############
-
- templates: {
- "notSupported": "#js-authenticate-u2f-not-supported",
- "setup": '#js-authenticate-u2f-setup',
- "inProgress": '#js-authenticate-u2f-in-progress',
- "error": '#js-authenticate-u2f-error',
- "authenticated": '#js-authenticate-u2f-authenticated'
- }
-
- renderTemplate: (name, params) =>
- templateString = $(@templates[name]).html()
- template = _.template(templateString)
- @container.html(template(params))
-
- renderSetup: () =>
- @renderTemplate('setup')
- @container.find('#js-login-u2f-device').on('click', @renderInProgress)
-
- renderInProgress: () =>
- @renderTemplate('inProgress')
- @authenticate()
-
- renderError: (error) =>
- @renderTemplate('error', {error_message: error.message()})
- @container.find('#js-u2f-try-again').on('click', @renderSetup)
-
- renderAuthenticated: (deviceResponse) =>
- @renderTemplate('authenticated')
- # Prefer to do this instead of interpolating using Underscore templates
- # because of JSON escaping issues.
- @container.find("#js-device-response").val(deviceResponse)
-
- renderNotSupported: () =>
- @renderTemplate('notSupported')
diff --git a/app/assets/javascripts/u2f/error.js b/app/assets/javascripts/u2f/error.js
new file mode 100644
index 00000000000..d82f4e54f1e
--- /dev/null
+++ b/app/assets/javascripts/u2f/error.js
@@ -0,0 +1,24 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.U2FError = (function() {
+ function U2FError(errorCode) {
+ this.errorCode = errorCode;
+ this.message = bind(this.message, this);
+ this.httpsDisabled = window.location.protocol !== 'https:';
+ console.error("U2F Error Code: " + this.errorCode);
+ }
+
+ U2FError.prototype.message = function() {
+ switch (false) {
+ case !(this.errorCode === u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled):
+ return "U2F only works with HTTPS-enabled websites. Contact your administrator for more details.";
+ case this.errorCode !== u2f.ErrorCodes.DEVICE_INELIGIBLE:
+ return "This device has already been registered with us.";
+ default:
+ return "There was a problem communicating with your device.";
+ }
+ };
+
+ return U2FError;
+
+})();
diff --git a/app/assets/javascripts/u2f/error.js.coffee b/app/assets/javascripts/u2f/error.js.coffee
deleted file mode 100644
index 1a2fc3e757f..00000000000
--- a/app/assets/javascripts/u2f/error.js.coffee
+++ /dev/null
@@ -1,13 +0,0 @@
-class @U2FError
- constructor: (@errorCode) ->
- @httpsDisabled = (window.location.protocol isnt 'https:')
- console.error("U2F Error Code: #{@errorCode}")
-
- message: () =>
- switch
- when (@errorCode is u2f.ErrorCodes.BAD_REQUEST and @httpsDisabled)
- "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
- when @errorCode is u2f.ErrorCodes.DEVICE_INELIGIBLE
- "This device has already been registered with us."
- else
- "There was a problem communicating with your device."
diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js
new file mode 100644
index 00000000000..712f33bdede
--- /dev/null
+++ b/app/assets/javascripts/u2f/register.js
@@ -0,0 +1,84 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.U2FRegister = (function() {
+ function U2FRegister(container, u2fParams) {
+ this.container = container;
+ this.renderNotSupported = bind(this.renderNotSupported, this);
+ this.renderRegistered = bind(this.renderRegistered, this);
+ this.renderError = bind(this.renderError, this);
+ this.renderInProgress = bind(this.renderInProgress, this);
+ this.renderSetup = bind(this.renderSetup, this);
+ this.renderTemplate = bind(this.renderTemplate, this);
+ this.register = bind(this.register, this);
+ this.start = bind(this.start, this);
+ this.appId = u2fParams.app_id;
+ this.registerRequests = u2fParams.register_requests;
+ this.signRequests = u2fParams.sign_requests;
+ }
+
+ U2FRegister.prototype.start = function() {
+ if (U2FUtil.isU2FSupported()) {
+ return this.renderSetup();
+ } else {
+ return this.renderNotSupported();
+ }
+ };
+
+ U2FRegister.prototype.register = function() {
+ return u2f.register(this.appId, this.registerRequests, this.signRequests, (function(_this) {
+ return function(response) {
+ var error;
+ if (response.errorCode) {
+ error = new U2FError(response.errorCode);
+ return _this.renderError(error);
+ } else {
+ return _this.renderRegistered(JSON.stringify(response));
+ }
+ };
+ })(this), 10);
+ };
+
+ U2FRegister.prototype.templates = {
+ "notSupported": "#js-register-u2f-not-supported",
+ "setup": '#js-register-u2f-setup',
+ "inProgress": '#js-register-u2f-in-progress',
+ "error": '#js-register-u2f-error',
+ "registered": '#js-register-u2f-registered'
+ };
+
+ U2FRegister.prototype.renderTemplate = function(name, params) {
+ var template, templateString;
+ templateString = $(this.templates[name]).html();
+ template = _.template(templateString);
+ return this.container.html(template(params));
+ };
+
+ U2FRegister.prototype.renderSetup = function() {
+ this.renderTemplate('setup');
+ return this.container.find('#js-setup-u2f-device').on('click', this.renderInProgress);
+ };
+
+ U2FRegister.prototype.renderInProgress = function() {
+ this.renderTemplate('inProgress');
+ return this.register();
+ };
+
+ U2FRegister.prototype.renderError = function(error) {
+ this.renderTemplate('error', {
+ error_message: error.message()
+ });
+ return this.container.find('#js-u2f-try-again').on('click', this.renderSetup);
+ };
+
+ U2FRegister.prototype.renderRegistered = function(deviceResponse) {
+ this.renderTemplate('registered');
+ return this.container.find("#js-device-response").val(deviceResponse);
+ };
+
+ U2FRegister.prototype.renderNotSupported = function() {
+ return this.renderTemplate('notSupported');
+ };
+
+ return U2FRegister;
+
+})();
diff --git a/app/assets/javascripts/u2f/register.js.coffee b/app/assets/javascripts/u2f/register.js.coffee
deleted file mode 100644
index 74472cfa120..00000000000
--- a/app/assets/javascripts/u2f/register.js.coffee
+++ /dev/null
@@ -1,63 +0,0 @@
-# Register U2F (universal 2nd factor) devices for users to authenticate with.
-#
-# State Flow #1: setup -> in_progress -> registered -> POST to server
-# State Flow #2: setup -> in_progress -> error -> setup
-
-class @U2FRegister
- constructor: (@container, u2fParams) ->
- @appId = u2fParams.app_id
- @registerRequests = u2fParams.register_requests
- @signRequests = u2fParams.sign_requests
-
- start: () =>
- if U2FUtil.isU2FSupported()
- @renderSetup()
- else
- @renderNotSupported()
-
- register: () =>
- u2f.register(@appId, @registerRequests, @signRequests, (response) =>
- if response.errorCode
- error = new U2FError(response.errorCode)
- @renderError(error);
- else
- @renderRegistered(JSON.stringify(response))
- , 10)
-
- #############
- # Rendering #
- #############
-
- templates: {
- "notSupported": "#js-register-u2f-not-supported",
- "setup": '#js-register-u2f-setup',
- "inProgress": '#js-register-u2f-in-progress',
- "error": '#js-register-u2f-error',
- "registered": '#js-register-u2f-registered'
- }
-
- renderTemplate: (name, params) =>
- templateString = $(@templates[name]).html()
- template = _.template(templateString)
- @container.html(template(params))
-
- renderSetup: () =>
- @renderTemplate('setup')
- @container.find('#js-setup-u2f-device').on('click', @renderInProgress)
-
- renderInProgress: () =>
- @renderTemplate('inProgress')
- @register()
-
- renderError: (error) =>
- @renderTemplate('error', {error_message: error.message()})
- @container.find('#js-u2f-try-again').on('click', @renderSetup)
-
- renderRegistered: (deviceResponse) =>
- @renderTemplate('registered')
- # Prefer to do this instead of interpolating using Underscore templates
- # because of JSON escaping issues.
- @container.find("#js-device-response").val(deviceResponse)
-
- renderNotSupported: () =>
- @renderTemplate('notSupported')
diff --git a/app/assets/javascripts/u2f/util.js b/app/assets/javascripts/u2f/util.js
new file mode 100644
index 00000000000..70ed4dc4617
--- /dev/null
+++ b/app/assets/javascripts/u2f/util.js
@@ -0,0 +1,10 @@
+this.U2FUtil = (function() {
+ function U2FUtil() {}
+
+ U2FUtil.isU2FSupported = function() {
+ return window.u2f;
+ };
+
+ return U2FUtil;
+
+})();
diff --git a/app/assets/javascripts/u2f/util.js.coffee b/app/assets/javascripts/u2f/util.js.coffee
deleted file mode 100644
index 5ef324f609d..00000000000
--- a/app/assets/javascripts/u2f/util.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-class @U2FUtil
- @isU2FSupported: ->
- window.u2f
diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js
new file mode 100644
index 00000000000..3cab0db65a2
--- /dev/null
+++ b/app/assets/javascripts/user.js
@@ -0,0 +1,28 @@
+this.User = (function() {
+ function User(opts) {
+ this.opts = opts;
+ $('.profile-groups-avatars').tooltip({
+ "placement": "top"
+ });
+ this.initTabs();
+ $('.hide-project-limit-message').on('click', function(e) {
+ var path;
+ path = '/';
+ $.cookie('hide_project_limit_message', 'false', {
+ path: path
+ });
+ $(this).parents('.project-limit-message').remove();
+ return e.preventDefault();
+ });
+ }
+
+ User.prototype.initTabs = function() {
+ return new UserTabs({
+ parentEl: '.user-profile',
+ action: this.opts.action
+ });
+ };
+
+ return User;
+
+})();
diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee
deleted file mode 100644
index 2882a90d118..00000000000
--- a/app/assets/javascripts/user.js.coffee
+++ /dev/null
@@ -1,17 +0,0 @@
-class @User
- constructor: (@opts) ->
- $('.profile-groups-avatars').tooltip("placement": "top")
-
- @initTabs()
-
- $('.hide-project-limit-message').on 'click', (e) ->
- path = '/'
- $.cookie('hide_project_limit_message', 'false', { path: path })
- $(@).parents('.project-limit-message').remove()
- e.preventDefault()
-
- initTabs: ->
- new UserTabs(
- parentEl: '.user-profile'
- action: @opts.action
- )
diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js
new file mode 100644
index 00000000000..ad06dc0e78c
--- /dev/null
+++ b/app/assets/javascripts/user_tabs.js
@@ -0,0 +1,116 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.UserTabs = (function() {
+ function UserTabs(opts) {
+ this.tabShown = bind(this.tabShown, this);
+ var i, item, len, ref, ref1, ref2, ref3;
+ this.action = (ref = opts.action) != null ? ref : 'activity', this.defaultAction = (ref1 = opts.defaultAction) != null ? ref1 : 'activity', this.parentEl = (ref2 = opts.parentEl) != null ? ref2 : $(document);
+ if (typeof this.parentEl === 'string') {
+ this.parentEl = $(this.parentEl);
+ }
+ this._location = location;
+ this.loaded = {};
+ ref3 = this.parentEl.find('.nav-links a');
+ for (i = 0, len = ref3.length; i < len; i++) {
+ item = ref3[i];
+ this.loaded[$(item).attr('data-action')] = false;
+ }
+ this.actions = Object.keys(this.loaded);
+ this.bindEvents();
+ if (this.action === 'show') {
+ this.action = this.defaultAction;
+ }
+ this.activateTab(this.action);
+ }
+
+ UserTabs.prototype.bindEvents = function() {
+ return this.parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]').on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', this.tabShown);
+ };
+
+ UserTabs.prototype.tabShown = function(event) {
+ var $target, action, source;
+ $target = $(event.target);
+ action = $target.data('action');
+ source = $target.attr('href');
+ this.setTab(source, action);
+ return this.setCurrentAction(action);
+ };
+
+ UserTabs.prototype.activateTab = function(action) {
+ return this.parentEl.find(".nav-links .js-" + action + "-tab a").tab('show');
+ };
+
+ UserTabs.prototype.setTab = function(source, action) {
+ if (this.loaded[action] === true) {
+ return;
+ }
+ if (action === 'activity') {
+ this.loadActivities(source);
+ }
+ if (action === 'groups' || action === 'contributed' || action === 'projects' || action === 'snippets') {
+ return this.loadTab(source, action);
+ }
+ };
+
+ UserTabs.prototype.loadTab = function(source, action) {
+ return $.ajax({
+ beforeSend: (function(_this) {
+ return function() {
+ return _this.toggleLoading(true);
+ };
+ })(this),
+ complete: (function(_this) {
+ return function() {
+ return _this.toggleLoading(false);
+ };
+ })(this),
+ dataType: 'json',
+ type: 'GET',
+ url: source + ".json",
+ success: (function(_this) {
+ return function(data) {
+ var tabSelector;
+ tabSelector = 'div#' + action;
+ _this.parentEl.find(tabSelector).html(data.html);
+ _this.loaded[action] = true;
+ return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
+ };
+ })(this)
+ });
+ };
+
+ UserTabs.prototype.loadActivities = function(source) {
+ var $calendarWrap;
+ if (this.loaded['activity'] === true) {
+ return;
+ }
+ $calendarWrap = this.parentEl.find('.user-calendar');
+ $calendarWrap.load($calendarWrap.data('href'));
+ new Activities();
+ return this.loaded['activity'] = true;
+ };
+
+ UserTabs.prototype.toggleLoading = function(status) {
+ return this.parentEl.find('.loading-status .loading').toggle(status);
+ };
+
+ UserTabs.prototype.setCurrentAction = function(action) {
+ var new_state, regExp;
+ regExp = new RegExp('\/(' + this.actions.join('|') + ')(\.html)?\/?$');
+ new_state = this._location.pathname;
+ new_state = new_state.replace(/\/+$/, "");
+ new_state = new_state.replace(regExp, '');
+ if (action !== this.defaultAction) {
+ new_state += "/" + action;
+ }
+ new_state += this._location.search + this._location.hash;
+ history.replaceState({
+ turbolinks: true,
+ url: new_state
+ }, document.title, new_state);
+ return new_state;
+ };
+
+ return UserTabs;
+
+})();
diff --git a/app/assets/javascripts/user_tabs.js.coffee b/app/assets/javascripts/user_tabs.js.coffee
deleted file mode 100644
index 29dad21faed..00000000000
--- a/app/assets/javascripts/user_tabs.js.coffee
+++ /dev/null
@@ -1,156 +0,0 @@
-# UserTabs
-#
-# Handles persisting and restoring the current tab selection and lazily-loading
-# content on the Users#show page.
-#
-# ### Example Markup
-#
-# <ul class="nav-links">
-# <li class="activity-tab active">
-# <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
-# Activity
-# </a>
-# </li>
-# <li class="groups-tab">
-# <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
-# Groups
-# </a>
-# </li>
-# <li class="contributed-tab">
-# <a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed">
-# Contributed projects
-# </a>
-# </li>
-# <li class="projects-tab">
-# <a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects">
-# Personal projects
-# </a>
-# </li>
-# <li class="snippets-tab">
-# <a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets">
-# </a>
-# </li>
-# </ul>
-#
-# <div class="tab-content">
-# <div class="tab-pane" id="activity">
-# Activity Content
-# </div>
-# <div class="tab-pane" id="groups">
-# Groups Content
-# </div>
-# <div class="tab-pane" id="contributed">
-# Contributed projects content
-# </div>
-# <div class="tab-pane" id="projects">
-# Projects content
-# </div>
-# <div class="tab-pane" id="snippets">
-# Snippets content
-# </div>
-# </div>
-#
-# <div class="loading-status">
-# <div class="loading">
-# Loading Animation
-# </div>
-# </div>
-#
-class @UserTabs
- constructor: (opts) ->
- {
- @action = 'activity'
- @defaultAction = 'activity'
- @parentEl = $(document)
- } = opts
-
- # Make jQuery object if selector is provided
- @parentEl = $(@parentEl) if typeof @parentEl is 'string'
-
- # Store the `location` object, allowing for easier stubbing in tests
- @_location = location
-
- # Set tab states
- @loaded = {}
- for item in @parentEl.find('.nav-links a')
- @loaded[$(item).attr 'data-action'] = false
-
- # Actions
- @actions = Object.keys @loaded
-
- @bindEvents()
-
- # Set active tab
- @action = @defaultAction if @action is 'show'
- @activateTab(@action)
-
- bindEvents: ->
- # Toggle event listeners
- @parentEl
- .off 'shown.bs.tab', '.nav-links a[data-toggle="tab"]'
- .on 'shown.bs.tab', '.nav-links a[data-toggle="tab"]', @tabShown
-
- tabShown: (event) =>
- $target = $(event.target)
- action = $target.data('action')
- source = $target.attr('href')
-
- @setTab(source, action)
- @setCurrentAction(action)
-
- activateTab: (action) ->
- @parentEl.find(".nav-links .js-#{action}-tab a").tab('show')
-
- setTab: (source, action) ->
- return if @loaded[action] is true
-
- if action is 'activity'
- @loadActivities(source)
-
- if action in ['groups', 'contributed', 'projects', 'snippets']
- @loadTab(source, action)
-
- loadTab: (source, action) ->
- $.ajax
- beforeSend: => @toggleLoading(true)
- complete: => @toggleLoading(false)
- dataType: 'json'
- type: 'GET'
- url: "#{source}.json"
- success: (data) =>
- tabSelector = 'div#' + action
- @parentEl.find(tabSelector).html(data.html)
- @loaded[action] = true
-
- # Fix tooltips
- gl.utils.localTimeAgo($('.js-timeago', tabSelector))
-
- loadActivities: (source) ->
- return if @loaded['activity'] is true
-
- $calendarWrap = @parentEl.find('.user-calendar')
- $calendarWrap.load($calendarWrap.data('href'))
-
- new Activities()
- @loaded['activity'] = true
-
- toggleLoading: (status) ->
- @parentEl.find('.loading-status .loading').toggle(status)
-
- setCurrentAction: (action) ->
- # Remove possible actions from URL
- regExp = new RegExp('\/(' + @actions.join('|') + ')(\.html)?\/?$')
- new_state = @_location.pathname
- new_state = new_state.replace(/\/+$/, "") # remove trailing slashes
- new_state = new_state.replace(regExp, '')
-
- # Append the new action if we're on a tab other than 'activity'
- unless action == @defaultAction
- new_state += "/#{action}"
-
- # Ensure parameters and hash come along for the ride
- new_state += @_location.search + @_location.hash
-
- history.replaceState {turbolinks: true, url: new_state}, document.title, new_state
-
- new_state
diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js
new file mode 100644
index 00000000000..30ba139a617
--- /dev/null
+++ b/app/assets/javascripts/users/calendar.js
@@ -0,0 +1,189 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.Calendar = (function() {
+ function Calendar(timestamps, calendar_activities_path) {
+ var group, i;
+ this.calendar_activities_path = calendar_activities_path;
+ this.clickDay = bind(this.clickDay, this);
+ this.currentSelectedDate = '';
+ this.daySpace = 1;
+ this.daySize = 15;
+ this.daySizeWithSpace = this.daySize + (this.daySpace * 2);
+ this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ this.months = [];
+ this.timestampsTmp = [];
+ i = 0;
+ group = 0;
+ _.each(timestamps, (function(_this) {
+ return function(count, date) {
+ var day, innerArray, newDate;
+ newDate = new Date(parseInt(date) * 1000);
+ day = newDate.getDay();
+ if ((day === 0 && i !== 0) || i === 0) {
+ _this.timestampsTmp.push([]);
+ group++;
+ }
+ innerArray = _this.timestampsTmp[group - 1];
+ innerArray.push({
+ count: count,
+ date: newDate,
+ day: day
+ });
+ return i++;
+ };
+ })(this));
+ this.colorKey = this.initColorKey();
+ this.color = this.initColor();
+ this.renderSvg(group);
+ this.renderDays();
+ this.renderMonths();
+ this.renderDayTitles();
+ this.renderKey();
+ this.initTooltips();
+ }
+
+ Calendar.prototype.renderSvg = function(group) {
+ return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', (group + 1) * this.daySizeWithSpace).attr('height', 167).attr('class', 'contrib-calendar');
+ };
+
+ Calendar.prototype.renderDays = function() {
+ return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) {
+ return function(group, i) {
+ _.each(group, function(stamp, a) {
+ var lastMonth, lastMonthX, month, x;
+ if (a === 0 && stamp.day === 0) {
+ month = stamp.date.getMonth();
+ x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace;
+ lastMonth = _.last(_this.months);
+ if (lastMonth != null) {
+ lastMonthX = lastMonth.x;
+ }
+ if (lastMonth == null) {
+ return _this.months.push({
+ month: month,
+ x: x
+ });
+ } else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) {
+ return _this.months.push({
+ month: month,
+ x: x
+ });
+ }
+ }
+ });
+ return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)";
+ };
+ })(this)).selectAll('rect').data(function(stamp) {
+ return stamp;
+ }).enter().append('rect').attr('x', '0').attr('y', (function(_this) {
+ return function(stamp, i) {
+ return _this.daySizeWithSpace * stamp.day;
+ };
+ })(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) {
+ return function(stamp) {
+ var contribText, date, dateText;
+ date = new Date(stamp.date);
+ contribText = 'No contributions';
+ if (stamp.count > 0) {
+ contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : '');
+ }
+ dateText = dateFormat(date, 'mmm d, yyyy');
+ return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText;
+ };
+ })(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) {
+ return function(stamp) {
+ if (stamp.count !== 0) {
+ return _this.color(Math.min(stamp.count, 40));
+ } else {
+ return '#ededed';
+ }
+ };
+ })(this)).attr('data-container', 'body').on('click', this.clickDay);
+ };
+
+ Calendar.prototype.renderDayTitles = function() {
+ var days;
+ days = [
+ {
+ text: 'M',
+ y: 29 + (this.daySizeWithSpace * 1)
+ }, {
+ text: 'W',
+ y: 29 + (this.daySizeWithSpace * 3)
+ }, {
+ text: 'F',
+ y: 29 + (this.daySizeWithSpace * 5)
+ }
+ ];
+ return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) {
+ return day.y;
+ }).text(function(day) {
+ return day.text;
+ }).attr('class', 'user-contrib-text');
+ };
+
+ Calendar.prototype.renderMonths = function() {
+ return this.svg.append('g').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
+ return date.x;
+ }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) {
+ return function(date) {
+ return _this.monthNames[date.month];
+ };
+ })(this));
+ };
+
+ Calendar.prototype.renderKey = function() {
+ var keyColors;
+ keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
+ return this.svg.append('g').attr('transform', "translate(18, " + (this.daySizeWithSpace * 8 + 16) + ")").selectAll('rect').data(keyColors).enter().append('rect').attr('width', this.daySize).attr('height', this.daySize).attr('x', (function(_this) {
+ return function(color, i) {
+ return _this.daySizeWithSpace * i;
+ };
+ })(this)).attr('y', 0).attr('fill', function(color) {
+ return color;
+ });
+ };
+
+ Calendar.prototype.initColor = function() {
+ var colorRange;
+ colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
+ return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange);
+ };
+
+ Calendar.prototype.initColorKey = function() {
+ return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
+ };
+
+ Calendar.prototype.clickDay = function(stamp) {
+ var formatted_date;
+ if (this.currentSelectedDate !== stamp.date) {
+ this.currentSelectedDate = stamp.date;
+ formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate();
+ return $.ajax({
+ url: this.calendar_activities_path,
+ data: {
+ date: formatted_date
+ },
+ cache: false,
+ dataType: 'html',
+ beforeSend: function() {
+ return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>');
+ },
+ success: function(data) {
+ return $('.user-calendar-activities').html(data);
+ }
+ });
+ } else {
+ return $('.user-calendar-activities').html('');
+ }
+ };
+
+ Calendar.prototype.initTooltips = function() {
+ return $('.js-contrib-calendar .js-tooltip').tooltip({
+ html: true
+ });
+ };
+
+ return Calendar;
+
+})();
diff --git a/app/assets/javascripts/users/calendar.js.coffee b/app/assets/javascripts/users/calendar.js.coffee
deleted file mode 100644
index c49ba5186f2..00000000000
--- a/app/assets/javascripts/users/calendar.js.coffee
+++ /dev/null
@@ -1,194 +0,0 @@
-class @Calendar
- constructor: (timestamps, @calendar_activities_path) ->
- @currentSelectedDate = ''
- @daySpace = 1
- @daySize = 15
- @daySizeWithSpace = @daySize + (@daySpace * 2)
- @monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
- @months = []
-
- # Loop through the timestamps to create a group of objects
- # The group of objects will be grouped based on the day of the week they are
- @timestampsTmp = []
- i = 0
- group = 0
- _.each timestamps, (count, date) =>
- newDate = new Date parseInt(date) * 1000
- day = newDate.getDay()
-
- # Create a new group array if this is the first day of the week
- # or if is first object
- if (day is 0 and i isnt 0) or i is 0
- @timestampsTmp.push []
- group++
-
- innerArray = @timestampsTmp[group-1]
-
- # Push to the inner array the values that will be used to render map
- innerArray.push
- count: count
- date: newDate
- day: day
-
- i++
-
- # Init color functions
- @colorKey = @initColorKey()
- @color = @initColor()
-
- # Init the svg element
- @renderSvg(group)
- @renderDays()
- @renderMonths()
- @renderDayTitles()
- @renderKey()
-
- @initTooltips()
-
- renderSvg: (group) ->
- @svg = d3.select '.js-contrib-calendar'
- .append 'svg'
- .attr 'width', (group + 1) * @daySizeWithSpace
- .attr 'height', 167
- .attr 'class', 'contrib-calendar'
-
- renderDays: ->
- @svg.selectAll 'g'
- .data @timestampsTmp
- .enter()
- .append 'g'
- .attr 'transform', (group, i) =>
- _.each group, (stamp, a) =>
- if a is 0 and stamp.day is 0
- month = stamp.date.getMonth()
- x = (@daySizeWithSpace * i + 1) + @daySizeWithSpace
- lastMonth = _.last(@months)
- if lastMonth?
- lastMonthX = lastMonth.x
-
- if !lastMonth?
- @months.push
- month: month
- x: x
- else if month isnt lastMonth.month and x - @daySizeWithSpace isnt lastMonthX
- @months.push
- month: month
- x: x
-
- "translate(#{(@daySizeWithSpace * i + 1) + @daySizeWithSpace}, 18)"
- .selectAll 'rect'
- .data (stamp) ->
- stamp
- .enter()
- .append 'rect'
- .attr 'x', '0'
- .attr 'y', (stamp, i) =>
- (@daySizeWithSpace * stamp.day)
- .attr 'width', @daySize
- .attr 'height', @daySize
- .attr 'title', (stamp) =>
- date = new Date(stamp.date)
- contribText = 'No contributions'
-
- if stamp.count > 0
- contribText = "#{stamp.count} contribution#{if stamp.count > 1 then 's' else ''}"
-
- dateText = dateFormat(date, 'mmm d, yyyy')
-
- "#{contribText}<br />#{gl.utils.getDayName(date)} #{dateText}"
- .attr 'class', 'user-contrib-cell js-tooltip'
- .attr 'fill', (stamp) =>
- if stamp.count isnt 0
- @color(Math.min(stamp.count, 40))
- else
- '#ededed'
- .attr 'data-container', 'body'
- .on 'click', @clickDay
-
- renderDayTitles: ->
- days = [{
- text: 'M'
- y: 29 + (@daySizeWithSpace * 1)
- }, {
- text: 'W'
- y: 29 + (@daySizeWithSpace * 3)
- }, {
- text: 'F'
- y: 29 + (@daySizeWithSpace * 5)
- }]
- @svg.append 'g'
- .selectAll 'text'
- .data days
- .enter()
- .append 'text'
- .attr 'text-anchor', 'middle'
- .attr 'x', 8
- .attr 'y', (day) ->
- day.y
- .text (day) ->
- day.text
- .attr 'class', 'user-contrib-text'
-
- renderMonths: ->
- @svg.append 'g'
- .selectAll 'text'
- .data @months
- .enter()
- .append 'text'
- .attr 'x', (date) ->
- date.x
- .attr 'y', 10
- .attr 'class', 'user-contrib-text'
- .text (date) =>
- @monthNames[date.month]
-
- renderKey: ->
- keyColors = ['#ededed', @colorKey(0), @colorKey(1), @colorKey(2), @colorKey(3)]
- @svg.append 'g'
- .attr 'transform', "translate(18, #{@daySizeWithSpace * 8 + 16})"
- .selectAll 'rect'
- .data keyColors
- .enter()
- .append 'rect'
- .attr 'width', @daySize
- .attr 'height', @daySize
- .attr 'x', (color, i) =>
- @daySizeWithSpace * i
- .attr 'y', 0
- .attr 'fill', (color) ->
- color
-
- initColor: ->
- colorRange = ['#ededed', @colorKey(0), @colorKey(1), @colorKey(2), @colorKey(3)]
- d3.scale
- .threshold()
- .domain([0, 10, 20, 30])
- .range(colorRange)
-
- initColorKey: ->
- d3.scale
- .linear()
- .range(['#acd5f2', '#254e77'])
- .domain([0, 3])
-
- clickDay: (stamp) =>
- if @currentSelectedDate isnt stamp.date
- @currentSelectedDate = stamp.date
- formatted_date = @currentSelectedDate.getFullYear() + "-" + (@currentSelectedDate.getMonth()+1) + "-" + @currentSelectedDate.getDate()
-
- $.ajax
- url: @calendar_activities_path
- data:
- date: formatted_date
- cache: false
- dataType: 'html'
- beforeSend: ->
- $('.user-calendar-activities').html '<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>'
- success: (data) ->
- $('.user-calendar-activities').html data
- else
- $('.user-calendar-activities').html ''
-
- initTooltips: ->
- $('.js-contrib-calendar .js-tooltip').tooltip
- html: true
diff --git a/app/assets/javascripts/users/users_bundle.js b/app/assets/javascripts/users/users_bundle.js
new file mode 100644
index 00000000000..334007d6b38
--- /dev/null
+++ b/app/assets/javascripts/users/users_bundle.js
@@ -0,0 +1,3 @@
+
+/*= require_tree . */
+
diff --git a/app/assets/javascripts/users/users_bundle.js.coffee b/app/assets/javascripts/users/users_bundle.js.coffee
deleted file mode 100644
index 91cacfece46..00000000000
--- a/app/assets/javascripts/users/users_bundle.js.coffee
+++ /dev/null
@@ -1,2 +0,0 @@
-#
-#= require_tree .
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
new file mode 100644
index 00000000000..4131061fdee
--- /dev/null
+++ b/app/assets/javascripts/users_select.js
@@ -0,0 +1,339 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ slice = [].slice;
+
+this.UsersSelect = (function() {
+ function UsersSelect(currentUser) {
+ this.users = bind(this.users, this);
+ this.user = bind(this.user, this);
+ this.usersPath = "/autocomplete/users.json";
+ this.userPath = "/autocomplete/users/:id.json";
+ if (currentUser != null) {
+ this.currentUser = JSON.parse(currentUser);
+ }
+ $('.js-user-search').each((function(_this) {
+ return function(i, dropdown) {
+ var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser;
+ $dropdown = $(dropdown);
+ _this.projectId = $dropdown.data('project-id');
+ _this.showCurrentUser = $dropdown.data('current-user');
+ showNullUser = $dropdown.data('null-user');
+ showAnyUser = $dropdown.data('any-user');
+ firstUser = $dropdown.data('first-user');
+ _this.authorId = $dropdown.data('author-id');
+ selectedId = $dropdown.data('selected');
+ defaultLabel = $dropdown.data('default-label');
+ issueURL = $dropdown.data('issueUpdate');
+ $selectbox = $dropdown.closest('.selectbox');
+ $block = $selectbox.closest('.block');
+ abilityName = $dropdown.data('ability-name');
+ $value = $block.find('.value');
+ $collapsedSidebar = $block.find('.sidebar-collapsed-user');
+ $loading = $block.find('.block-loading').fadeOut();
+ $block.on('click', '.js-assign-yourself', function(e) {
+ e.preventDefault();
+ return assignTo(_this.currentUser.id);
+ });
+ assignTo = function(selected) {
+ var data;
+ data = {};
+ data[abilityName] = {};
+ data[abilityName].assignee_id = selected != null ? selected : null;
+ $loading.fadeIn();
+ $dropdown.trigger('loading.gl.dropdown');
+ return $.ajax({
+ type: 'PUT',
+ dataType: 'json',
+ url: issueURL,
+ data: data
+ }).done(function(data) {
+ var user;
+ $dropdown.trigger('loaded.gl.dropdown');
+ $loading.fadeOut();
+ $selectbox.hide();
+ if (data.assignee) {
+ user = {
+ name: data.assignee.name,
+ username: data.assignee.username,
+ avatar: data.assignee.avatar_url
+ };
+ } else {
+ user = {
+ name: 'Unassigned',
+ username: '',
+ avatar: ''
+ };
+ }
+ $value.html(assigneeTemplate(user));
+ $collapsedSidebar.attr('title', user.name).tooltip('fixTitle');
+ return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
+ });
+ };
+ collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <a class="author_link" href="/u/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>');
+ assigneeTemplate = _.template('<% if (username) { %> <a class="author_link bold" href="/u/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>');
+ return $dropdown.glDropdown({
+ data: function(term, callback) {
+ var isAuthorFilter;
+ isAuthorFilter = $('.js-author-search');
+ return _this.users(term, function(users) {
+ var anyUser, index, j, len, name, obj, showDivider;
+ if (term.length === 0) {
+ showDivider = 0;
+ if (firstUser) {
+ for (index = j = 0, len = users.length; j < len; index = ++j) {
+ obj = users[index];
+ if (obj.username === firstUser) {
+ users.splice(index, 1);
+ users.unshift(obj);
+ break;
+ }
+ }
+ }
+ if (showNullUser) {
+ showDivider += 1;
+ users.unshift({
+ beforeDivider: true,
+ name: 'Unassigned',
+ id: 0
+ });
+ }
+ if (showAnyUser) {
+ showDivider += 1;
+ name = showAnyUser;
+ if (name === true) {
+ name = 'Any User';
+ }
+ anyUser = {
+ beforeDivider: true,
+ name: name,
+ id: null
+ };
+ users.unshift(anyUser);
+ }
+ }
+ if (showDivider) {
+ users.splice(showDivider, 0, "divider");
+ }
+ return callback(users);
+ });
+ },
+ filterable: true,
+ filterRemote: true,
+ search: {
+ fields: ['name', 'username']
+ },
+ selectable: true,
+ fieldName: $dropdown.data('field-name'),
+ toggleLabel: function(selected) {
+ if (selected && 'id' in selected) {
+ if (selected.text) {
+ return selected.text;
+ } else {
+ return selected.name;
+ }
+ } else {
+ return defaultLabel;
+ }
+ },
+ inputId: 'issue_assignee_id',
+ hidden: function(e) {
+ $selectbox.hide();
+ return $value.css('display', '');
+ },
+ clicked: function(user) {
+ var isIssueIndex, isMRIndex, page, selected;
+ page = $('body').data('page');
+ isIssueIndex = page === 'projects:issues:index';
+ isMRIndex = (page === page && page === 'projects:merge_requests:index');
+ if ($dropdown.hasClass('js-filter-bulk-update')) {
+ return;
+ }
+ if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+ selectedId = user.id;
+ return Issuable.filterResults($dropdown.closest('form'));
+ } else if ($dropdown.hasClass('js-filter-submit')) {
+ return $dropdown.closest('form').submit();
+ } else {
+ selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").val();
+ return assignTo(selected);
+ }
+ },
+ renderRow: function(user) {
+ var avatar, img, listClosingTags, listWithName, listWithUserName, selected, username;
+ username = user.username ? "@" + user.username : "";
+ avatar = user.avatar_url ? user.avatar_url : false;
+ selected = user.id === selectedId ? "is-active" : "";
+ img = "";
+ if (user.beforeDivider != null) {
+ "<li> <a href='#' class='" + selected + "'> " + user.name + " </a> </li>";
+ } else {
+ if (avatar) {
+ img = "<img src='" + avatar + "' class='avatar avatar-inline' width='30' />";
+ }
+ }
+ listWithName = "<li> <a href='#' class='dropdown-menu-user-link " + selected + "'> " + img + " <strong class='dropdown-menu-user-full-name'> " + user.name + " </strong>";
+ listWithUserName = "<span class='dropdown-menu-user-username'> " + username + " </span>";
+ listClosingTags = "</a> </li>";
+ if (username === '') {
+ listWithUserName = '';
+ }
+ return listWithName + listWithUserName + listClosingTags;
+ }
+ });
+ };
+ })(this));
+ $('.ajax-users-select').each((function(_this) {
+ return function(i, select) {
+ var firstUser, showAnyUser, showEmailUser, showNullUser;
+ _this.projectId = $(select).data('project-id');
+ _this.groupId = $(select).data('group-id');
+ _this.showCurrentUser = $(select).data('current-user');
+ _this.authorId = $(select).data('author-id');
+ showNullUser = $(select).data('null-user');
+ showAnyUser = $(select).data('any-user');
+ showEmailUser = $(select).data('email-user');
+ firstUser = $(select).data('first-user');
+ return $(select).select2({
+ placeholder: "Search for a user",
+ multiple: $(select).hasClass('multiselect'),
+ minimumInputLength: 0,
+ query: function(query) {
+ return _this.users(query.term, function(users) {
+ var anyUser, data, emailUser, index, j, len, name, nullUser, obj, ref;
+ data = {
+ results: users
+ };
+ if (query.term.length === 0) {
+ if (firstUser) {
+ ref = data.results;
+ for (index = j = 0, len = ref.length; j < len; index = ++j) {
+ obj = ref[index];
+ if (obj.username === firstUser) {
+ data.results.splice(index, 1);
+ data.results.unshift(obj);
+ break;
+ }
+ }
+ }
+ if (showNullUser) {
+ nullUser = {
+ name: 'Unassigned',
+ id: 0
+ };
+ data.results.unshift(nullUser);
+ }
+ if (showAnyUser) {
+ name = showAnyUser;
+ if (name === true) {
+ name = 'Any User';
+ }
+ anyUser = {
+ name: name,
+ id: null
+ };
+ data.results.unshift(anyUser);
+ }
+ }
+ if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) {
+ emailUser = {
+ name: "Invite \"" + query.term + "\"",
+ username: query.term,
+ id: query.term
+ };
+ data.results.unshift(emailUser);
+ }
+ return query.callback(data);
+ });
+ },
+ initSelection: function() {
+ var args;
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ return _this.initSelection.apply(_this, args);
+ },
+ formatResult: function() {
+ var args;
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ return _this.formatResult.apply(_this, args);
+ },
+ formatSelection: function() {
+ var args;
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ return _this.formatSelection.apply(_this, args);
+ },
+ dropdownCssClass: "ajax-users-dropdown",
+ escapeMarkup: function(m) {
+ return m;
+ }
+ });
+ };
+ })(this));
+ }
+
+ UsersSelect.prototype.initSelection = function(element, callback) {
+ var id, nullUser;
+ id = $(element).val();
+ if (id === "0") {
+ nullUser = {
+ name: 'Unassigned'
+ };
+ return callback(nullUser);
+ } else if (id !== "") {
+ return this.user(id, callback);
+ }
+ };
+
+ UsersSelect.prototype.formatResult = function(user) {
+ var avatar;
+ if (user.avatar_url) {
+ avatar = user.avatar_url;
+ } else {
+ avatar = gon.default_avatar_url;
+ }
+ return "<div class='user-result " + (!user.username ? 'no-username' : void 0) + "'> <div class='user-image'><img class='avatar s24' src='" + avatar + "'></div> <div class='user-name'>" + user.name + "</div> <div class='user-username'>" + (user.username || "") + "</div> </div>";
+ };
+
+ UsersSelect.prototype.formatSelection = function(user) {
+ return user.name;
+ };
+
+ UsersSelect.prototype.user = function(user_id, callback) {
+ var url;
+ url = this.buildUrl(this.userPath);
+ url = url.replace(':id', user_id);
+ return $.ajax({
+ url: url,
+ dataType: "json"
+ }).done(function(user) {
+ return callback(user);
+ });
+ };
+
+ UsersSelect.prototype.users = function(query, callback) {
+ var url;
+ url = this.buildUrl(this.usersPath);
+ return $.ajax({
+ url: url,
+ data: {
+ search: query,
+ per_page: 20,
+ active: true,
+ project_id: this.projectId,
+ group_id: this.groupId,
+ current_user: this.showCurrentUser,
+ author_id: this.authorId
+ },
+ dataType: "json"
+ }).done(function(users) {
+ return callback(users);
+ });
+ };
+
+ UsersSelect.prototype.buildUrl = function(url) {
+ if (gon.relative_url_root != null) {
+ url = gon.relative_url_root.replace(/\/$/, '') + url;
+ }
+ return url;
+ };
+
+ return UsersSelect;
+
+})();
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
deleted file mode 100644
index 344be811e0d..00000000000
--- a/app/assets/javascripts/users_select.js.coffee
+++ /dev/null
@@ -1,330 +0,0 @@
-class @UsersSelect
- constructor: (currentUser) ->
- @usersPath = "/autocomplete/users.json"
- @userPath = "/autocomplete/users/:id.json"
- if currentUser?
- @currentUser = JSON.parse(currentUser)
-
- $('.js-user-search').each (i, dropdown) =>
- $dropdown = $(dropdown)
- @projectId = $dropdown.data('project-id')
- @showCurrentUser = $dropdown.data('current-user')
- showNullUser = $dropdown.data('null-user')
- showAnyUser = $dropdown.data('any-user')
- firstUser = $dropdown.data('first-user')
- @authorId = $dropdown.data('author-id')
- selectedId = $dropdown.data('selected')
- defaultLabel = $dropdown.data('default-label')
- issueURL = $dropdown.data('issueUpdate')
- $selectbox = $dropdown.closest('.selectbox')
- $block = $selectbox.closest('.block')
- abilityName = $dropdown.data('ability-name')
- $value = $block.find('.value')
- $collapsedSidebar = $block.find('.sidebar-collapsed-user')
- $loading = $block.find('.block-loading').fadeOut()
-
- $block.on('click', '.js-assign-yourself', (e) =>
- e.preventDefault()
- assignTo(@currentUser.id)
- )
-
- assignTo = (selected) ->
- data = {}
- data[abilityName] = {}
- data[abilityName].assignee_id = if selected? then selected else null
- $loading
- .fadeIn()
- $dropdown.trigger('loading.gl.dropdown')
- $.ajax(
- type: 'PUT'
- dataType: 'json'
- url: issueURL
- data: data
- ).done (data) ->
- $dropdown.trigger('loaded.gl.dropdown')
- $loading.fadeOut()
- $selectbox.hide()
-
- if data.assignee
- user =
- name: data.assignee.name
- username: data.assignee.username
- avatar: data.assignee.avatar_url
- else
- user =
- name: 'Unassigned'
- username: ''
- avatar: ''
- $value.html(assigneeTemplate(user))
-
- $collapsedSidebar
- .attr('title', user.name)
- .tooltip('fixTitle')
-
- $collapsedSidebar.html(collapsedAssigneeTemplate(user))
-
-
- collapsedAssigneeTemplate = _.template(
- '<% if( avatar ) { %>
- <a class="author_link" href="/u/<%- username %>">
- <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>">
- </a>
- <% } else { %>
- <i class="fa fa-user"></i>
- <% } %>'
- )
-
- assigneeTemplate = _.template(
- '<% if (username) { %>
- <a class="author_link bold" href="/u/<%- username %>">
- <% if( avatar ) { %>
- <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>">
- <% } %>
- <span class="author"><%- name %></span>
- <span class="username">
- @<%- username %>
- </span>
- </a>
- <% } else { %>
- <span class="no-value assign-yourself">
- No assignee -
- <a href="#" class="js-assign-yourself">
- assign yourself
- </a>
- </span>
- <% } %>'
- )
-
- $dropdown.glDropdown(
- data: (term, callback) =>
- isAuthorFilter = $('.js-author-search')
-
- @users term, (users) =>
- if term.length is 0
- showDivider = 0
-
- if firstUser
- # Move current user to the front of the list
- for obj, index in users
- if obj.username == firstUser
- users.splice(index, 1)
- users.unshift(obj)
- break
-
- if showNullUser
- showDivider += 1
- users.unshift(
- beforeDivider: true
- name: 'Unassigned',
- id: 0
- )
-
- if showAnyUser
- showDivider += 1
- name = showAnyUser
- name = 'Any User' if name == true
- anyUser = {
- beforeDivider: true
- name: name,
- id: null
- }
- users.unshift(anyUser)
-
- if showDivider
- users.splice(showDivider, 0, "divider")
-
- # Send the data back
- callback users
- filterable: true
- filterRemote: true
- search:
- fields: ['name', 'username']
- selectable: true
- fieldName: $dropdown.data('field-name')
-
- toggleLabel: (selected) ->
- if selected && 'id' of selected
- if selected.text then selected.text else selected.name
- else
- defaultLabel
-
- inputId: 'issue_assignee_id'
-
- hidden: (e) ->
- $selectbox.hide()
- # display:block overrides the hide-collapse rule
- $value.css('display', '')
-
- clicked: (user) ->
- page = $('body').data 'page'
- isIssueIndex = page is 'projects:issues:index'
- isMRIndex = page is page is 'projects:merge_requests:index'
- if $dropdown.hasClass('js-filter-bulk-update')
- return
-
- if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
- selectedId = user.id
- Issuable.filterResults $dropdown.closest('form')
- else if $dropdown.hasClass 'js-filter-submit'
- $dropdown.closest('form').submit()
- else
- selected = $dropdown
- .closest('.selectbox')
- .find("input[name='#{$dropdown.data('field-name')}']").val()
- assignTo(selected)
-
- renderRow: (user) ->
- username = if user.username then "@#{user.username}" else ""
- avatar = if user.avatar_url then user.avatar_url else false
- selected = if user.id is selectedId then "is-active" else ""
- img = ""
-
- if user.beforeDivider?
- "<li>
- <a href='#' class='#{selected}'>
- #{user.name}
- </a>
- </li>"
- else
- if avatar
- img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
-
- # split into three parts so we can remove the username section if nessesary
- listWithName = "<li>
- <a href='#' class='dropdown-menu-user-link #{selected}'>
- #{img}
- <strong class='dropdown-menu-user-full-name'>
- #{user.name}
- </strong>"
-
- listWithUserName = "<span class='dropdown-menu-user-username'>
- #{username}
- </span>"
- listClosingTags = "</a>
- </li>"
-
-
- if username is ''
- listWithUserName = ''
-
- listWithName + listWithUserName + listClosingTags
- )
-
- $('.ajax-users-select').each (i, select) =>
- @projectId = $(select).data('project-id')
- @groupId = $(select).data('group-id')
- @showCurrentUser = $(select).data('current-user')
- @authorId = $(select).data('author-id')
- showNullUser = $(select).data('null-user')
- showAnyUser = $(select).data('any-user')
- showEmailUser = $(select).data('email-user')
- firstUser = $(select).data('first-user')
-
- $(select).select2
- placeholder: "Search for a user"
- multiple: $(select).hasClass('multiselect')
- minimumInputLength: 0
- query: (query) =>
- @users query.term, (users) =>
- data = { results: users }
-
- if query.term.length == 0
- if firstUser
- # Move current user to the front of the list
- for obj, index in data.results
- if obj.username == firstUser
- data.results.splice(index, 1)
- data.results.unshift(obj)
- break
-
- if showNullUser
- nullUser = {
- name: 'Unassigned',
- id: 0
- }
- data.results.unshift(nullUser)
-
- if showAnyUser
- name = showAnyUser
- name = 'Any User' if name == true
- anyUser = {
- name: name,
- id: null
- }
- data.results.unshift(anyUser)
-
- if showEmailUser && data.results.length == 0 && query.term.match(/^[^@]+@[^@]+$/)
- emailUser = {
- name: "Invite \"#{query.term}\"",
- username: query.term,
- id: query.term
- }
- data.results.unshift(emailUser)
-
- query.callback(data)
-
- initSelection: (args...) =>
- @initSelection(args...)
- formatResult: (args...) =>
- @formatResult(args...)
- formatSelection: (args...) =>
- @formatSelection(args...)
- dropdownCssClass: "ajax-users-dropdown"
- escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
- m
-
- initSelection: (element, callback) ->
- id = $(element).val()
- if id == "0"
- nullUser = { name: 'Unassigned' }
- callback(nullUser)
- else if id != ""
- @user(id, callback)
-
- formatResult: (user) ->
- if user.avatar_url
- avatar = user.avatar_url
- else
- avatar = gon.default_avatar_url
-
- "<div class='user-result #{'no-username' unless user.username}'>
- <div class='user-image'><img class='avatar s24' src='#{avatar}'></div>
- <div class='user-name'>#{user.name}</div>
- <div class='user-username'>#{user.username || ""}</div>
- </div>"
-
- formatSelection: (user) ->
- user.name
-
- user: (user_id, callback) =>
- url = @buildUrl(@userPath)
- url = url.replace(':id', user_id)
-
- $.ajax(
- url: url
- dataType: "json"
- ).done (user) ->
- callback(user)
-
- # Return users list. Filtered by query
- # Only active users retrieved
- users: (query, callback) =>
- url = @buildUrl(@usersPath)
-
- $.ajax(
- url: url
- data:
- search: query
- per_page: 20
- active: true
- project_id: @projectId
- group_id: @groupId
- current_user: @showCurrentUser
- author_id: @authorId
- dataType: "json"
- ).done (users) ->
- callback(users)
-
- buildUrl: (url) ->
- url = gon.relative_url_root.replace(/\/$/, '') + url if gon.relative_url_root?
- return url
diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js
new file mode 100644
index 00000000000..6fa70f57277
--- /dev/null
+++ b/app/assets/javascripts/wikis.js
@@ -0,0 +1,33 @@
+
+/*= require latinise */
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.Wikis = (function() {
+ function Wikis() {
+ this.slugify = bind(this.slugify, this);
+ $('.new-wiki-page').on('submit', (function(_this) {
+ return function(e) {
+ var field, path, slug;
+ $('[data-error~=slug]').addClass('hidden');
+ field = $('#new_wiki_path');
+ slug = _this.slugify(field.val());
+ if (slug.length > 0) {
+ path = field.attr('data-wikis-path');
+ location.href = path + '/' + slug;
+ return e.preventDefault();
+ }
+ };
+ })(this));
+ }
+
+ Wikis.prototype.dasherize = function(value) {
+ return value.replace(/[_\s]+/g, '-');
+ };
+
+ Wikis.prototype.slugify = function(value) {
+ return this.dasherize(value.trim().toLowerCase().latinise());
+ };
+
+ return Wikis;
+
+})();
diff --git a/app/assets/javascripts/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee
deleted file mode 100644
index 1ee827f1fa3..00000000000
--- a/app/assets/javascripts/wikis.js.coffee
+++ /dev/null
@@ -1,19 +0,0 @@
-#= require latinise
-
-class @Wikis
- constructor: ->
- $('.new-wiki-page').on 'submit', (e) =>
- $('[data-error~=slug]').addClass('hidden')
- field = $('#new_wiki_path')
- slug = @slugify(field.val())
-
- if (slug.length > 0)
- path = field.attr('data-wikis-path')
- location.href = path + '/' + slug
- e.preventDefault()
-
- dasherize: (value) ->
- value.replace(/[_\s]+/g, '-')
-
- slugify: (value) =>
- @dasherize(value.trim().toLowerCase().latinise())
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
new file mode 100644
index 00000000000..b2289eb6a50
--- /dev/null
+++ b/app/assets/javascripts/zen_mode.js
@@ -0,0 +1,71 @@
+
+/*= provides zen_mode:enter */
+
+/*= provides zen_mode:leave */
+
+/*= require jquery.scrollTo */
+
+/*= require dropzone */
+
+/*= require mousetrap */
+
+/*= require mousetrap/pause */
+this.ZenMode = (function() {
+ function ZenMode() {
+ this.active_backdrop = null;
+ this.active_textarea = null;
+ $(document).on('click', '.js-zen-enter', function(e) {
+ e.preventDefault();
+ return $(e.currentTarget).trigger('zen_mode:enter');
+ });
+ $(document).on('click', '.js-zen-leave', function(e) {
+ e.preventDefault();
+ return $(e.currentTarget).trigger('zen_mode:leave');
+ });
+ $(document).on('zen_mode:enter', (function(_this) {
+ return function(e) {
+ return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop'));
+ };
+ })(this));
+ $(document).on('zen_mode:leave', (function(_this) {
+ return function(e) {
+ return _this.exit();
+ };
+ })(this));
+ $(document).on('keydown', function(e) {
+ if (e.keyCode === 27) {
+ e.preventDefault();
+ return $(document).trigger('zen_mode:leave');
+ }
+ });
+ }
+
+ ZenMode.prototype.enter = function(backdrop) {
+ Mousetrap.pause();
+ this.active_backdrop = $(backdrop);
+ this.active_backdrop.addClass('fullscreen');
+ this.active_textarea = this.active_backdrop.find('textarea');
+ this.active_textarea.removeAttr('style');
+ return this.active_textarea.focus();
+ };
+
+ ZenMode.prototype.exit = function() {
+ if (this.active_textarea) {
+ Mousetrap.unpause();
+ this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
+ this.scrollTo(this.active_textarea);
+ this.active_textarea = null;
+ this.active_backdrop = null;
+ return Dropzone.forElement('.div-dropzone').enable();
+ }
+ };
+
+ ZenMode.prototype.scrollTo = function(zen_area) {
+ return $.scrollTo(zen_area, 0, {
+ offset: -150
+ });
+ };
+
+ return ZenMode;
+
+})();
diff --git a/app/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee
deleted file mode 100644
index 99f35ecfb0f..00000000000
--- a/app/assets/javascripts/zen_mode.js.coffee
+++ /dev/null
@@ -1,80 +0,0 @@
-# Zen Mode (full screen) textarea
-#
-#= provides zen_mode:enter
-#= provides zen_mode:leave
-#
-#= require jquery.scrollTo
-#= require dropzone
-#= require mousetrap
-#= require mousetrap/pause
-#
-# ### Events
-#
-# `zen_mode:enter`
-#
-# Fired when the "Edit in fullscreen" link is clicked.
-#
-# **Synchronicity** Sync
-# **Bubbles** Yes
-# **Cancelable** No
-# **Target** a.js-zen-enter
-#
-# `zen_mode:leave`
-#
-# Fired when the "Leave Fullscreen" link is clicked.
-#
-# **Synchronicity** Sync
-# **Bubbles** Yes
-# **Cancelable** No
-# **Target** a.js-zen-leave
-#
-class @ZenMode
- constructor: ->
- @active_backdrop = null
- @active_textarea = null
-
- $(document).on 'click', '.js-zen-enter', (e) ->
- e.preventDefault()
- $(e.currentTarget).trigger('zen_mode:enter')
-
- $(document).on 'click', '.js-zen-leave', (e) ->
- e.preventDefault()
- $(e.currentTarget).trigger('zen_mode:leave')
-
- $(document).on 'zen_mode:enter', (e) =>
- @enter($(e.target).closest('.md-area').find('.zen-backdrop'))
- $(document).on 'zen_mode:leave', (e) =>
- @exit()
-
- $(document).on 'keydown', (e) ->
- if e.keyCode == 27 # Esc
- e.preventDefault()
- $(document).trigger('zen_mode:leave')
-
- enter: (backdrop) ->
- Mousetrap.pause()
-
- @active_backdrop = $(backdrop)
- @active_backdrop.addClass('fullscreen')
-
- @active_textarea = @active_backdrop.find('textarea')
-
- # Prevent a user-resized textarea from persisting to fullscreen
- @active_textarea.removeAttr('style')
- @active_textarea.focus()
-
- exit: ->
- if @active_textarea
- Mousetrap.unpause()
-
- @active_textarea.closest('.zen-backdrop').removeClass('fullscreen')
-
- @scrollTo(@active_textarea)
-
- @active_textarea = null
- @active_backdrop = null
-
- Dropzone.forElement('.div-dropzone').enable()
-
- scrollTo: (zen_area) ->
- $.scrollTo(zen_area, 0, offset: -150)
diff --git a/coffee-asset-pipeline-comments.js b/coffee-asset-pipeline-comments.js
new file mode 100644
index 00000000000..67bf40b0321
--- /dev/null
+++ b/coffee-asset-pipeline-comments.js
@@ -0,0 +1,37 @@
+var fs = require('fs'),
+ os = require('os'),
+ lineReader = require('line-reader');
+
+function help(){
+ console.log('Usage: node coffee-asset-pipeline-comments.js file1.coffee');
+}
+
+function compilefile(pathname, outdir){
+ var src = '';
+
+ lineReader.eachLine(pathname, function(line, last) {
+ // asset pipeline comments
+ if ( line.indexOf('#') === 0 && line.indexOf('=') === 1) {
+ line = line.replace('#', '');
+ line = '###' + line + ' ###';
+ }
+ src += line;
+ src += os.EOL;
+
+ if ( last ) {
+ fs.writeFile(pathname, src);
+ }
+ });
+}
+
+function main(){
+ if (process.argv.length <=2){
+ help();
+ return;
+ }
+
+ console.log('Compiling: '+ process.argv[2]);
+ compilefile(process.argv[2]);
+}
+
+main();
diff --git a/node_modules/line-reader/.eslintrc b/node_modules/line-reader/.eslintrc
new file mode 100644
index 00000000000..d097e3a5629
--- /dev/null
+++ b/node_modules/line-reader/.eslintrc
@@ -0,0 +1,14 @@
+{
+ "env": {
+ "node": true,
+ "mocha": true
+ },
+ "extends": "airbnb/base",
+ "rules": {
+ "no-var": 0,
+ "indent": [2, 2, {"VariableDeclarator": { "var": 2 }}],
+ "func-names": 0,
+ "strict": [2, "global"],
+ }
+}
+
diff --git a/node_modules/line-reader/.npmignore b/node_modules/line-reader/.npmignore
new file mode 100644
index 00000000000..68286e6b826
--- /dev/null
+++ b/node_modules/line-reader/.npmignore
@@ -0,0 +1,35 @@
+
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules
+jspm_packages
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
diff --git a/node_modules/line-reader/LICENSE b/node_modules/line-reader/LICENSE
new file mode 100644
index 00000000000..4202b5e08b9
--- /dev/null
+++ b/node_modules/line-reader/LICENSE
@@ -0,0 +1,20 @@
+Copyright (C) 2012 by Nick Ewing
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/node_modules/line-reader/README.md b/node_modules/line-reader/README.md
new file mode 100644
index 00000000000..decdcad6426
--- /dev/null
+++ b/node_modules/line-reader/README.md
@@ -0,0 +1,166 @@
+Line Reader
+===========
+
+Asynchronous, buffered, line-by-line file/stream reader with support for
+user-defined line separators.
+
+Install
+-------
+
+`npm install line-reader`
+
+Usage
+-----
+
+The `eachLine` function reads each line of the given file. Upon each new line,
+the given callback function is called with two parameters: the line read and a
+boolean value specifying whether the line read was the last line of the file.
+If the callback returns `false`, reading will stop and the file will be closed.
+
+ var lineReader = require('line-reader');
+
+ lineReader.eachLine('file.txt', function(line, last) {
+ console.log(line);
+
+ if (/* done */) {
+ return false; // stop reading
+ }
+ });
+
+`eachLine` can also be used in an asynchronous manner by providing a third
+callback parameter like so:
+
+ var lineReader = require('line-reader');
+
+ lineReader.eachLine('file.txt', function(line, last, cb) {
+ console.log(line);
+
+ if (/* done */) {
+ cb(false); // stop reading
+ } else {
+ cb();
+ }
+ });
+
+You can provide an optional second node-style callback that will be called with
+`(err)` on failure or `()` when finished (even if you manually terminate iteration
+by returning `false` from the iteratee):
+
+ var lineReader = require('line-reader');
+
+ // read all lines:
+ lineReader.eachLine('file.txt', function(line) {
+ console.log(line);
+ }).then(function (err) {
+ if (err) throw err;
+ console.log("I'm done!!");
+ });
+
+For more granular control, `open`, `hasNextLine`, and `nextLine` maybe be used
+to iterate a file (but you must `close` it yourself):
+
+ // or read line by line:
+ lineReader.open('file.txt', function(err, reader) {
+ if (err) throw err;
+ if (reader.hasNextLine()) {
+ reader.nextLine(function(err, line) {
+ try {
+ if (err) throw err;
+ console.log(line);
+ } finally {
+ reader.close(function(err) {
+ if (err) throw err;
+ });
+ }
+ });
+ }
+ else {
+ reader.close(function(err) {
+ if (err) throw err;
+ });
+ }
+ });
+
+You may provide additional options in a hash before the callbacks to `eachLine` or `open`:
+* `separator` - a `string` or `RegExp` separator (defaults to `/\r\n?|\n/`)
+* `encoding` - file encoding (defaults to `'utf8'`)
+* `bufferSize` - amount of bytes to buffer (defaults to 1024)
+
+For example:
+
+ lineReader.eachLine('file.txt', {separator: ';', encoding: 'utf8'}, function(line, last, cb) {
+ console.log(line);
+ });
+ lineReader.open('file.txt', {bufferSize: 1024}, function(err, reader) {
+ ...
+ });
+
+Streams
+-------
+
+Both `eachLine` and `open` support passing either a file name or a read stream:
+
+ // reading from stdin
+ lineReader.eachLine(process.stdin, function(line) {});
+
+ // reading with file position boundaries
+ var readStream = fs.createReadStream('test.log', { start: 0, end: 10000 });
+ lineReader.eachLine(readStream, function(line) {});
+
+Note however that if you're reading user input from stdin then the
+[readline module](https://nodejs.org/api/readline.html) is probably a better choice.
+
+Promises
+--------
+
+`eachLine` and `open` are compatible with `promisify` from [bluebird](https://github.com/petkaantonov/bluebird/blob/master/API.md#promisepromisifyfunction-nodefunction--dynamic-receiver---function):
+
+ var lineReader = require('line-reader'),
+ Promise = require('bluebird');
+
+ var eachLine = Promise.promisify(lineReader.eachLine);
+ eachLine('file.txt', function(line) {
+ console.log(line);
+ }).then(function() {
+ console.log('done');
+ }).catch(function(err) {
+ console.error(err);
+ });
+
+If you're using a promise library that doesn't have a promisify function, here's how you can do it:
+
+ var lineReader = require('line-reader'),
+ Promise = require(...);
+
+ var eachLine = function(filename, options, iteratee) {
+ return new Promise(function(resolve, reject) {
+ lineReader.eachLine(filename, options, iteratee, function(err) {
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ });
+ });
+ }
+ eachLine('file.txt', function(line) {
+ console.log(line);
+ }).then(function() {
+ console.log('done');
+ }).catch(function(err) {
+ console.error(err);
+ });
+
+Contributors
+------------
+
+* Nick Ewing
+* Andy Edwards (jedwards1211)
+* Jameson Little (beatgammit)
+* Masum (masumsoft)
+* Matthew Caruana Galizia (mattcg)
+* Ricardo Bin (ricardohbin)
+
+Paul Em has also written a reverse-version of this gem to read files from bottom to top: [reverse-line-reader](https://github.com/paul-em/reverse-line-reader).
+
+Copyright 2011 Nick Ewing.
diff --git a/node_modules/line-reader/lib/line_reader.js b/node_modules/line-reader/lib/line_reader.js
new file mode 100644
index 00000000000..f1fd87342a2
--- /dev/null
+++ b/node_modules/line-reader/lib/line_reader.js
@@ -0,0 +1,306 @@
+'use strict';
+
+var fs = require('fs'),
+ StringDecoder = require('string_decoder').StringDecoder;
+
+function createLineReader(readStream, options, creationCb) {
+ if (options instanceof Function) {
+ creationCb = options;
+ options = undefined;
+ }
+ if (!options) options = {};
+
+ var encoding = options.encoding || 'utf8',
+ separator = options.separator || /\r\n?|\n/,
+ bufferSize = options.bufferSize || 1024,
+ bufferStr = '',
+ decoder = new StringDecoder(encoding),
+ closed = false,
+ eof = false,
+ separatorIndex = -1,
+ separatorLen,
+ readDefer,
+ moreToRead = false,
+ findSeparator;
+
+ if (separator instanceof RegExp) {
+ findSeparator = function() {
+ var result = separator.exec(bufferStr);
+ if (result && (result.index + result[0].length < bufferStr.length || eof)) {
+ separatorIndex = result.index;
+ separatorLen = result[0].length;
+ } else {
+ separatorIndex = -1;
+ separatorLen = 0;
+ }
+ };
+ } else {
+ separatorLen = separator.length;
+ findSeparator = function() {
+ separatorIndex = bufferStr.indexOf(separator);
+ };
+ }
+
+ function getReadStream() {
+ return readStream;
+ }
+
+ function close(cb) {
+ if (!closed) {
+ closed = true;
+ if (typeof readStream.close == 'function') {
+ readStream.close();
+ }
+ setImmediate(cb);
+ }
+ }
+
+ function onFailure(err) {
+ close(function(err2) {
+ return creationCb(err || err2);
+ });
+ }
+
+ function isOpen() {
+ return !closed;
+ }
+
+ function isClosed() {
+ return closed;
+ }
+
+ function waitForMoreToRead(cb) {
+ if (moreToRead) {
+ cb();
+ } else {
+ readDefer = cb;
+ }
+ }
+
+ function resumeDeferredRead() {
+ if (readDefer) {
+ readDefer();
+ readDefer = null;
+ }
+ }
+
+ function read(cb) {
+ waitForMoreToRead(function() {
+ var chunk;
+
+ try {
+ chunk = readStream.read(bufferSize);
+ } catch (err) {
+ cb(err);
+ }
+
+ if (chunk) {
+ bufferStr += decoder.write(chunk.slice(0, chunk.length));
+ } else {
+ moreToRead = false;
+ }
+
+ cb();
+ });
+ }
+
+ function onStreamReadable() {
+ moreToRead = true;
+ resumeDeferredRead();
+ }
+
+ function onStreamEnd() {
+ eof = true;
+ resumeDeferredRead();
+ }
+
+ readStream.on('readable', onStreamReadable);
+ readStream.on('end', onStreamEnd);
+ readStream.on('error', onFailure);
+
+ function shouldReadMore() {
+ findSeparator();
+
+ return separatorIndex < 0 && !eof;
+ }
+
+ function callWhile(conditionFn, bodyFn, doneCallback) {
+ if (conditionFn()) {
+ bodyFn(function (err) {
+ if (err) {
+ doneCallback(err);
+ } else {
+ setImmediate(callWhile, conditionFn, bodyFn, doneCallback);
+ }
+ });
+ } else {
+ doneCallback();
+ }
+ }
+
+ function readToSeparator(cb) {
+ callWhile(shouldReadMore, read, cb);
+ }
+
+ function hasNextLine() {
+ return bufferStr.length > 0 || !eof;
+ }
+
+ function nextLine(cb) {
+ if (closed) {
+ return cb(new Error('LineReader has been closed'));
+ }
+
+ function getLine(err) {
+ if (err) {
+ return cb(err);
+ }
+
+ if (separatorIndex < 0 && eof) {
+ separatorIndex = bufferStr.length;
+ }
+ var ret = bufferStr.substring(0, separatorIndex);
+
+ bufferStr = bufferStr.substring(separatorIndex + separatorLen);
+ separatorIndex = -1;
+ cb(undefined, ret);
+ }
+
+ findSeparator();
+
+ if (separatorIndex < 0) {
+ if (eof) {
+ if (hasNextLine()) {
+ separatorIndex = bufferStr.length;
+ getLine();
+ } else {
+ return cb(new Error('No more lines to read.'));
+ }
+ } else {
+ readToSeparator(getLine);
+ }
+ } else {
+ getLine();
+ }
+ }
+
+ readToSeparator(function(err) {
+ if (err) {
+ onFailure(err);
+ } else {
+ return creationCb(undefined, {
+ hasNextLine: hasNextLine,
+ nextLine: nextLine,
+ close: close,
+ isOpen: isOpen,
+ isClosed: isClosed,
+ getReadStream: getReadStream
+ });
+ }
+ });
+}
+
+function open(filenameOrStream, options, cb) {
+ if (options instanceof Function) {
+ cb = options;
+ options = undefined;
+ }
+
+ var readStream;
+
+ if (typeof filenameOrStream.read == 'function') {
+ readStream = filenameOrStream;
+ } else if (typeof filenameOrStream === 'string' || filenameOrStream instanceof String) {
+ readStream = fs.createReadStream(filenameOrStream);
+ } else {
+ cb(new Error('Invalid file argument for LineReader.open. Must be filename or stream.'));
+ return;
+ }
+
+ readStream.pause();
+ createLineReader(readStream, options, cb);
+}
+
+function eachLine(filename, options, iteratee, cb) {
+ if (options instanceof Function) {
+ cb = iteratee;
+ iteratee = options;
+ options = undefined;
+ }
+ var asyncIteratee = iteratee.length === 3;
+
+ var theReader;
+ var getReaderCb;
+
+ open(filename, options, function(err, reader) {
+ theReader = reader;
+ if (getReaderCb) {
+ getReaderCb(reader);
+ }
+
+ if (err) {
+ if (cb) cb(err);
+ return;
+ }
+
+ function finish(err) {
+ reader.close(function(err2) {
+ if (cb) cb(err || err2);
+ });
+ }
+
+ function newRead() {
+ if (reader.hasNextLine()) {
+ setImmediate(readNext);
+ } else {
+ finish();
+ }
+ }
+
+ function continueCb(continueReading) {
+ if (continueReading !== false) {
+ newRead();
+ } else {
+ finish();
+ }
+ }
+
+ function readNext() {
+ reader.nextLine(function(err, line) {
+ if (err) {
+ finish(err);
+ }
+
+ var last = !reader.hasNextLine();
+
+ if (asyncIteratee) {
+ iteratee(line, last, continueCb);
+ } else {
+ if (iteratee(line, last) !== false) {
+ newRead();
+ } else {
+ finish();
+ }
+ }
+ });
+ }
+
+ newRead();
+ });
+
+ // this hook is only for the sake of testing; if you choose to use it,
+ // please don't file any issues (unless you can also reproduce them without
+ // using this).
+ return {
+ getReader: function(cb) {
+ if (theReader) {
+ cb(theReader);
+ } else {
+ getReaderCb = cb;
+ }
+ }
+ };
+}
+
+module.exports.open = open;
+module.exports.eachLine = eachLine;
diff --git a/node_modules/line-reader/package.json b/node_modules/line-reader/package.json
new file mode 100644
index 00000000000..7915ce24dc2
--- /dev/null
+++ b/node_modules/line-reader/package.json
@@ -0,0 +1,61 @@
+{
+ "name": "line-reader",
+ "version": "0.4.0",
+ "description": "Asynchronous, buffered, line-by-line file/stream reader",
+ "url": "https://github.com/nickewing/line-reader",
+ "keywords": [
+ "file",
+ "line",
+ "reader",
+ "scanner"
+ ],
+ "author": {
+ "name": "Nick Ewing",
+ "email": "nick@nickewing.net"
+ },
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/nickewing/line-reader/issues"
+ },
+ "directories": {
+ "lib": "./lib"
+ },
+ "main": "./lib/line_reader",
+ "scripts": {
+ "test": "mocha test/line_reader.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://nickewing@github.com/nickewing/line-reader.git"
+ },
+ "devDependencies": {
+ "mocha": "^2.4.5"
+ },
+ "gitHead": "bd38cc8c5483e4b6799c01bc9b88819fda1461c7",
+ "homepage": "https://github.com/nickewing/line-reader#readme",
+ "_id": "line-reader@0.4.0",
+ "_shasum": "17e44818da0ac335675ba300954f94ef670e66fd",
+ "_from": "line-reader@*",
+ "_npmVersion": "3.6.0",
+ "_nodeVersion": "5.6.0",
+ "_npmUser": {
+ "name": "nickewing",
+ "email": "nick@nickewing.net"
+ },
+ "maintainers": [
+ {
+ "name": "nickewing",
+ "email": "nick@nickewing.net"
+ }
+ ],
+ "dist": {
+ "shasum": "17e44818da0ac335675ba300954f94ef670e66fd",
+ "tarball": "https://registry.npmjs.org/line-reader/-/line-reader-0.4.0.tgz"
+ },
+ "_npmOperationalInternal": {
+ "host": "packages-12-west.internal.npmjs.com",
+ "tmp": "tmp/line-reader-0.4.0.tgz_1458196332410_0.8783941068686545"
+ },
+ "_resolved": "https://registry.npmjs.org/line-reader/-/line-reader-0.4.0.tgz",
+ "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/line-reader/test.js b/node_modules/line-reader/test.js
new file mode 100644
index 00000000000..b8bfa55d330
--- /dev/null
+++ b/node_modules/line-reader/test.js
@@ -0,0 +1,89 @@
+// var file = './test/data/multibyte_file.txt';
+var file = './test/data/three_line_file.txt';
+// var file = './test/data/mac_os_9_file.txt';
+// var file = './test/data/separator_file.txt';
+
+var util = require('util');
+
+// var lineReader = require('readline').createInterface({
+ // input: require('fs').createReadStream(file)
+// });
+
+// lineReader.on('line', function (line) {
+ // console.log('Line from file:', util.inspect(line));
+// });
+
+
+
+var fs = require('fs');
+
+// var readStream = fs.createReadStream(file);
+// // var hash = crypto.createHash('sha1');
+
+// readStream
+ // .on('readable', function () {
+ // var chunk;
+ // while (null !== (chunk = readStream.read())) {
+ // console.log(chunk.length);
+ // }
+ // })
+ // .on('end', function () {
+ // console.log('done!');
+ // });
+
+
+
+// var readable = process.stdin;
+//
+
+// var readable = fs.createReadStream(file);
+// readable.pause();
+
+// console.log(readable.isPaused());
+
+// readable.on('readable', () => {
+ // var chunk;
+ // console.log('called');
+ // while (null !== (chunk = readable.read(4))) {
+ // console.log('got %d bytes of data: %s', chunk.length, util.inspect(chunk.toString()));
+ // }
+// });
+
+// readable.on('end', () => {
+ // console.log('done!');
+// });
+//
+//
+
+var lineReader = require('./lib/line_reader');
+var readStream = fs.createReadStream('development.log', { start: 0, end: 10000 });
+lineReader.eachLine(readStream, (line) => console.log(line));
+
+
+
+// var lineReader = require('./lib/line_reader'),
+ // Promise = require('bluebird');
+
+// var eachLine = Promise.promisify(lineReader.eachLine);
+// eachLine(process.stdin, function(line) {
+ // console.log(line);
+// }).then(function() {
+ // console.log('DONE');
+// }).catch(function(err) {
+ // console.error(err);
+// });
+
+
+// const readline = require('readline');
+
+// const rl = readline.createInterface({
+ // input: process.stdin,
+ // output: process.stdout
+// });
+
+// rl.question('What do you think of Node.js? ', (answer) => {
+ // TODO: Log the answer in a database
+ // console.log('Thank you for your valuable feedback:', answer);
+
+ // rl.close();
+// });
diff --git a/node_modules/line-reader/test/data/empty_file.txt b/node_modules/line-reader/test/data/empty_file.txt
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/node_modules/line-reader/test/data/empty_file.txt
diff --git a/node_modules/line-reader/test/data/mac_os_9_file.txt b/node_modules/line-reader/test/data/mac_os_9_file.txt
new file mode 100644
index 00000000000..b6fb9c0ceab
--- /dev/null
+++ b/node_modules/line-reader/test/data/mac_os_9_file.txt
@@ -0,0 +1 @@
+Jabberwocky ’Twas brillig, and the slithy toves Did gyre and gimble in the wabe; \ No newline at end of file
diff --git a/node_modules/line-reader/test/data/multi_separator_file.txt b/node_modules/line-reader/test/data/multi_separator_file.txt
new file mode 100644
index 00000000000..dd6e9d9b510
--- /dev/null
+++ b/node_modules/line-reader/test/data/multi_separator_file.txt
@@ -0,0 +1,2 @@
+foo||bar
+||baz
diff --git a/node_modules/line-reader/test/data/multibyte_file.txt b/node_modules/line-reader/test/data/multibyte_file.txt
new file mode 100644
index 00000000000..34d7f1682ad
--- /dev/null
+++ b/node_modules/line-reader/test/data/multibyte_file.txt
@@ -0,0 +1,2 @@
+ふうりうの初やおくの田植うた
+
diff --git a/node_modules/line-reader/test/data/normal_file.txt b/node_modules/line-reader/test/data/normal_file.txt
new file mode 100644
index 00000000000..27efca4a4ca
--- /dev/null
+++ b/node_modules/line-reader/test/data/normal_file.txt
@@ -0,0 +1,6 @@
+Jabberwocky
+
+’Twas brillig, and the slithy toves
+Did gyre and gimble in the wabe;
+
+
diff --git a/node_modules/line-reader/test/data/one_line_file.txt b/node_modules/line-reader/test/data/one_line_file.txt
new file mode 100644
index 00000000000..dfcec8b8886
--- /dev/null
+++ b/node_modules/line-reader/test/data/one_line_file.txt
@@ -0,0 +1 @@
+one line file
diff --git a/node_modules/line-reader/test/data/one_line_file_no_endline.txt b/node_modules/line-reader/test/data/one_line_file_no_endline.txt
new file mode 100644
index 00000000000..c727740fd8b
--- /dev/null
+++ b/node_modules/line-reader/test/data/one_line_file_no_endline.txt
@@ -0,0 +1 @@
+one line file no endline \ No newline at end of file
diff --git a/node_modules/line-reader/test/data/separator_file.txt b/node_modules/line-reader/test/data/separator_file.txt
new file mode 100644
index 00000000000..0c948c02bbc
--- /dev/null
+++ b/node_modules/line-reader/test/data/separator_file.txt
@@ -0,0 +1,2 @@
+foo;bar
+;baz
diff --git a/node_modules/line-reader/test/data/three_line_file.txt b/node_modules/line-reader/test/data/three_line_file.txt
new file mode 100644
index 00000000000..90a453c31e3
--- /dev/null
+++ b/node_modules/line-reader/test/data/three_line_file.txt
@@ -0,0 +1,3 @@
+This is line one.
+This is line two.
+This is line three.
diff --git a/node_modules/line-reader/test/data/unix_file.txt b/node_modules/line-reader/test/data/unix_file.txt
new file mode 100644
index 00000000000..27efca4a4ca
--- /dev/null
+++ b/node_modules/line-reader/test/data/unix_file.txt
@@ -0,0 +1,6 @@
+Jabberwocky
+
+’Twas brillig, and the slithy toves
+Did gyre and gimble in the wabe;
+
+
diff --git a/node_modules/line-reader/test/data/windows_buffer_overlap_file.txt b/node_modules/line-reader/test/data/windows_buffer_overlap_file.txt
new file mode 100644
index 00000000000..6c8808b19f8
--- /dev/null
+++ b/node_modules/line-reader/test/data/windows_buffer_overlap_file.txt
@@ -0,0 +1,2 @@
+test
+file \ No newline at end of file
diff --git a/node_modules/line-reader/test/data/windows_file.txt b/node_modules/line-reader/test/data/windows_file.txt
new file mode 100644
index 00000000000..baacab1fa9e
--- /dev/null
+++ b/node_modules/line-reader/test/data/windows_file.txt
@@ -0,0 +1,6 @@
+Jabberwocky
+
+’Twas brillig, and the slithy toves
+Did gyre and gimble in the wabe;
+
+
diff --git a/node_modules/line-reader/test/line_reader.js b/node_modules/line-reader/test/line_reader.js
new file mode 100644
index 00000000000..43e127dd439
--- /dev/null
+++ b/node_modules/line-reader/test/line_reader.js
@@ -0,0 +1,412 @@
+var lineReader = require('../lib/line_reader'),
+ assert = require('assert'),
+ fs = require('fs'),
+ testFilePath = __dirname + '/data/normal_file.txt',
+ windowsFilePath = __dirname + '/data/windows_file.txt',
+ windowsBufferOverlapFilePath = __dirname + '/data/windows_buffer_overlap_file.txt',
+ unixFilePath = __dirname + '/data/unix_file.txt',
+ macOs9FilePath = __dirname + '/data/mac_os_9_file.txt',
+ separatorFilePath = __dirname + '/data/separator_file.txt',
+ multiSeparatorFilePath = __dirname + '/data/multi_separator_file.txt',
+ multibyteFilePath = __dirname + '/data/multibyte_file.txt',
+ emptyFilePath = __dirname + '/data/empty_file.txt',
+ oneLineFilePath = __dirname + '/data/one_line_file.txt',
+ oneLineFileNoEndlinePath = __dirname + '/data/one_line_file_no_endline.txt',
+ threeLineFilePath = __dirname + '/data/three_line_file.txt',
+ testSeparatorFile = ['foo', 'bar\n', 'baz\n'],
+ testFile = [
+ 'Jabberwocky',
+ '',
+ '’Twas brillig, and the slithy toves',
+ 'Did gyre and gimble in the wabe;',
+ '',
+ ''
+ ],
+ testBufferOverlapFile = [
+ 'test',
+ 'file'
+ ];
+
+describe("lineReader", function() {
+ describe("eachLine", function() {
+ it("should read lines using the default separator", function(done) {
+ var i = 0;
+
+ lineReader.eachLine(testFilePath, function(line, last) {
+ assert.equal(testFile[i], line, 'Each line should be what we expect');
+ i += 1;
+
+ if (i === 6) {
+ assert.ok(last);
+ } else {
+ assert.ok(!last);
+ }
+ }, function(err) {
+ assert.ok(!err);
+ assert.equal(6, i);
+ done();
+ });
+ });
+
+ it("should read windows files by default", function(done) {
+ var i = 0;
+
+ lineReader.eachLine(windowsFilePath, function(line, last) {
+ assert.equal(testFile[i], line, 'Each line should be what we expect');
+ i += 1;
+
+ if (i === 6) {
+ assert.ok(last);
+ } else {
+ assert.ok(!last);
+ }
+ }, function(err) {
+ assert.ok(!err);
+ assert.equal(6, i);
+ done();
+ });
+ });
+
+ it("should handle \\r\\n overlapping buffer window correctly", function(done) {
+ var i = 0;
+ var bufferSize = 5;
+
+ lineReader.eachLine(windowsBufferOverlapFilePath, {bufferSize: bufferSize}, function(line, last) {
+ assert.equal(testBufferOverlapFile[i], line, 'Each line should be what we expect');
+ i += 1;
+
+ if (i === 2) {
+ assert.ok(last);
+ } else {
+ assert.ok(!last);
+ }
+ }, function(err) {
+ assert.ok(!err);
+ assert.equal(2, i);
+ done();
+ });
+ });
+
+ it("should read unix files by default", function(done) {
+ var i = 0;
+
+ lineReader.eachLine(unixFilePath, function(line, last) {
+ assert.equal(testFile[i], line, 'Each line should be what we expect');
+ i += 1;
+
+ if (i === 6) {
+ assert.ok(last);
+ } else {
+ assert.ok(!last);
+ }
+ }, function(err) {
+ assert.ok(!err);
+ assert.equal(6, i);
+ done();
+ });
+ });
+
+ it("should read mac os 9 files by default", function(done) {
+ var i = 0;
+
+ lineReader.eachLine(macOs9FilePath, function(line, last) {
+ assert.equal(testFile[i], line, 'Each line should be what we expect');
+ i += 1;
+
+ if (i === 6) {
+ assert.ok(last);
+ } else {
+ assert.ok(!last);
+ }
+ }, function(err) {
+ assert.ok(!err);
+ assert.equal(6, i);
+ done();
+ });
+ });
+
+ it("should allow continuation of line reading via a callback", function(done) {
+ var i = 0;
+
+ lineReader.eachLine(testFilePath, function(line, last, cb) {
+ assert.equal(testFile[i], line, 'Each line should be what we expect');
+ i += 1;
+
+ if (i === 6) {
+ assert.ok(last);
+ } else {
+ assert.ok(!last);
+ }
+
+ process.nextTick(cb);
+ }, function(err) {
+ assert.ok(!err);
+ assert.equal(6, i);
+ done();
+ });
+ });
+
+ it("should separate files using given separator", function(done) {
+ var i = 0;
+ lineReader.eachLine(separatorFilePath, {separator: ';'}, function(line, last) {
+ assert.equal(testSeparatorFile[i], line);
+ i += 1;
+
+ if (i === 3) {
+ assert.ok(last);
+ } else {
+ assert.ok(!last);
+ }
+ }, function(err) {
+ assert.ok(!err);
+ assert.equal(3, i);
+ done();
+ });
+ });
+
+ it("should separate files using given separator with more than one character", function(done) {
+ var i = 0;
+ lineReader.eachLine(multiSeparatorFilePath, {separator: '||'}, function(line, last) {
+ assert.equal(testSeparatorFile[i], line);
+ i += 1;
+
+ if (i === 3) {
+ assert.ok(last);
+ } else {
+ assert.ok(!last);
+ }
+ }, function(err) {
+ assert.ok(!err);
+ assert.equal(3, i);
+ done();
+ });
+ });
+
+ it("should allow early termination of line reading", function(done) {
+ var i = 0;
+ lineReader.eachLine(testFilePath, function(line, last) {
+ assert.equal(testFile[i], line, 'Each line should be what we expect');
+ i += 1;
+
+ if (i === 2) {
+ return false;
+ }
+ }, function(err) {
+ assert.ok(!err);
+ assert.equal(2, i);
+ done();
+ });
+ });
+
+ it("should allow early termination of line reading via a callback", function(done) {
+ var i = 0;
+ lineReader.eachLine(testFilePath, function(line, last, cb) {
+ assert.equal(testFile[i], line, 'Each line should be what we expect');
+ i += 1;
+
+ if (i === 2) {
+ cb(false);
+ } else {
+ cb();
+ }
+
+ }, function(err) {
+ assert.ok(!err);
+ assert.equal(2, i);
+ done();
+ });
+ });
+
+ it("should not call callback on empty file", function(done) {
+ lineReader.eachLine(emptyFilePath, function(line) {
+ assert.ok(false, "Empty file should not cause any callbacks");
+ }, function(err) {
+ assert.ok(!err);
+ done()
+ });
+ });
+
+ it("should error when the user tries calls nextLine on a closed LineReader", function(done) {
+ lineReader.open(oneLineFilePath, function(err, reader) {
+ assert.ok(!err);
+ reader.close(function(err) {
+ assert.ok(!err);
+ reader.nextLine(function(err, line) {
+ assert.ok(err, "nextLine should have errored because the reader is closed");
+ done();
+ });
+ });
+ });
+ });
+
+ it("should work with a file containing only one line", function(done) {
+ lineReader.eachLine(oneLineFilePath, function(line, last) {
+ return true;
+ }, function(err) {
+ assert.ok(!err);
+ done();
+ });
+ });
+
+ it("should work with a file containing only one line and no endline character.", function(done) {
+ var count = 0; var isDone = false;
+ lineReader.eachLine(oneLineFileNoEndlinePath, function(line, last) {
+ assert.equal(last, true, 'last should be true');
+ return true;
+ }, function(err) {
+ assert.ok(!err);
+ done();
+ });
+ });
+
+ it("should close the file when eachLine finishes", function(done) {
+ var reader;
+ lineReader.eachLine(oneLineFilePath, function(line, last) {
+ return false;
+ }, function(err) {
+ assert.ok(!err);
+ assert.ok(reader.isClosed());
+ done();
+ }).getReader(function(_reader) {
+ reader = _reader;
+ });
+ });
+
+ it("should close the file if there is an error during eachLine", function(done) {
+ lineReader.eachLine(testFilePath, {bufferSize: 10}, function(line, last) {
+ }, function(err) {
+ assert.equal('a test error', err.message);
+ assert.ok(reader.isClosed());
+ done();
+ }).getReader(function(_reader) {
+ reader = _reader;
+
+ reader.getReadStream().read = function() {
+ throw new Error('a test error');
+ };
+ });
+ });
+ });
+
+ describe("open", function() {
+ it("should return a reader object and allow calls to nextLine", function(done) {
+ lineReader.open(testFilePath, function(err, reader) {
+ assert.ok(!err);
+ assert.ok(reader.hasNextLine());
+
+ assert.ok(reader.hasNextLine(), 'Calling hasNextLine multiple times should be ok');
+
+ reader.nextLine(function(err, line) {
+ assert.ok(!err);
+ assert.equal('Jabberwocky', line);
+ assert.ok(reader.hasNextLine());
+ reader.nextLine(function(err, line) {
+ assert.ok(!err);
+ assert.equal('', line);
+ assert.ok(reader.hasNextLine());
+ reader.nextLine(function(err, line) {
+ assert.ok(!err);
+ assert.equal('’Twas brillig, and the slithy toves', line);
+ assert.ok(reader.hasNextLine());
+ reader.nextLine(function(err, line) {
+ assert.ok(!err);
+ assert.equal('Did gyre and gimble in the wabe;', line);
+ assert.ok(reader.hasNextLine());
+ reader.nextLine(function(err, line) {
+ assert.ok(!err);
+ assert.equal('', line);
+ assert.ok(reader.hasNextLine());
+ reader.nextLine(function(err, line) {
+ assert.ok(!err);
+ assert.equal('', line);
+ assert.ok(!reader.hasNextLine());
+ reader.nextLine(function(err, line) {
+ assert.ok(err);
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+
+ it("should work with a file containing only one line", function(done) {
+ lineReader.open(oneLineFilePath, function(err, reader) {
+ assert.ok(!err);
+ reader.close(function(err) {
+ assert.ok(!err);
+ done();
+ });
+ });
+ });
+
+ it("should read multibyte characters on the buffer boundary", function(done) {
+ lineReader.open(multibyteFilePath, {separator: '\n', encoding: 'utf8', bufferSize: 2}, function(err, reader) {
+ assert.ok(!err);
+ assert.ok(reader.hasNextLine());
+ reader.nextLine(function(err, line) {
+ assert.ok(!err);
+ assert.equal('ふうりうの初やおくの田植うた', line,
+ "Should read multibyte characters on buffer boundary");
+ reader.close(function(err) {
+ assert.ok(!err);
+ done();
+ });
+ });
+ });
+ });
+
+ it("should support opened streams", function() {
+ var readStream = fs.createReadStream(testFilePath);
+
+ lineReader.open(readStream, function(err, reader) {
+ assert.ok(!err);
+ assert.ok(reader.hasNextLine());
+
+ assert.ok(reader.hasNextLine(), 'Calling hasNextLine multiple times should be ok');
+
+ reader.nextLine(function(err, line) {
+ assert.ok(!err);
+ assert.equal('Jabberwocky', line);
+ });
+ });
+ });
+
+ it("should handle error while opening read stream", function() {
+ lineReader.open('a file that does not exist', function(err, reader) {
+ assert.ok(err);
+ assert.ok(reader.isClosed());
+ });
+ });
+
+ describe("hasNextLine", function() {
+ it("should return true when buffer is empty but not at EOF", function(done) {
+ lineReader.open(threeLineFilePath, {separator: '\n', encoding: 'utf8', bufferSize: 36}, function(err, reader) {
+ assert.ok(!err);
+ reader.nextLine(function(err, line) {
+ assert.ok(!err);
+ assert.equal("This is line one.", line);
+ assert.ok(reader.hasNextLine());
+ reader.nextLine(function(err, line) {
+ assert.ok(!err);
+ assert.equal("This is line two.", line);
+ assert.ok(reader.hasNextLine());
+ reader.nextLine(function(err, line) {
+ assert.ok(!err);
+ assert.equal("This is line three.", line);
+ assert.ok(!reader.hasNextLine());
+ reader.close(function(err) {
+ assert.ok(!err);
+ done();
+ })
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js
new file mode 100644
index 00000000000..75b891c3ef0
--- /dev/null
+++ b/spec/javascripts/application_spec.js
@@ -0,0 +1,28 @@
+
+/*= require lib/utils/common_utils */
+describe('Application', function() {
+ return describe('disable buttons', function() {
+ fixture.preload('application.html');
+ beforeEach(function() {
+ return fixture.load('application.html');
+ });
+ it('should prevent default action for disabled buttons', function() {
+ var $button, isClicked;
+ gl.utils.preventDisabledButtons();
+ isClicked = false;
+ $button = $('#test-button');
+ $button.click(function() {
+ return isClicked = true;
+ });
+ $button.trigger('click');
+ return expect(isClicked).toBe(false);
+ });
+ return it('should be on the same page if a disabled link clicked', function() {
+ var locationBeforeLinkClick;
+ locationBeforeLinkClick = window.location.href;
+ gl.utils.preventDisabledButtons();
+ $('#test-link').click();
+ return expect(window.location.href).toBe(locationBeforeLinkClick);
+ });
+ });
+});
diff --git a/spec/javascripts/application_spec.js.coffee b/spec/javascripts/application_spec.js.coffee
deleted file mode 100644
index 4b6a2bb5440..00000000000
--- a/spec/javascripts/application_spec.js.coffee
+++ /dev/null
@@ -1,30 +0,0 @@
-#= require lib/utils/common_utils
-
-describe 'Application', ->
- describe 'disable buttons', ->
- fixture.preload('application.html')
-
- beforeEach ->
- fixture.load('application.html')
-
- it 'should prevent default action for disabled buttons', ->
-
- gl.utils.preventDisabledButtons()
-
- isClicked = false
- $button = $ '#test-button'
-
- $button.click -> isClicked = true
- $button.trigger 'click'
-
- expect(isClicked).toBe false
-
-
- it 'should be on the same page if a disabled link clicked', ->
-
- locationBeforeLinkClick = window.location.href
- gl.utils.preventDisabledButtons()
-
- $('#test-link').click()
-
- expect(window.location.href).toBe locationBeforeLinkClick
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
new file mode 100644
index 00000000000..ff4dd78f183
--- /dev/null
+++ b/spec/javascripts/awards_handler_spec.js
@@ -0,0 +1,180 @@
+
+/*= require awards_handler */
+
+/*= require jquery */
+
+/*= require jquery.cookie */
+
+/*= require ./fixtures/emoji_menu */
+var awardsHandler, lazyAssert;
+
+awardsHandler = null;
+
+window.gl || (window.gl = {});
+
+window.gon || (window.gon = {});
+
+gl.emojiAliases = function() {
+ return {
+ '+1': 'thumbsup',
+ '-1': 'thumbsdown'
+ };
+};
+
+gon.award_menu_url = '/emojis';
+
+lazyAssert = function(done, assertFn) {
+ return setTimeout(function() {
+ assertFn();
+ return done();
+ }, 333);
+};
+
+describe('AwardsHandler', function() {
+ fixture.preload('awards_handler.html');
+ beforeEach(function() {
+ fixture.load('awards_handler.html');
+ awardsHandler = new AwardsHandler;
+ spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) {
+ return function(url, emoji, cb) {
+ return cb();
+ };
+ })(this));
+ return spyOn(jQuery, 'get').and.callFake(function(req, cb) {
+ return cb(window.emojiMenu);
+ });
+ });
+ describe('::showEmojiMenu', function() {
+ it('should show emoji menu when Add emoji button clicked', function(done) {
+ $('.js-add-award').eq(0).click();
+ return lazyAssert(done, function() {
+ var $emojiMenu;
+ $emojiMenu = $('.emoji-menu');
+ expect($emojiMenu.length).toBe(1);
+ expect($emojiMenu.hasClass('is-visible')).toBe(true);
+ expect($emojiMenu.find('#emoji_search').length).toBe(1);
+ return expect($('.js-awards-block.current').length).toBe(1);
+ });
+ });
+ it('should also show emoji menu for the smiley icon in notes', function(done) {
+ $('.note-action-button').click();
+ return lazyAssert(done, function() {
+ var $emojiMenu;
+ $emojiMenu = $('.emoji-menu');
+ return expect($emojiMenu.length).toBe(1);
+ });
+ });
+ return it('should remove emoji menu when body is clicked', function(done) {
+ $('.js-add-award').eq(0).click();
+ return lazyAssert(done, function() {
+ var $emojiMenu;
+ $emojiMenu = $('.emoji-menu');
+ $('body').click();
+ expect($emojiMenu.length).toBe(1);
+ expect($emojiMenu.hasClass('is-visible')).toBe(false);
+ return expect($('.js-awards-block.current').length).toBe(0);
+ });
+ });
+ });
+ describe('::addAwardToEmojiBar', function() {
+ it('should add emoji to votes block', function() {
+ var $emojiButton, $votesBlock;
+ $votesBlock = $('.js-awards-block').eq(0);
+ awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
+ $emojiButton = $votesBlock.find('[data-emoji=heart]');
+ expect($emojiButton.length).toBe(1);
+ expect($emojiButton.next('.js-counter').text()).toBe('1');
+ return expect($votesBlock.hasClass('hidden')).toBe(false);
+ });
+ it('should remove the emoji when we click again', function() {
+ var $emojiButton, $votesBlock;
+ $votesBlock = $('.js-awards-block').eq(0);
+ awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
+ awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
+ $emojiButton = $votesBlock.find('[data-emoji=heart]');
+ return expect($emojiButton.length).toBe(0);
+ });
+ return it('should decrement the emoji counter', function() {
+ var $emojiButton, $votesBlock;
+ $votesBlock = $('.js-awards-block').eq(0);
+ awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
+ $emojiButton = $votesBlock.find('[data-emoji=heart]');
+ $emojiButton.next('.js-counter').text(5);
+ awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
+ expect($emojiButton.length).toBe(1);
+ return expect($emojiButton.next('.js-counter').text()).toBe('4');
+ });
+ });
+ describe('::getAwardUrl', function() {
+ return it('should return the url for request', function() {
+ return expect(awardsHandler.getAwardUrl()).toBe('/gitlab-org/gitlab-test/issues/8/toggle_award_emoji');
+ });
+ });
+ describe('::addAward and ::checkMutuality', function() {
+ return it('should handle :+1: and :-1: mutuality', function() {
+ var $thumbsDownEmoji, $thumbsUpEmoji, $votesBlock, awardUrl;
+ awardUrl = awardsHandler.getAwardUrl();
+ $votesBlock = $('.js-awards-block').eq(0);
+ $thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent();
+ $thumbsDownEmoji = $votesBlock.find('[data-emoji=thumbsdown]').parent();
+ awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
+ expect($thumbsUpEmoji.hasClass('active')).toBe(true);
+ expect($thumbsDownEmoji.hasClass('active')).toBe(false);
+ $thumbsUpEmoji.tooltip();
+ $thumbsDownEmoji.tooltip();
+ awardsHandler.addAward($votesBlock, awardUrl, 'thumbsdown', true);
+ expect($thumbsUpEmoji.hasClass('active')).toBe(false);
+ return expect($thumbsDownEmoji.hasClass('active')).toBe(true);
+ });
+ });
+ describe('::removeEmoji', function() {
+ return it('should remove emoji', function() {
+ var $votesBlock, awardUrl;
+ awardUrl = awardsHandler.getAwardUrl();
+ $votesBlock = $('.js-awards-block').eq(0);
+ awardsHandler.addAward($votesBlock, awardUrl, 'fire', false);
+ expect($votesBlock.find('[data-emoji=fire]').length).toBe(1);
+ awardsHandler.removeEmoji($votesBlock.find('[data-emoji=fire]').closest('button'));
+ return expect($votesBlock.find('[data-emoji=fire]').length).toBe(0);
+ });
+ });
+ describe('search', function() {
+ return it('should filter the emoji', function() {
+ $('.js-add-award').eq(0).click();
+ expect($('[data-emoji=angel]').is(':visible')).toBe(true);
+ expect($('[data-emoji=anger]').is(':visible')).toBe(true);
+ $('#emoji_search').val('ali').trigger('keyup');
+ expect($('[data-emoji=angel]').is(':visible')).toBe(false);
+ expect($('[data-emoji=anger]').is(':visible')).toBe(false);
+ return expect($('[data-emoji=alien]').is(':visible')).toBe(true);
+ });
+ });
+ return describe('emoji menu', function() {
+ var openEmojiMenuAndAddEmoji, selector;
+ selector = '[data-emoji=sunglasses]';
+ openEmojiMenuAndAddEmoji = function() {
+ var $block, $emoji, $menu;
+ $('.js-add-award').eq(0).click();
+ $menu = $('.emoji-menu');
+ $block = $('.js-awards-block');
+ $emoji = $menu.find(".emoji-menu-list-item " + selector);
+ expect($emoji.length).toBe(1);
+ expect($block.find(selector).length).toBe(0);
+ $emoji.click();
+ expect($menu.hasClass('.is-visible')).toBe(false);
+ return expect($block.find(selector).length).toBe(1);
+ };
+ it('should add selected emoji to awards block', function() {
+ return openEmojiMenuAndAddEmoji();
+ });
+ return it('should remove already selected emoji', function() {
+ var $block, $emoji;
+ openEmojiMenuAndAddEmoji();
+ $('.js-add-award').eq(0).click();
+ $block = $('.js-awards-block');
+ $emoji = $('.emoji-menu').find(".emoji-menu-list-item " + selector);
+ $emoji.click();
+ return expect($block.find(selector).length).toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/awards_handler_spec.js.coffee b/spec/javascripts/awards_handler_spec.js.coffee
deleted file mode 100644
index d7f9c6fc076..00000000000
--- a/spec/javascripts/awards_handler_spec.js.coffee
+++ /dev/null
@@ -1,200 +0,0 @@
-#= require awards_handler
-#= require jquery
-#= require jquery.cookie
-#= require ./fixtures/emoji_menu
-
-awardsHandler = null
-window.gl or= {}
-window.gon or= {}
-gl.emojiAliases = -> return { '+1': 'thumbsup', '-1': 'thumbsdown' }
-gon.award_menu_url = '/emojis'
-
-
-lazyAssert = (done, assertFn) ->
-
- setTimeout -> # Maybe jasmine.clock here?
- assertFn()
- done()
- , 333
-
-
-describe 'AwardsHandler', ->
-
- fixture.preload 'awards_handler.html'
-
- beforeEach ->
- fixture.load 'awards_handler.html'
- awardsHandler = new AwardsHandler
- spyOn(awardsHandler, 'postEmoji').and.callFake (url, emoji, cb) => cb()
- spyOn(jQuery, 'get').and.callFake (req, cb) -> cb window.emojiMenu
-
-
- describe '::showEmojiMenu', ->
-
- it 'should show emoji menu when Add emoji button clicked', (done) ->
-
- $('.js-add-award').eq(0).click()
-
- lazyAssert done, ->
- $emojiMenu = $ '.emoji-menu'
- expect($emojiMenu.length).toBe 1
- expect($emojiMenu.hasClass('is-visible')).toBe yes
- expect($emojiMenu.find('#emoji_search').length).toBe 1
- expect($('.js-awards-block.current').length).toBe 1
-
-
- it 'should also show emoji menu for the smiley icon in notes', (done) ->
-
- $('.note-action-button').click()
-
- lazyAssert done, ->
- $emojiMenu = $ '.emoji-menu'
- expect($emojiMenu.length).toBe 1
-
-
- it 'should remove emoji menu when body is clicked', (done) ->
-
- $('.js-add-award').eq(0).click()
-
- lazyAssert done, ->
- $emojiMenu = $('.emoji-menu')
- $('body').click()
- expect($emojiMenu.length).toBe 1
- expect($emojiMenu.hasClass('is-visible')).toBe no
- expect($('.js-awards-block.current').length).toBe 0
-
-
- describe '::addAwardToEmojiBar', ->
-
- it 'should add emoji to votes block', ->
-
- $votesBlock = $('.js-awards-block').eq 0
- awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
-
- $emojiButton = $votesBlock.find '[data-emoji=heart]'
-
- expect($emojiButton.length).toBe 1
- expect($emojiButton.next('.js-counter').text()).toBe '1'
- expect($votesBlock.hasClass('hidden')).toBe no
-
-
- it 'should remove the emoji when we click again', ->
-
- $votesBlock = $('.js-awards-block').eq 0
- awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
- awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
- $emojiButton = $votesBlock.find '[data-emoji=heart]'
-
- expect($emojiButton.length).toBe 0
-
-
- it 'should decrement the emoji counter', ->
-
- $votesBlock = $('.js-awards-block').eq 0
- awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
-
- $emojiButton = $votesBlock.find '[data-emoji=heart]'
- $emojiButton.next('.js-counter').text 5
-
- awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
-
- expect($emojiButton.length).toBe 1
- expect($emojiButton.next('.js-counter').text()).toBe '4'
-
-
- describe '::getAwardUrl', ->
-
- it 'should return the url for request', ->
-
- expect(awardsHandler.getAwardUrl()).toBe '/gitlab-org/gitlab-test/issues/8/toggle_award_emoji'
-
-
- describe '::addAward and ::checkMutuality', ->
-
- it 'should handle :+1: and :-1: mutuality', ->
-
- awardUrl = awardsHandler.getAwardUrl()
- $votesBlock = $('.js-awards-block').eq 0
- $thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent()
- $thumbsDownEmoji = $votesBlock.find('[data-emoji=thumbsdown]').parent()
-
- awardsHandler.addAward $votesBlock, awardUrl, 'thumbsup', no
-
- expect($thumbsUpEmoji.hasClass('active')).toBe yes
- expect($thumbsDownEmoji.hasClass('active')).toBe no
-
- $thumbsUpEmoji.tooltip()
- $thumbsDownEmoji.tooltip()
-
- awardsHandler.addAward $votesBlock, awardUrl, 'thumbsdown', yes
-
- expect($thumbsUpEmoji.hasClass('active')).toBe no
- expect($thumbsDownEmoji.hasClass('active')).toBe yes
-
-
- describe '::removeEmoji', ->
-
- it 'should remove emoji', ->
-
- awardUrl = awardsHandler.getAwardUrl()
- $votesBlock = $('.js-awards-block').eq 0
-
- awardsHandler.addAward $votesBlock, awardUrl, 'fire', no
- expect($votesBlock.find('[data-emoji=fire]').length).toBe 1
-
- awardsHandler.removeEmoji $votesBlock.find('[data-emoji=fire]').closest('button')
- expect($votesBlock.find('[data-emoji=fire]').length).toBe 0
-
-
- describe 'search', ->
-
- it 'should filter the emoji', ->
-
- $('.js-add-award').eq(0).click()
-
- expect($('[data-emoji=angel]').is(':visible')).toBe yes
- expect($('[data-emoji=anger]').is(':visible')).toBe yes
-
- $('#emoji_search').val('ali').trigger 'keyup'
-
- expect($('[data-emoji=angel]').is(':visible')).toBe no
- expect($('[data-emoji=anger]').is(':visible')).toBe no
- expect($('[data-emoji=alien]').is(':visible')).toBe yes
-
-
- describe 'emoji menu', ->
-
- selector = '[data-emoji=sunglasses]'
-
- openEmojiMenuAndAddEmoji = ->
-
- $('.js-add-award').eq(0).click()
-
- $menu = $ '.emoji-menu'
- $block = $ '.js-awards-block'
- $emoji = $menu.find ".emoji-menu-list-item #{selector}"
-
- expect($emoji.length).toBe 1
- expect($block.find(selector).length).toBe 0
-
- $emoji.click()
-
- expect($menu.hasClass('.is-visible')).toBe no
- expect($block.find(selector).length).toBe 1
-
-
- it 'should add selected emoji to awards block', ->
-
- openEmojiMenuAndAddEmoji()
-
-
- it 'should remove already selected emoji', ->
-
- openEmojiMenuAndAddEmoji()
- $('.js-add-award').eq(0).click()
-
- $block = $ '.js-awards-block'
- $emoji = $('.emoji-menu').find ".emoji-menu-list-item #{selector}"
-
- $emoji.click()
- expect($block.find(selector).length).toBe 0
diff --git a/spec/javascripts/behaviors/autosize_spec.js b/spec/javascripts/behaviors/autosize_spec.js
new file mode 100644
index 00000000000..f88df8544e1
--- /dev/null
+++ b/spec/javascripts/behaviors/autosize_spec.js
@@ -0,0 +1,17 @@
+
+/*= require behaviors/autosize */
+describe('Autosize behavior', function() {
+ var load;
+ beforeEach(function() {
+ return fixture.set('<textarea class="js-autosize" style="resize: vertical"></textarea>');
+ });
+ it('does not overwrite the resize property', function() {
+ load();
+ return expect($('textarea')).toHaveCss({
+ resize: 'vertical'
+ });
+ });
+ return load = function() {
+ return $(document).trigger('page:load');
+ };
+});
diff --git a/spec/javascripts/behaviors/autosize_spec.js.coffee b/spec/javascripts/behaviors/autosize_spec.js.coffee
deleted file mode 100644
index 7fc1d19c35f..00000000000
--- a/spec/javascripts/behaviors/autosize_spec.js.coffee
+++ /dev/null
@@ -1,11 +0,0 @@
-#= require behaviors/autosize
-
-describe 'Autosize behavior', ->
- beforeEach ->
- fixture.set('<textarea class="js-autosize" style="resize: vertical"></textarea>')
-
- it 'does not overwrite the resize property', ->
- load()
- expect($('textarea')).toHaveCss(resize: 'vertical')
-
- load = -> $(document).trigger('page:load')
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
new file mode 100644
index 00000000000..3c48b3632c5
--- /dev/null
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -0,0 +1,89 @@
+
+/*= require behaviors/quick_submit */
+describe('Quick Submit behavior', function() {
+ var keydownEvent;
+ fixture.preload('behaviors/quick_submit.html');
+ beforeEach(function() {
+ fixture.load('behaviors/quick_submit.html');
+ $('form').submit(function(e) {
+ return e.preventDefault();
+ });
+ return this.spies = {
+ submit: spyOnEvent('form', 'submit')
+ };
+ });
+ it('does not respond to other keyCodes', function() {
+ $('input.quick-submit-input').trigger(keydownEvent({
+ keyCode: 32
+ }));
+ return expect(this.spies.submit).not.toHaveBeenTriggered();
+ });
+ it('does not respond to Enter alone', function() {
+ $('input.quick-submit-input').trigger(keydownEvent({
+ ctrlKey: false,
+ metaKey: false
+ }));
+ return expect(this.spies.submit).not.toHaveBeenTriggered();
+ });
+ it('does not respond to repeated events', function() {
+ $('input.quick-submit-input').trigger(keydownEvent({
+ repeat: true
+ }));
+ return expect(this.spies.submit).not.toHaveBeenTriggered();
+ });
+ it('disables submit buttons', function() {
+ $('textarea').trigger(keydownEvent());
+ expect($('input[type=submit]')).toBeDisabled();
+ return expect($('button[type=submit]')).toBeDisabled();
+ });
+ if (navigator.userAgent.match(/Macintosh/)) {
+ it('responds to Meta+Enter', function() {
+ $('input.quick-submit-input').trigger(keydownEvent());
+ return expect(this.spies.submit).toHaveBeenTriggered();
+ });
+ it('excludes other modifier keys', function() {
+ $('input.quick-submit-input').trigger(keydownEvent({
+ altKey: true
+ }));
+ $('input.quick-submit-input').trigger(keydownEvent({
+ ctrlKey: true
+ }));
+ $('input.quick-submit-input').trigger(keydownEvent({
+ shiftKey: true
+ }));
+ return expect(this.spies.submit).not.toHaveBeenTriggered();
+ });
+ } else {
+ it('responds to Ctrl+Enter', function() {
+ $('input.quick-submit-input').trigger(keydownEvent());
+ return expect(this.spies.submit).toHaveBeenTriggered();
+ });
+ it('excludes other modifier keys', function() {
+ $('input.quick-submit-input').trigger(keydownEvent({
+ altKey: true
+ }));
+ $('input.quick-submit-input').trigger(keydownEvent({
+ metaKey: true
+ }));
+ $('input.quick-submit-input').trigger(keydownEvent({
+ shiftKey: true
+ }));
+ return expect(this.spies.submit).not.toHaveBeenTriggered();
+ });
+ }
+ return keydownEvent = function(options) {
+ var defaults;
+ if (navigator.userAgent.match(/Macintosh/)) {
+ defaults = {
+ keyCode: 13,
+ metaKey: true
+ };
+ } else {
+ defaults = {
+ keyCode: 13,
+ ctrlKey: true
+ };
+ }
+ return $.Event('keydown', $.extend({}, defaults, options));
+ };
+});
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js.coffee b/spec/javascripts/behaviors/quick_submit_spec.js.coffee
deleted file mode 100644
index d3b003a328a..00000000000
--- a/spec/javascripts/behaviors/quick_submit_spec.js.coffee
+++ /dev/null
@@ -1,70 +0,0 @@
-#= require behaviors/quick_submit
-
-describe 'Quick Submit behavior', ->
- fixture.preload('behaviors/quick_submit.html')
-
- beforeEach ->
- fixture.load('behaviors/quick_submit.html')
-
- # Prevent a form submit from moving us off the testing page
- $('form').submit (e) -> e.preventDefault()
-
- @spies = {
- submit: spyOnEvent('form', 'submit')
- }
-
- it 'does not respond to other keyCodes', ->
- $('input.quick-submit-input').trigger(keydownEvent(keyCode: 32))
-
- expect(@spies.submit).not.toHaveBeenTriggered()
-
- it 'does not respond to Enter alone', ->
- $('input.quick-submit-input').trigger(keydownEvent(ctrlKey: false, metaKey: false))
-
- expect(@spies.submit).not.toHaveBeenTriggered()
-
- it 'does not respond to repeated events', ->
- $('input.quick-submit-input').trigger(keydownEvent(repeat: true))
-
- expect(@spies.submit).not.toHaveBeenTriggered()
-
- it 'disables submit buttons', ->
- $('textarea').trigger(keydownEvent())
-
- expect($('input[type=submit]')).toBeDisabled()
- expect($('button[type=submit]')).toBeDisabled()
-
- # We cannot stub `navigator.userAgent` for CI's `rake teaspoon` task, so we'll
- # only run the tests that apply to the current platform
- if navigator.userAgent.match(/Macintosh/)
- it 'responds to Meta+Enter', ->
- $('input.quick-submit-input').trigger(keydownEvent())
-
- expect(@spies.submit).toHaveBeenTriggered()
-
- it 'excludes other modifier keys', ->
- $('input.quick-submit-input').trigger(keydownEvent(altKey: true))
- $('input.quick-submit-input').trigger(keydownEvent(ctrlKey: true))
- $('input.quick-submit-input').trigger(keydownEvent(shiftKey: true))
-
- expect(@spies.submit).not.toHaveBeenTriggered()
- else
- it 'responds to Ctrl+Enter', ->
- $('input.quick-submit-input').trigger(keydownEvent())
-
- expect(@spies.submit).toHaveBeenTriggered()
-
- it 'excludes other modifier keys', ->
- $('input.quick-submit-input').trigger(keydownEvent(altKey: true))
- $('input.quick-submit-input').trigger(keydownEvent(metaKey: true))
- $('input.quick-submit-input').trigger(keydownEvent(shiftKey: true))
-
- expect(@spies.submit).not.toHaveBeenTriggered()
-
- keydownEvent = (options) ->
- if navigator.userAgent.match(/Macintosh/)
- defaults = { keyCode: 13, metaKey: true }
- else
- defaults = { keyCode: 13, ctrlKey: true }
-
- $.Event('keydown', $.extend({}, defaults, options))
diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js
new file mode 100644
index 00000000000..1ab63363fd0
--- /dev/null
+++ b/spec/javascripts/behaviors/requires_input_spec.js
@@ -0,0 +1,40 @@
+
+/*= require behaviors/requires_input */
+describe('requiresInput', function() {
+ fixture.preload('behaviors/requires_input.html');
+ beforeEach(function() {
+ return fixture.load('behaviors/requires_input.html');
+ });
+ it('disables submit when any field is required', function() {
+ $('.js-requires-input').requiresInput();
+ return expect($('.submit')).toBeDisabled();
+ });
+ it('enables submit when no field is required', function() {
+ $('*[required=required]').removeAttr('required');
+ $('.js-requires-input').requiresInput();
+ return expect($('.submit')).not.toBeDisabled();
+ });
+ it('enables submit when all required fields are pre-filled', function() {
+ $('*[required=required]').remove();
+ $('.js-requires-input').requiresInput();
+ return expect($('.submit')).not.toBeDisabled();
+ });
+ it('enables submit when all required fields receive input', function() {
+ $('.js-requires-input').requiresInput();
+ $('#required1').val('input1').change();
+ expect($('.submit')).toBeDisabled();
+ $('#optional1').val('input1').change();
+ expect($('.submit')).toBeDisabled();
+ $('#required2').val('input2').change();
+ $('#required3').val('input3').change();
+ $('#required4').val('input4').change();
+ $('#required5').val('1').change();
+ return expect($('.submit')).not.toBeDisabled();
+ });
+ return it('is called on page:load event', function() {
+ var spy;
+ spy = spyOn($.fn, 'requiresInput');
+ $(document).trigger('page:load');
+ return expect(spy).toHaveBeenCalled();
+ });
+});
diff --git a/spec/javascripts/behaviors/requires_input_spec.js.coffee b/spec/javascripts/behaviors/requires_input_spec.js.coffee
deleted file mode 100644
index 61a17632173..00000000000
--- a/spec/javascripts/behaviors/requires_input_spec.js.coffee
+++ /dev/null
@@ -1,49 +0,0 @@
-#= require behaviors/requires_input
-
-describe 'requiresInput', ->
- fixture.preload('behaviors/requires_input.html')
-
- beforeEach ->
- fixture.load('behaviors/requires_input.html')
-
- it 'disables submit when any field is required', ->
- $('.js-requires-input').requiresInput()
-
- expect($('.submit')).toBeDisabled()
-
- it 'enables submit when no field is required', ->
- $('*[required=required]').removeAttr('required')
-
- $('.js-requires-input').requiresInput()
-
- expect($('.submit')).not.toBeDisabled()
-
- it 'enables submit when all required fields are pre-filled', ->
- $('*[required=required]').remove()
-
- $('.js-requires-input').requiresInput()
-
- expect($('.submit')).not.toBeDisabled()
-
- it 'enables submit when all required fields receive input', ->
- $('.js-requires-input').requiresInput()
-
- $('#required1').val('input1').change()
- expect($('.submit')).toBeDisabled()
-
- $('#optional1').val('input1').change()
- expect($('.submit')).toBeDisabled()
-
- $('#required2').val('input2').change()
- $('#required3').val('input3').change()
- $('#required4').val('input4').change()
- $('#required5').val('1').change()
-
- expect($('.submit')).not.toBeDisabled()
-
- it 'is called on page:load event', ->
- spy = spyOn($.fn, 'requiresInput')
-
- $(document).trigger('page:load')
-
- expect(spy).toHaveBeenCalled()
diff --git a/spec/javascripts/extensions/array_spec.js b/spec/javascripts/extensions/array_spec.js
new file mode 100644
index 00000000000..5df78d3caf5
--- /dev/null
+++ b/spec/javascripts/extensions/array_spec.js
@@ -0,0 +1,18 @@
+
+/*= require extensions/array */
+describe('Array extensions', function() {
+ describe('first', function() {
+ return it('returns the first item', function() {
+ var arr;
+ arr = [0, 1, 2, 3, 4, 5];
+ return expect(arr.first()).toBe(0);
+ });
+ });
+ return describe('last', function() {
+ return it('returns the last item', function() {
+ var arr;
+ arr = [0, 1, 2, 3, 4, 5];
+ return expect(arr.last()).toBe(5);
+ });
+ });
+});
diff --git a/spec/javascripts/extensions/array_spec.js.coffee b/spec/javascripts/extensions/array_spec.js.coffee
deleted file mode 100644
index 4ceac619422..00000000000
--- a/spec/javascripts/extensions/array_spec.js.coffee
+++ /dev/null
@@ -1,12 +0,0 @@
-#= require extensions/array
-
-describe 'Array extensions', ->
- describe 'first', ->
- it 'returns the first item', ->
- arr = [0, 1, 2, 3, 4, 5]
- expect(arr.first()).toBe(0)
-
- describe 'last', ->
- it 'returns the last item', ->
- arr = [0, 1, 2, 3, 4, 5]
- expect(arr.last()).toBe(5)
diff --git a/spec/javascripts/extensions/jquery_spec.js b/spec/javascripts/extensions/jquery_spec.js
new file mode 100644
index 00000000000..87581f24031
--- /dev/null
+++ b/spec/javascripts/extensions/jquery_spec.js
@@ -0,0 +1,38 @@
+
+/*= require extensions/jquery */
+describe('jQuery extensions', function() {
+ describe('disable', function() {
+ beforeEach(function() {
+ return fixture.set('<input type="text" />');
+ });
+ it('adds the disabled attribute', function() {
+ var $input;
+ $input = $('input').first();
+ $input.disable();
+ return expect($input).toHaveAttr('disabled', 'disabled');
+ });
+ return it('adds the disabled class', function() {
+ var $input;
+ $input = $('input').first();
+ $input.disable();
+ return expect($input).toHaveClass('disabled');
+ });
+ });
+ return describe('enable', function() {
+ beforeEach(function() {
+ return fixture.set('<input type="text" disabled="disabled" class="disabled" />');
+ });
+ it('removes the disabled attribute', function() {
+ var $input;
+ $input = $('input').first();
+ $input.enable();
+ return expect($input).not.toHaveAttr('disabled');
+ });
+ return it('removes the disabled class', function() {
+ var $input;
+ $input = $('input').first();
+ $input.enable();
+ return expect($input).not.toHaveClass('disabled');
+ });
+ });
+});
diff --git a/spec/javascripts/extensions/jquery_spec.js.coffee b/spec/javascripts/extensions/jquery_spec.js.coffee
deleted file mode 100644
index b10e16b7d01..00000000000
--- a/spec/javascripts/extensions/jquery_spec.js.coffee
+++ /dev/null
@@ -1,34 +0,0 @@
-#= require extensions/jquery
-
-describe 'jQuery extensions', ->
- describe 'disable', ->
- beforeEach ->
- fixture.set '<input type="text" />'
-
- it 'adds the disabled attribute', ->
- $input = $('input').first()
-
- $input.disable()
- expect($input).toHaveAttr('disabled', 'disabled')
-
- it 'adds the disabled class', ->
- $input = $('input').first()
-
- $input.disable()
- expect($input).toHaveClass('disabled')
-
- describe 'enable', ->
- beforeEach ->
- fixture.set '<input type="text" disabled="disabled" class="disabled" />'
-
- it 'removes the disabled attribute', ->
- $input = $('input').first()
-
- $input.enable()
- expect($input).not.toHaveAttr('disabled')
-
- it 'removes the disabled class', ->
- $input = $('input').first()
-
- $input.enable()
- expect($input).not.toHaveClass('disabled')
diff --git a/spec/javascripts/fixtures/emoji_menu.coffee b/spec/javascripts/fixtures/emoji_menu.coffee
deleted file mode 100644
index ce1a41390d2..00000000000
--- a/spec/javascripts/fixtures/emoji_menu.coffee
+++ /dev/null
@@ -1,957 +0,0 @@
-window.emojiMenu = """
- <div class='emoji-menu'>
- <input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" />
- <div class='emoji-menu-content'>
- <h5 class='emoji-menu-title'>
- Emoticons
- </h5>
- <ul class='clearfix emoji-menu-list'>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F47D" title="alien" data-aliases="" data-emoji="alien" data-unicode-name="1F47D"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F47C" title="angel" data-aliases="" data-emoji="angel" data-unicode-name="1F47C"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F4A2" title="anger" data-aliases="" data-emoji="anger" data-unicode-name="1F4A2"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F620" title="angry" data-aliases="" data-emoji="angry" data-unicode-name="1F620"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F627" title="anguished" data-aliases="" data-emoji="anguished" data-unicode-name="1F627"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F632" title="astonished" data-aliases="" data-emoji="astonished" data-unicode-name="1F632"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F45F" title="athletic_shoe" data-aliases="" data-emoji="athletic_shoe" data-unicode-name="1F45F"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F476" title="baby" data-aliases="" data-emoji="baby" data-unicode-name="1F476"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F459" title="bikini" data-aliases="" data-emoji="bikini" data-unicode-name="1F459"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F499" title="blue_heart" data-aliases="" data-emoji="blue_heart" data-unicode-name="1F499"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F60A" title="blush" data-aliases="" data-emoji="blush" data-unicode-name="1F60A"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F4A5" title="boom" data-aliases="" data-emoji="boom" data-unicode-name="1F4A5"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F462" title="boot" data-aliases="" data-emoji="boot" data-unicode-name="1F462"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F647" title="bow" data-aliases="" data-emoji="bow" data-unicode-name="1F647"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F466" title="boy" data-aliases="" data-emoji="boy" data-unicode-name="1F466"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F470" title="bride_with_veil" data-aliases="" data-emoji="bride_with_veil" data-unicode-name="1F470"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F4BC" title="briefcase" data-aliases="" data-emoji="briefcase" data-unicode-name="1F4BC"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F494" title="broken_heart" data-aliases="" data-emoji="broken_heart" data-unicode-name="1F494"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F464" title="bust_in_silhouette" data-aliases="" data-emoji="bust_in_silhouette" data-unicode-name="1F464"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F465" title="busts_in_silhouette" data-aliases="" data-emoji="busts_in_silhouette" data-unicode-name="1F465"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F44F" title="clap" data-aliases="" data-emoji="clap" data-unicode-name="1F44F"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F302" title="closed_umbrella" data-aliases="" data-emoji="closed_umbrella" data-unicode-name="1F302"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F630" title="cold_sweat" data-aliases="" data-emoji="cold_sweat" data-unicode-name="1F630"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F616" title="confounded" data-aliases="" data-emoji="confounded" data-unicode-name="1F616"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F615" title="confused" data-aliases="" data-emoji="confused" data-unicode-name="1F615"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F477" title="construction_worker" data-aliases="" data-emoji="construction_worker" data-unicode-name="1F477"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F46E" title="cop" data-aliases="" data-emoji="cop" data-unicode-name="1F46E"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F46B" title="couple" data-aliases="" data-emoji="couple" data-unicode-name="1F46B"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F491" title="couple_with_heart" data-aliases="" data-emoji="couple_with_heart" data-unicode-name="1F491"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F48F" title="couplekiss" data-aliases="" data-emoji="couplekiss" data-unicode-name="1F48F"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F451" title="crown" data-aliases="" data-emoji="crown" data-unicode-name="1F451"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F622" title="cry" data-aliases="" data-emoji="cry" data-unicode-name="1F622"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F63F" title="crying_cat_face" data-aliases="" data-emoji="crying_cat_face" data-unicode-name="1F63F"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F498" title="cupid" data-aliases="" data-emoji="cupid" data-unicode-name="1F498"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F483" title="dancer" data-aliases="" data-emoji="dancer" data-unicode-name="1F483"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F46F" title="dancers" data-aliases="" data-emoji="dancers" data-unicode-name="1F46F"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F4A8" title="dash" data-aliases="" data-emoji="dash" data-unicode-name="1F4A8"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F61E" title="disappointed" data-aliases="" data-emoji="disappointed" data-unicode-name="1F61E"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F625" title="disappointed_relieved" data-aliases="" data-emoji="disappointed_relieved" data-unicode-name="1F625"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F4AB" title="dizzy" data-aliases="" data-emoji="dizzy" data-unicode-name="1F4AB"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F635" title="dizzy_face" data-aliases="" data-emoji="dizzy_face" data-unicode-name="1F635"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F457" title="dress" data-aliases="" data-emoji="dress" data-unicode-name="1F457"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F4A7" title="droplet" data-aliases="" data-emoji="droplet" data-unicode-name="1F4A7"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F442" title="ear" data-aliases="" data-emoji="ear" data-unicode-name="1F442"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F611" title="expressionless" data-aliases="" data-emoji="expressionless" data-unicode-name="1F611"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F453" title="eyeglasses" data-aliases="" data-emoji="eyeglasses" data-unicode-name="1F453"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F440" title="eyes" data-aliases="" data-emoji="eyes" data-unicode-name="1F440"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F46A" title="family" data-aliases="" data-emoji="family" data-unicode-name="1F46A"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F628" title="fearful" data-aliases="" data-emoji="fearful" data-unicode-name="1F628"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F525" title="fire" data-aliases=":flame:" data-emoji="fire" data-unicode-name="1F525"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-270A" title="fist" data-aliases="" data-emoji="fist" data-unicode-name="270A"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F633" title="flushed" data-aliases="" data-emoji="flushed" data-unicode-name="1F633"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F463" title="footprints" data-aliases="" data-emoji="footprints" data-unicode-name="1F463"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F626" title="frowning" data-aliases=":anguished:" data-emoji="frowning" data-unicode-name="1F626"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F48E" title="gem" data-aliases="" data-emoji="gem" data-unicode-name="1F48E"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F467" title="girl" data-aliases="" data-emoji="girl" data-unicode-name="1F467"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F49A" title="green_heart" data-aliases="" data-emoji="green_heart" data-unicode-name="1F49A"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F62C" title="grimacing" data-aliases="" data-emoji="grimacing" data-unicode-name="1F62C"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F601" title="grin" data-aliases="" data-emoji="grin" data-unicode-name="1F601"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F600" title="grinning" data-aliases="" data-emoji="grinning" data-unicode-name="1F600"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F482" title="guardsman" data-aliases="" data-emoji="guardsman" data-unicode-name="1F482"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F487" title="haircut" data-aliases="" data-emoji="haircut" data-unicode-name="1F487"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F45C" title="handbag" data-aliases="" data-emoji="handbag" data-unicode-name="1F45C"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F649" title="hear_no_evil" data-aliases="" data-emoji="hear_no_evil" data-unicode-name="1F649"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-2764" title="heart" data-aliases="" data-emoji="heart" data-unicode-name="2764"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F60D" title="heart_eyes" data-aliases="" data-emoji="heart_eyes" data-unicode-name="1F60D"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F63B" title="heart_eyes_cat" data-aliases="" data-emoji="heart_eyes_cat" data-unicode-name="1F63B"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F493" title="heartbeat" data-aliases="" data-emoji="heartbeat" data-unicode-name="1F493"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F497" title="heartpulse" data-aliases="" data-emoji="heartpulse" data-unicode-name="1F497"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F460" title="high_heel" data-aliases="" data-emoji="high_heel" data-unicode-name="1F460"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F62F" title="hushed" data-aliases="" data-emoji="hushed" data-unicode-name="1F62F"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F47F" title="imp" data-aliases="" data-emoji="imp" data-unicode-name="1F47F"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F481" title="information_desk_person" data-aliases="" data-emoji="information_desk_person" data-unicode-name="1F481"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F607" title="innocent" data-aliases="" data-emoji="innocent" data-unicode-name="1F607"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F47A" title="japanese_goblin" data-aliases="" data-emoji="japanese_goblin" data-unicode-name="1F47A"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F479" title="japanese_ogre" data-aliases="" data-emoji="japanese_ogre" data-unicode-name="1F479"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F456" title="jeans" data-aliases="" data-emoji="jeans" data-unicode-name="1F456"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F602" title="joy" data-aliases="" data-emoji="joy" data-unicode-name="1F602"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F639" title="joy_cat" data-aliases="" data-emoji="joy_cat" data-unicode-name="1F639"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F458" title="kimono" data-aliases="" data-emoji="kimono" data-unicode-name="1F458"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F48B" title="kiss" data-aliases="" data-emoji="kiss" data-unicode-name="1F48B"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F617" title="kissing" data-aliases="" data-emoji="kissing" data-unicode-name="1F617"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F63D" title="kissing_cat" data-aliases="" data-emoji="kissing_cat" data-unicode-name="1F63D"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F61A" title="kissing_closed_eyes" data-aliases="" data-emoji="kissing_closed_eyes" data-unicode-name="1F61A"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F618" title="kissing_heart" data-aliases="" data-emoji="kissing_heart" data-unicode-name="1F618"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F619" title="kissing_smiling_eyes" data-aliases="" data-emoji="kissing_smiling_eyes" data-unicode-name="1F619"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F606" title="laughing" data-aliases=":satisfied:" data-emoji="laughing" data-unicode-name="1F606"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F444" title="lips" data-aliases="" data-emoji="lips" data-unicode-name="1F444"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F484" title="lipstick" data-aliases="" data-emoji="lipstick" data-unicode-name="1F484"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F48C" title="love_letter" data-aliases="" data-emoji="love_letter" data-unicode-name="1F48C"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F468" title="man" data-aliases="" data-emoji="man" data-unicode-name="1F468"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F472" title="man_with_gua_pi_mao" data-aliases="" data-emoji="man_with_gua_pi_mao" data-unicode-name="1F472"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F473" title="man_with_turban" data-aliases="" data-emoji="man_with_turban" data-unicode-name="1F473"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F45E" title="mans_shoe" data-aliases="" data-emoji="mans_shoe" data-unicode-name="1F45E"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F637" title="mask" data-aliases="" data-emoji="mask" data-unicode-name="1F637"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F486" title="massage" data-aliases="" data-emoji="massage" data-unicode-name="1F486"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F4AA" title="muscle" data-aliases="" data-emoji="muscle" data-unicode-name="1F4AA"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F485" title="nail_care" data-aliases="" data-emoji="nail_care" data-unicode-name="1F485"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F454" title="necktie" data-aliases="" data-emoji="necktie" data-unicode-name="1F454"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F610" title="neutral_face" data-aliases="" data-emoji="neutral_face" data-unicode-name="1F610"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F645" title="no_good" data-aliases="" data-emoji="no_good" data-unicode-name="1F645"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F636" title="no_mouth" data-aliases="" data-emoji="no_mouth" data-unicode-name="1F636"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F443" title="nose" data-aliases="" data-emoji="nose" data-unicode-name="1F443"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F44C" title="ok_hand" data-aliases="" data-emoji="ok_hand" data-unicode-name="1F44C"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F646" title="ok_woman" data-aliases="" data-emoji="ok_woman" data-unicode-name="1F646"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F474" title="older_man" data-aliases="" data-emoji="older_man" data-unicode-name="1F474"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F475" title="older_woman" data-aliases=":grandma:" data-emoji="older_woman" data-unicode-name="1F475"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F450" title="open_hands" data-aliases="" data-emoji="open_hands" data-unicode-name="1F450"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F62E" title="open_mouth" data-aliases="" data-emoji="open_mouth" data-unicode-name="1F62E"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F614" title="pensive" data-aliases="" data-emoji="pensive" data-unicode-name="1F614"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F623" title="persevere" data-aliases="" data-emoji="persevere" data-unicode-name="1F623"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F64D" title="person_frowning" data-aliases="" data-emoji="person_frowning" data-unicode-name="1F64D"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F471" title="person_with_blond_hair" data-aliases="" data-emoji="person_with_blond_hair" data-unicode-name="1F471"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F64E" title="person_with_pouting_face" data-aliases="" data-emoji="person_with_pouting_face" data-unicode-name="1F64E"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F447" title="point_down" data-aliases="" data-emoji="point_down" data-unicode-name="1F447"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F448" title="point_left" data-aliases="" data-emoji="point_left" data-unicode-name="1F448"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F449" title="point_right" data-aliases="" data-emoji="point_right" data-unicode-name="1F449"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-261D" title="point_up" data-aliases="" data-emoji="point_up" data-unicode-name="261D"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F446" title="point_up_2" data-aliases="" data-emoji="point_up_2" data-unicode-name="1F446"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F4A9" title="poop" data-aliases=":shit: :hankey: :poo:" data-emoji="poop" data-unicode-name="1F4A9"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F45D" title="pouch" data-aliases="" data-emoji="pouch" data-unicode-name="1F45D"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F63E" title="pouting_cat" data-aliases="" data-emoji="pouting_cat" data-unicode-name="1F63E"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F64F" title="pray" data-aliases="" data-emoji="pray" data-unicode-name="1F64F"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F478" title="princess" data-aliases="" data-emoji="princess" data-unicode-name="1F478"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F44A" title="punch" data-aliases="" data-emoji="punch" data-unicode-name="1F44A"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F49C" title="purple_heart" data-aliases="" data-emoji="purple_heart" data-unicode-name="1F49C"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F45B" title="purse" data-aliases="" data-emoji="purse" data-unicode-name="1F45B"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F621" title="rage" data-aliases="" data-emoji="rage" data-unicode-name="1F621"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-270B" title="raised_hand" data-aliases="" data-emoji="raised_hand" data-unicode-name="270B"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F64C" title="raised_hands" data-aliases="" data-emoji="raised_hands" data-unicode-name="1F64C"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F64B" title="raising_hand" data-aliases="" data-emoji="raising_hand" data-unicode-name="1F64B"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-263A" title="relaxed" data-aliases="" data-emoji="relaxed" data-unicode-name="263A"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F60C" title="relieved" data-aliases="" data-emoji="relieved" data-unicode-name="1F60C"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F49E" title="revolving_hearts" data-aliases="" data-emoji="revolving_hearts" data-unicode-name="1F49E"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F380" title="ribbon" data-aliases="" data-emoji="ribbon" data-unicode-name="1F380"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F48D" title="ring" data-aliases="" data-emoji="ring" data-unicode-name="1F48D"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F3C3" title="runner" data-aliases="" data-emoji="runner" data-unicode-name="1F3C3"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F3BD" title="running_shirt_with_sash" data-aliases="" data-emoji="running_shirt_with_sash" data-unicode-name="1F3BD"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F461" title="sandal" data-aliases="" data-emoji="sandal" data-unicode-name="1F461"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F631" title="scream" data-aliases="" data-emoji="scream" data-unicode-name="1F631"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F640" title="scream_cat" data-aliases="" data-emoji="scream_cat" data-unicode-name="1F640"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F648" title="see_no_evil" data-aliases="" data-emoji="see_no_evil" data-unicode-name="1F648"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F455" title="shirt" data-aliases="" data-emoji="shirt" data-unicode-name="1F455"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F480" title="skull" data-aliases=":skeleton:" data-emoji="skull" data-unicode-name="1F480"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F634" title="sleeping" data-aliases="" data-emoji="sleeping" data-unicode-name="1F634"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F62A" title="sleepy" data-aliases="" data-emoji="sleepy" data-unicode-name="1F62A"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F604" title="smile" data-aliases="" data-emoji="smile" data-unicode-name="1F604"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F638" title="smile_cat" data-aliases="" data-emoji="smile_cat" data-unicode-name="1F638"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F603" title="smiley" data-aliases="" data-emoji="smiley" data-unicode-name="1F603"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F63A" title="smiley_cat" data-aliases="" data-emoji="smiley_cat" data-unicode-name="1F63A"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F608" title="smiling_imp" data-aliases="" data-emoji="smiling_imp" data-unicode-name="1F608"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F60F" title="smirk" data-aliases="" data-emoji="smirk" data-unicode-name="1F60F"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F63C" title="smirk_cat" data-aliases="" data-emoji="smirk_cat" data-unicode-name="1F63C"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F62D" title="sob" data-aliases="" data-emoji="sob" data-unicode-name="1F62D"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-2728" title="sparkles" data-aliases="" data-emoji="sparkles" data-unicode-name="2728"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F496" title="sparkling_heart" data-aliases="" data-emoji="sparkling_heart" data-unicode-name="1F496"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F64A" title="speak_no_evil" data-aliases="" data-emoji="speak_no_evil" data-unicode-name="1F64A"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F4AC" title="speech_balloon" data-aliases="" data-emoji="speech_balloon" data-unicode-name="1F4AC"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F31F" title="star2" data-aliases="" data-emoji="star2" data-unicode-name="1F31F"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F61B" title="stuck_out_tongue" data-aliases="" data-emoji="stuck_out_tongue" data-unicode-name="1F61B"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F61D" title="stuck_out_tongue_closed_eyes" data-aliases="" data-emoji="stuck_out_tongue_closed_eyes" data-unicode-name="1F61D"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F61C" title="stuck_out_tongue_winking_eye" data-aliases="" data-emoji="stuck_out_tongue_winking_eye" data-unicode-name="1F61C"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F60E" title="sunglasses" data-aliases="" data-emoji="sunglasses" data-unicode-name="1F60E"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F613" title="sweat" data-aliases="" data-emoji="sweat" data-unicode-name="1F613"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F4A6" title="sweat_drops" data-aliases="" data-emoji="sweat_drops" data-unicode-name="1F4A6"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F605" title="sweat_smile" data-aliases="" data-emoji="sweat_smile" data-unicode-name="1F605"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F4AD" title="thought_balloon" data-aliases="" data-emoji="thought_balloon" data-unicode-name="1F4AD"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F44E" title="thumbsdown" data-aliases=":-1:" data-emoji="thumbsdown" data-unicode-name="1F44E"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F44D" title="thumbsup" data-aliases=":+1:" data-emoji="thumbsup" data-unicode-name="1F44D"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F62B" title="tired_face" data-aliases="" data-emoji="tired_face" data-unicode-name="1F62B"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F445" title="tongue" data-aliases="" data-emoji="tongue" data-unicode-name="1F445"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F3A9" title="tophat" data-aliases="" data-emoji="tophat" data-unicode-name="1F3A9"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F624" title="triumph" data-aliases="" data-emoji="triumph" data-unicode-name="1F624"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F495" title="two_hearts" data-aliases="" data-emoji="two_hearts" data-unicode-name="1F495"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F46C" title="two_men_holding_hands" data-aliases="" data-emoji="two_men_holding_hands" data-unicode-name="1F46C"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F46D" title="two_women_holding_hands" data-aliases="" data-emoji="two_women_holding_hands" data-unicode-name="1F46D"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F612" title="unamused" data-aliases="" data-emoji="unamused" data-unicode-name="1F612"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-270C" title="v" data-aliases="" data-emoji="v" data-unicode-name="270C"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F6B6" title="walking" data-aliases="" data-emoji="walking" data-unicode-name="1F6B6"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F44B" title="wave" data-aliases="" data-emoji="wave" data-unicode-name="1F44B"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F629" title="weary" data-aliases="" data-emoji="weary" data-unicode-name="1F629"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F609" title="wink" data-aliases="" data-emoji="wink" data-unicode-name="1F609"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F469" title="woman" data-aliases="" data-emoji="woman" data-unicode-name="1F469"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F45A" title="womans_clothes" data-aliases="" data-emoji="womans_clothes" data-unicode-name="1F45A"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F452" title="womans_hat" data-aliases="" data-emoji="womans_hat" data-unicode-name="1F452"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F61F" title="worried" data-aliases="" data-emoji="worried" data-unicode-name="1F61F"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F49B" title="yellow_heart" data-aliases="" data-emoji="yellow_heart" data-unicode-name="1F49B"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F60B" title="yum" data-aliases="" data-emoji="yum" data-unicode-name="1F60B"></div>
- </button>
- </li>
- <li class='pull-left text-center emoji-menu-list-item'>
- <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
- <div class="icon emoji-icon emoji-1F4A4" title="zzz" data-aliases="" data-emoji="zzz" data-unicode-name="1F4A4"></div>
- </button>
- </li>
- </ul>
- </div>
- </div>
-"""
diff --git a/spec/javascripts/fixtures/emoji_menu.js b/spec/javascripts/fixtures/emoji_menu.js
new file mode 100644
index 00000000000..22cb3a99265
--- /dev/null
+++ b/spec/javascripts/fixtures/emoji_menu.js
@@ -0,0 +1 @@
+window.emojiMenu = "<div class='emoji-menu'>\n <input type=\"text\" name=\"emoji_search\" id=\"emoji_search\" value=\"\" class=\"emoji-search search-input form-control\" />\n <div class='emoji-menu-content'>\n <h5 class='emoji-menu-title'>\n Emoticons\n </h5>\n <ul class='clearfix emoji-menu-list'>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47D\" title=\"alien\" data-aliases=\"\" data-emoji=\"alien\" data-unicode-name=\"1F47D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47C\" title=\"angel\" data-aliases=\"\" data-emoji=\"angel\" data-unicode-name=\"1F47C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A2\" title=\"anger\" data-aliases=\"\" data-emoji=\"anger\" data-unicode-name=\"1F4A2\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F620\" title=\"angry\" data-aliases=\"\" data-emoji=\"angry\" data-unicode-name=\"1F620\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F627\" title=\"anguished\" data-aliases=\"\" data-emoji=\"anguished\" data-unicode-name=\"1F627\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F632\" title=\"astonished\" data-aliases=\"\" data-emoji=\"astonished\" data-unicode-name=\"1F632\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45F\" title=\"athletic_shoe\" data-aliases=\"\" data-emoji=\"athletic_shoe\" data-unicode-name=\"1F45F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F476\" title=\"baby\" data-aliases=\"\" data-emoji=\"baby\" data-unicode-name=\"1F476\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F459\" title=\"bikini\" data-aliases=\"\" data-emoji=\"bikini\" data-unicode-name=\"1F459\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F499\" title=\"blue_heart\" data-aliases=\"\" data-emoji=\"blue_heart\" data-unicode-name=\"1F499\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60A\" title=\"blush\" data-aliases=\"\" data-emoji=\"blush\" data-unicode-name=\"1F60A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A5\" title=\"boom\" data-aliases=\"\" data-emoji=\"boom\" data-unicode-name=\"1F4A5\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F462\" title=\"boot\" data-aliases=\"\" data-emoji=\"boot\" data-unicode-name=\"1F462\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F647\" title=\"bow\" data-aliases=\"\" data-emoji=\"bow\" data-unicode-name=\"1F647\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F466\" title=\"boy\" data-aliases=\"\" data-emoji=\"boy\" data-unicode-name=\"1F466\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F470\" title=\"bride_with_veil\" data-aliases=\"\" data-emoji=\"bride_with_veil\" data-unicode-name=\"1F470\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4BC\" title=\"briefcase\" data-aliases=\"\" data-emoji=\"briefcase\" data-unicode-name=\"1F4BC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F494\" title=\"broken_heart\" data-aliases=\"\" data-emoji=\"broken_heart\" data-unicode-name=\"1F494\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F464\" title=\"bust_in_silhouette\" data-aliases=\"\" data-emoji=\"bust_in_silhouette\" data-unicode-name=\"1F464\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F465\" title=\"busts_in_silhouette\" data-aliases=\"\" data-emoji=\"busts_in_silhouette\" data-unicode-name=\"1F465\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44F\" title=\"clap\" data-aliases=\"\" data-emoji=\"clap\" data-unicode-name=\"1F44F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F302\" title=\"closed_umbrella\" data-aliases=\"\" data-emoji=\"closed_umbrella\" data-unicode-name=\"1F302\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F630\" title=\"cold_sweat\" data-aliases=\"\" data-emoji=\"cold_sweat\" data-unicode-name=\"1F630\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F616\" title=\"confounded\" data-aliases=\"\" data-emoji=\"confounded\" data-unicode-name=\"1F616\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F615\" title=\"confused\" data-aliases=\"\" data-emoji=\"confused\" data-unicode-name=\"1F615\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F477\" title=\"construction_worker\" data-aliases=\"\" data-emoji=\"construction_worker\" data-unicode-name=\"1F477\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46E\" title=\"cop\" data-aliases=\"\" data-emoji=\"cop\" data-unicode-name=\"1F46E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46B\" title=\"couple\" data-aliases=\"\" data-emoji=\"couple\" data-unicode-name=\"1F46B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F491\" title=\"couple_with_heart\" data-aliases=\"\" data-emoji=\"couple_with_heart\" data-unicode-name=\"1F491\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48F\" title=\"couplekiss\" data-aliases=\"\" data-emoji=\"couplekiss\" data-unicode-name=\"1F48F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F451\" title=\"crown\" data-aliases=\"\" data-emoji=\"crown\" data-unicode-name=\"1F451\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F622\" title=\"cry\" data-aliases=\"\" data-emoji=\"cry\" data-unicode-name=\"1F622\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63F\" title=\"crying_cat_face\" data-aliases=\"\" data-emoji=\"crying_cat_face\" data-unicode-name=\"1F63F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F498\" title=\"cupid\" data-aliases=\"\" data-emoji=\"cupid\" data-unicode-name=\"1F498\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F483\" title=\"dancer\" data-aliases=\"\" data-emoji=\"dancer\" data-unicode-name=\"1F483\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46F\" title=\"dancers\" data-aliases=\"\" data-emoji=\"dancers\" data-unicode-name=\"1F46F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A8\" title=\"dash\" data-aliases=\"\" data-emoji=\"dash\" data-unicode-name=\"1F4A8\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61E\" title=\"disappointed\" data-aliases=\"\" data-emoji=\"disappointed\" data-unicode-name=\"1F61E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F625\" title=\"disappointed_relieved\" data-aliases=\"\" data-emoji=\"disappointed_relieved\" data-unicode-name=\"1F625\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AB\" title=\"dizzy\" data-aliases=\"\" data-emoji=\"dizzy\" data-unicode-name=\"1F4AB\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F635\" title=\"dizzy_face\" data-aliases=\"\" data-emoji=\"dizzy_face\" data-unicode-name=\"1F635\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F457\" title=\"dress\" data-aliases=\"\" data-emoji=\"dress\" data-unicode-name=\"1F457\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A7\" title=\"droplet\" data-aliases=\"\" data-emoji=\"droplet\" data-unicode-name=\"1F4A7\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F442\" title=\"ear\" data-aliases=\"\" data-emoji=\"ear\" data-unicode-name=\"1F442\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F611\" title=\"expressionless\" data-aliases=\"\" data-emoji=\"expressionless\" data-unicode-name=\"1F611\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F453\" title=\"eyeglasses\" data-aliases=\"\" data-emoji=\"eyeglasses\" data-unicode-name=\"1F453\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F440\" title=\"eyes\" data-aliases=\"\" data-emoji=\"eyes\" data-unicode-name=\"1F440\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46A\" title=\"family\" data-aliases=\"\" data-emoji=\"family\" data-unicode-name=\"1F46A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F628\" title=\"fearful\" data-aliases=\"\" data-emoji=\"fearful\" data-unicode-name=\"1F628\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F525\" title=\"fire\" data-aliases=\":flame:\" data-emoji=\"fire\" data-unicode-name=\"1F525\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270A\" title=\"fist\" data-aliases=\"\" data-emoji=\"fist\" data-unicode-name=\"270A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F633\" title=\"flushed\" data-aliases=\"\" data-emoji=\"flushed\" data-unicode-name=\"1F633\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F463\" title=\"footprints\" data-aliases=\"\" data-emoji=\"footprints\" data-unicode-name=\"1F463\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F626\" title=\"frowning\" data-aliases=\":anguished:\" data-emoji=\"frowning\" data-unicode-name=\"1F626\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48E\" title=\"gem\" data-aliases=\"\" data-emoji=\"gem\" data-unicode-name=\"1F48E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F467\" title=\"girl\" data-aliases=\"\" data-emoji=\"girl\" data-unicode-name=\"1F467\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49A\" title=\"green_heart\" data-aliases=\"\" data-emoji=\"green_heart\" data-unicode-name=\"1F49A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62C\" title=\"grimacing\" data-aliases=\"\" data-emoji=\"grimacing\" data-unicode-name=\"1F62C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F601\" title=\"grin\" data-aliases=\"\" data-emoji=\"grin\" data-unicode-name=\"1F601\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F600\" title=\"grinning\" data-aliases=\"\" data-emoji=\"grinning\" data-unicode-name=\"1F600\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F482\" title=\"guardsman\" data-aliases=\"\" data-emoji=\"guardsman\" data-unicode-name=\"1F482\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F487\" title=\"haircut\" data-aliases=\"\" data-emoji=\"haircut\" data-unicode-name=\"1F487\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45C\" title=\"handbag\" data-aliases=\"\" data-emoji=\"handbag\" data-unicode-name=\"1F45C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F649\" title=\"hear_no_evil\" data-aliases=\"\" data-emoji=\"hear_no_evil\" data-unicode-name=\"1F649\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2764\" title=\"heart\" data-aliases=\"\" data-emoji=\"heart\" data-unicode-name=\"2764\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60D\" title=\"heart_eyes\" data-aliases=\"\" data-emoji=\"heart_eyes\" data-unicode-name=\"1F60D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63B\" title=\"heart_eyes_cat\" data-aliases=\"\" data-emoji=\"heart_eyes_cat\" data-unicode-name=\"1F63B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F493\" title=\"heartbeat\" data-aliases=\"\" data-emoji=\"heartbeat\" data-unicode-name=\"1F493\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F497\" title=\"heartpulse\" data-aliases=\"\" data-emoji=\"heartpulse\" data-unicode-name=\"1F497\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F460\" title=\"high_heel\" data-aliases=\"\" data-emoji=\"high_heel\" data-unicode-name=\"1F460\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62F\" title=\"hushed\" data-aliases=\"\" data-emoji=\"hushed\" data-unicode-name=\"1F62F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47F\" title=\"imp\" data-aliases=\"\" data-emoji=\"imp\" data-unicode-name=\"1F47F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F481\" title=\"information_desk_person\" data-aliases=\"\" data-emoji=\"information_desk_person\" data-unicode-name=\"1F481\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F607\" title=\"innocent\" data-aliases=\"\" data-emoji=\"innocent\" data-unicode-name=\"1F607\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47A\" title=\"japanese_goblin\" data-aliases=\"\" data-emoji=\"japanese_goblin\" data-unicode-name=\"1F47A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F479\" title=\"japanese_ogre\" data-aliases=\"\" data-emoji=\"japanese_ogre\" data-unicode-name=\"1F479\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F456\" title=\"jeans\" data-aliases=\"\" data-emoji=\"jeans\" data-unicode-name=\"1F456\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F602\" title=\"joy\" data-aliases=\"\" data-emoji=\"joy\" data-unicode-name=\"1F602\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F639\" title=\"joy_cat\" data-aliases=\"\" data-emoji=\"joy_cat\" data-unicode-name=\"1F639\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F458\" title=\"kimono\" data-aliases=\"\" data-emoji=\"kimono\" data-unicode-name=\"1F458\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48B\" title=\"kiss\" data-aliases=\"\" data-emoji=\"kiss\" data-unicode-name=\"1F48B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F617\" title=\"kissing\" data-aliases=\"\" data-emoji=\"kissing\" data-unicode-name=\"1F617\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63D\" title=\"kissing_cat\" data-aliases=\"\" data-emoji=\"kissing_cat\" data-unicode-name=\"1F63D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61A\" title=\"kissing_closed_eyes\" data-aliases=\"\" data-emoji=\"kissing_closed_eyes\" data-unicode-name=\"1F61A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F618\" title=\"kissing_heart\" data-aliases=\"\" data-emoji=\"kissing_heart\" data-unicode-name=\"1F618\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F619\" title=\"kissing_smiling_eyes\" data-aliases=\"\" data-emoji=\"kissing_smiling_eyes\" data-unicode-name=\"1F619\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F606\" title=\"laughing\" data-aliases=\":satisfied:\" data-emoji=\"laughing\" data-unicode-name=\"1F606\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F444\" title=\"lips\" data-aliases=\"\" data-emoji=\"lips\" data-unicode-name=\"1F444\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F484\" title=\"lipstick\" data-aliases=\"\" data-emoji=\"lipstick\" data-unicode-name=\"1F484\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48C\" title=\"love_letter\" data-aliases=\"\" data-emoji=\"love_letter\" data-unicode-name=\"1F48C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F468\" title=\"man\" data-aliases=\"\" data-emoji=\"man\" data-unicode-name=\"1F468\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F472\" title=\"man_with_gua_pi_mao\" data-aliases=\"\" data-emoji=\"man_with_gua_pi_mao\" data-unicode-name=\"1F472\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F473\" title=\"man_with_turban\" data-aliases=\"\" data-emoji=\"man_with_turban\" data-unicode-name=\"1F473\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45E\" title=\"mans_shoe\" data-aliases=\"\" data-emoji=\"mans_shoe\" data-unicode-name=\"1F45E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F637\" title=\"mask\" data-aliases=\"\" data-emoji=\"mask\" data-unicode-name=\"1F637\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F486\" title=\"massage\" data-aliases=\"\" data-emoji=\"massage\" data-unicode-name=\"1F486\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AA\" title=\"muscle\" data-aliases=\"\" data-emoji=\"muscle\" data-unicode-name=\"1F4AA\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F485\" title=\"nail_care\" data-aliases=\"\" data-emoji=\"nail_care\" data-unicode-name=\"1F485\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F454\" title=\"necktie\" data-aliases=\"\" data-emoji=\"necktie\" data-unicode-name=\"1F454\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F610\" title=\"neutral_face\" data-aliases=\"\" data-emoji=\"neutral_face\" data-unicode-name=\"1F610\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F645\" title=\"no_good\" data-aliases=\"\" data-emoji=\"no_good\" data-unicode-name=\"1F645\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F636\" title=\"no_mouth\" data-aliases=\"\" data-emoji=\"no_mouth\" data-unicode-name=\"1F636\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F443\" title=\"nose\" data-aliases=\"\" data-emoji=\"nose\" data-unicode-name=\"1F443\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44C\" title=\"ok_hand\" data-aliases=\"\" data-emoji=\"ok_hand\" data-unicode-name=\"1F44C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F646\" title=\"ok_woman\" data-aliases=\"\" data-emoji=\"ok_woman\" data-unicode-name=\"1F646\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F474\" title=\"older_man\" data-aliases=\"\" data-emoji=\"older_man\" data-unicode-name=\"1F474\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F475\" title=\"older_woman\" data-aliases=\":grandma:\" data-emoji=\"older_woman\" data-unicode-name=\"1F475\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F450\" title=\"open_hands\" data-aliases=\"\" data-emoji=\"open_hands\" data-unicode-name=\"1F450\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62E\" title=\"open_mouth\" data-aliases=\"\" data-emoji=\"open_mouth\" data-unicode-name=\"1F62E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F614\" title=\"pensive\" data-aliases=\"\" data-emoji=\"pensive\" data-unicode-name=\"1F614\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F623\" title=\"persevere\" data-aliases=\"\" data-emoji=\"persevere\" data-unicode-name=\"1F623\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64D\" title=\"person_frowning\" data-aliases=\"\" data-emoji=\"person_frowning\" data-unicode-name=\"1F64D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F471\" title=\"person_with_blond_hair\" data-aliases=\"\" data-emoji=\"person_with_blond_hair\" data-unicode-name=\"1F471\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64E\" title=\"person_with_pouting_face\" data-aliases=\"\" data-emoji=\"person_with_pouting_face\" data-unicode-name=\"1F64E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F447\" title=\"point_down\" data-aliases=\"\" data-emoji=\"point_down\" data-unicode-name=\"1F447\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F448\" title=\"point_left\" data-aliases=\"\" data-emoji=\"point_left\" data-unicode-name=\"1F448\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F449\" title=\"point_right\" data-aliases=\"\" data-emoji=\"point_right\" data-unicode-name=\"1F449\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-261D\" title=\"point_up\" data-aliases=\"\" data-emoji=\"point_up\" data-unicode-name=\"261D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F446\" title=\"point_up_2\" data-aliases=\"\" data-emoji=\"point_up_2\" data-unicode-name=\"1F446\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A9\" title=\"poop\" data-aliases=\":shit: :hankey: :poo:\" data-emoji=\"poop\" data-unicode-name=\"1F4A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45D\" title=\"pouch\" data-aliases=\"\" data-emoji=\"pouch\" data-unicode-name=\"1F45D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63E\" title=\"pouting_cat\" data-aliases=\"\" data-emoji=\"pouting_cat\" data-unicode-name=\"1F63E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64F\" title=\"pray\" data-aliases=\"\" data-emoji=\"pray\" data-unicode-name=\"1F64F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F478\" title=\"princess\" data-aliases=\"\" data-emoji=\"princess\" data-unicode-name=\"1F478\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44A\" title=\"punch\" data-aliases=\"\" data-emoji=\"punch\" data-unicode-name=\"1F44A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49C\" title=\"purple_heart\" data-aliases=\"\" data-emoji=\"purple_heart\" data-unicode-name=\"1F49C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45B\" title=\"purse\" data-aliases=\"\" data-emoji=\"purse\" data-unicode-name=\"1F45B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F621\" title=\"rage\" data-aliases=\"\" data-emoji=\"rage\" data-unicode-name=\"1F621\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270B\" title=\"raised_hand\" data-aliases=\"\" data-emoji=\"raised_hand\" data-unicode-name=\"270B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64C\" title=\"raised_hands\" data-aliases=\"\" data-emoji=\"raised_hands\" data-unicode-name=\"1F64C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64B\" title=\"raising_hand\" data-aliases=\"\" data-emoji=\"raising_hand\" data-unicode-name=\"1F64B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-263A\" title=\"relaxed\" data-aliases=\"\" data-emoji=\"relaxed\" data-unicode-name=\"263A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60C\" title=\"relieved\" data-aliases=\"\" data-emoji=\"relieved\" data-unicode-name=\"1F60C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49E\" title=\"revolving_hearts\" data-aliases=\"\" data-emoji=\"revolving_hearts\" data-unicode-name=\"1F49E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F380\" title=\"ribbon\" data-aliases=\"\" data-emoji=\"ribbon\" data-unicode-name=\"1F380\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48D\" title=\"ring\" data-aliases=\"\" data-emoji=\"ring\" data-unicode-name=\"1F48D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3C3\" title=\"runner\" data-aliases=\"\" data-emoji=\"runner\" data-unicode-name=\"1F3C3\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3BD\" title=\"running_shirt_with_sash\" data-aliases=\"\" data-emoji=\"running_shirt_with_sash\" data-unicode-name=\"1F3BD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F461\" title=\"sandal\" data-aliases=\"\" data-emoji=\"sandal\" data-unicode-name=\"1F461\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F631\" title=\"scream\" data-aliases=\"\" data-emoji=\"scream\" data-unicode-name=\"1F631\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F640\" title=\"scream_cat\" data-aliases=\"\" data-emoji=\"scream_cat\" data-unicode-name=\"1F640\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F648\" title=\"see_no_evil\" data-aliases=\"\" data-emoji=\"see_no_evil\" data-unicode-name=\"1F648\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F455\" title=\"shirt\" data-aliases=\"\" data-emoji=\"shirt\" data-unicode-name=\"1F455\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F480\" title=\"skull\" data-aliases=\":skeleton:\" data-emoji=\"skull\" data-unicode-name=\"1F480\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F634\" title=\"sleeping\" data-aliases=\"\" data-emoji=\"sleeping\" data-unicode-name=\"1F634\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62A\" title=\"sleepy\" data-aliases=\"\" data-emoji=\"sleepy\" data-unicode-name=\"1F62A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F604\" title=\"smile\" data-aliases=\"\" data-emoji=\"smile\" data-unicode-name=\"1F604\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F638\" title=\"smile_cat\" data-aliases=\"\" data-emoji=\"smile_cat\" data-unicode-name=\"1F638\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F603\" title=\"smiley\" data-aliases=\"\" data-emoji=\"smiley\" data-unicode-name=\"1F603\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63A\" title=\"smiley_cat\" data-aliases=\"\" data-emoji=\"smiley_cat\" data-unicode-name=\"1F63A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F608\" title=\"smiling_imp\" data-aliases=\"\" data-emoji=\"smiling_imp\" data-unicode-name=\"1F608\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60F\" title=\"smirk\" data-aliases=\"\" data-emoji=\"smirk\" data-unicode-name=\"1F60F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63C\" title=\"smirk_cat\" data-aliases=\"\" data-emoji=\"smirk_cat\" data-unicode-name=\"1F63C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62D\" title=\"sob\" data-aliases=\"\" data-emoji=\"sob\" data-unicode-name=\"1F62D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2728\" title=\"sparkles\" data-aliases=\"\" data-emoji=\"sparkles\" data-unicode-name=\"2728\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F496\" title=\"sparkling_heart\" data-aliases=\"\" data-emoji=\"sparkling_heart\" data-unicode-name=\"1F496\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64A\" title=\"speak_no_evil\" data-aliases=\"\" data-emoji=\"speak_no_evil\" data-unicode-name=\"1F64A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AC\" title=\"speech_balloon\" data-aliases=\"\" data-emoji=\"speech_balloon\" data-unicode-name=\"1F4AC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F31F\" title=\"star2\" data-aliases=\"\" data-emoji=\"star2\" data-unicode-name=\"1F31F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61B\" title=\"stuck_out_tongue\" data-aliases=\"\" data-emoji=\"stuck_out_tongue\" data-unicode-name=\"1F61B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61D\" title=\"stuck_out_tongue_closed_eyes\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_closed_eyes\" data-unicode-name=\"1F61D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61C\" title=\"stuck_out_tongue_winking_eye\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_winking_eye\" data-unicode-name=\"1F61C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60E\" title=\"sunglasses\" data-aliases=\"\" data-emoji=\"sunglasses\" data-unicode-name=\"1F60E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F613\" title=\"sweat\" data-aliases=\"\" data-emoji=\"sweat\" data-unicode-name=\"1F613\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A6\" title=\"sweat_drops\" data-aliases=\"\" data-emoji=\"sweat_drops\" data-unicode-name=\"1F4A6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F605\" title=\"sweat_smile\" data-aliases=\"\" data-emoji=\"sweat_smile\" data-unicode-name=\"1F605\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AD\" title=\"thought_balloon\" data-aliases=\"\" data-emoji=\"thought_balloon\" data-unicode-name=\"1F4AD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44E\" title=\"thumbsdown\" data-aliases=\":-1:\" data-emoji=\"thumbsdown\" data-unicode-name=\"1F44E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44D\" title=\"thumbsup\" data-aliases=\":+1:\" data-emoji=\"thumbsup\" data-unicode-name=\"1F44D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62B\" title=\"tired_face\" data-aliases=\"\" data-emoji=\"tired_face\" data-unicode-name=\"1F62B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F445\" title=\"tongue\" data-aliases=\"\" data-emoji=\"tongue\" data-unicode-name=\"1F445\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3A9\" title=\"tophat\" data-aliases=\"\" data-emoji=\"tophat\" data-unicode-name=\"1F3A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F624\" title=\"triumph\" data-aliases=\"\" data-emoji=\"triumph\" data-unicode-name=\"1F624\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F495\" title=\"two_hearts\" data-aliases=\"\" data-emoji=\"two_hearts\" data-unicode-name=\"1F495\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46C\" title=\"two_men_holding_hands\" data-aliases=\"\" data-emoji=\"two_men_holding_hands\" data-unicode-name=\"1F46C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46D\" title=\"two_women_holding_hands\" data-aliases=\"\" data-emoji=\"two_women_holding_hands\" data-unicode-name=\"1F46D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F612\" title=\"unamused\" data-aliases=\"\" data-emoji=\"unamused\" data-unicode-name=\"1F612\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270C\" title=\"v\" data-aliases=\"\" data-emoji=\"v\" data-unicode-name=\"270C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F6B6\" title=\"walking\" data-aliases=\"\" data-emoji=\"walking\" data-unicode-name=\"1F6B6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44B\" title=\"wave\" data-aliases=\"\" data-emoji=\"wave\" data-unicode-name=\"1F44B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F629\" title=\"weary\" data-aliases=\"\" data-emoji=\"weary\" data-unicode-name=\"1F629\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F609\" title=\"wink\" data-aliases=\"\" data-emoji=\"wink\" data-unicode-name=\"1F609\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F469\" title=\"woman\" data-aliases=\"\" data-emoji=\"woman\" data-unicode-name=\"1F469\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45A\" title=\"womans_clothes\" data-aliases=\"\" data-emoji=\"womans_clothes\" data-unicode-name=\"1F45A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F452\" title=\"womans_hat\" data-aliases=\"\" data-emoji=\"womans_hat\" data-unicode-name=\"1F452\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61F\" title=\"worried\" data-aliases=\"\" data-emoji=\"worried\" data-unicode-name=\"1F61F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49B\" title=\"yellow_heart\" data-aliases=\"\" data-emoji=\"yellow_heart\" data-unicode-name=\"1F49B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60B\" title=\"yum\" data-aliases=\"\" data-emoji=\"yum\" data-unicode-name=\"1F60B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A4\" title=\"zzz\" data-aliases=\"\" data-emoji=\"zzz\" data-unicode-name=\"1F4A4\"></div>\n </button>\n </li>\n </ul>\n </div>\n</div>";
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
new file mode 100644
index 00000000000..e140fd6f645
--- /dev/null
+++ b/spec/javascripts/issue_spec.js
@@ -0,0 +1,116 @@
+
+/*= require lib/utils/text_utility */
+
+/*= require issue */
+describe('Issue', function() {
+ return describe('task lists', function() {
+ fixture.preload('issues_show.html');
+ beforeEach(function() {
+ fixture.load('issues_show.html');
+ return this.issue = new Issue();
+ });
+ it('modifies the Markdown field', function() {
+ spyOn(jQuery, 'ajax').and.stub();
+ $('input[type=checkbox]').attr('checked', true).trigger('change');
+ return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
+ });
+ return it('submits an ajax request on tasklist:changed', function() {
+ spyOn(jQuery, 'ajax').and.callFake(function(req) {
+ expect(req.type).toBe('PATCH');
+ expect(req.url).toBe('/foo');
+ return expect(req.data.issue.description).not.toBe(null);
+ });
+ return $('.js-task-list-field').trigger('tasklist:changed');
+ });
+ });
+});
+
+describe('reopen/close issue', function() {
+ fixture.preload('issues_show.html');
+ beforeEach(function() {
+ fixture.load('issues_show.html');
+ return this.issue = new Issue();
+ });
+ it('closes an issue', function() {
+ var $btnClose, $btnReopen;
+ spyOn(jQuery, 'ajax').and.callFake(function(req) {
+ expect(req.type).toBe('PUT');
+ expect(req.url).toBe('http://gitlab.com/issues/6/close');
+ return req.success({
+ id: 34
+ });
+ });
+ $btnClose = $('a.btn-close');
+ $btnReopen = $('a.btn-reopen');
+ expect($btnReopen).toBeHidden();
+ expect($btnClose.text()).toBe('Close');
+ expect(typeof $btnClose.prop('disabled')).toBe('undefined');
+ $btnClose.trigger('click');
+ expect($btnReopen).toBeVisible();
+ expect($btnClose).toBeHidden();
+ expect($('div.status-box-closed')).toBeVisible();
+ return expect($('div.status-box-open')).toBeHidden();
+ });
+ it('fails to close an issue with success:false', function() {
+ var $btnClose, $btnReopen;
+ spyOn(jQuery, 'ajax').and.callFake(function(req) {
+ expect(req.type).toBe('PUT');
+ expect(req.url).toBe('http://goesnowhere.nothing/whereami');
+ return req.success({
+ saved: false
+ });
+ });
+ $btnClose = $('a.btn-close');
+ $btnReopen = $('a.btn-reopen');
+ $btnClose.attr('href', 'http://goesnowhere.nothing/whereami');
+ expect($btnReopen).toBeHidden();
+ expect($btnClose.text()).toBe('Close');
+ expect(typeof $btnClose.prop('disabled')).toBe('undefined');
+ $btnClose.trigger('click');
+ expect($btnReopen).toBeHidden();
+ expect($btnClose).toBeVisible();
+ expect($('div.status-box-closed')).toBeHidden();
+ expect($('div.status-box-open')).toBeVisible();
+ expect($('div.flash-alert')).toBeVisible();
+ return expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.');
+ });
+ it('fails to closes an issue with HTTP error', function() {
+ var $btnClose, $btnReopen;
+ spyOn(jQuery, 'ajax').and.callFake(function(req) {
+ expect(req.type).toBe('PUT');
+ expect(req.url).toBe('http://goesnowhere.nothing/whereami');
+ return req.error();
+ });
+ $btnClose = $('a.btn-close');
+ $btnReopen = $('a.btn-reopen');
+ $btnClose.attr('href', 'http://goesnowhere.nothing/whereami');
+ expect($btnReopen).toBeHidden();
+ expect($btnClose.text()).toBe('Close');
+ expect(typeof $btnClose.prop('disabled')).toBe('undefined');
+ $btnClose.trigger('click');
+ expect($btnReopen).toBeHidden();
+ expect($btnClose).toBeVisible();
+ expect($('div.status-box-closed')).toBeHidden();
+ expect($('div.status-box-open')).toBeVisible();
+ expect($('div.flash-alert')).toBeVisible();
+ return expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.');
+ });
+ return it('reopens an issue', function() {
+ var $btnClose, $btnReopen;
+ spyOn(jQuery, 'ajax').and.callFake(function(req) {
+ expect(req.type).toBe('PUT');
+ expect(req.url).toBe('http://gitlab.com/issues/6/reopen');
+ return req.success({
+ id: 34
+ });
+ });
+ $btnClose = $('a.btn-close');
+ $btnReopen = $('a.btn-reopen');
+ expect($btnReopen.text()).toBe('Reopen');
+ $btnReopen.trigger('click');
+ expect($btnReopen).toBeHidden();
+ expect($btnClose).toBeVisible();
+ expect($('div.status-box-open')).toBeVisible();
+ return expect($('div.status-box-closed')).toBeHidden();
+ });
+});
diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee
deleted file mode 100644
index d84d80f266b..00000000000
--- a/spec/javascripts/issue_spec.js.coffee
+++ /dev/null
@@ -1,109 +0,0 @@
-#= require lib/utils/text_utility
-#= require issue
-
-describe 'Issue', ->
- describe 'task lists', ->
- fixture.preload('issues_show.html')
-
- beforeEach ->
- fixture.load('issues_show.html')
- @issue = new Issue()
-
- it 'modifies the Markdown field', ->
- spyOn(jQuery, 'ajax').and.stub()
- $('input[type=checkbox]').attr('checked', true).trigger('change')
- expect($('.js-task-list-field').val()).toBe('- [x] Task List Item')
-
- it 'submits an ajax request on tasklist:changed', ->
- spyOn(jQuery, 'ajax').and.callFake (req) ->
- expect(req.type).toBe('PATCH')
- expect(req.url).toBe('/foo')
- expect(req.data.issue.description).not.toBe(null)
-
- $('.js-task-list-field').trigger('tasklist:changed')
-describe 'reopen/close issue', ->
- fixture.preload('issues_show.html')
- beforeEach ->
- fixture.load('issues_show.html')
- @issue = new Issue()
- it 'closes an issue', ->
- spyOn(jQuery, 'ajax').and.callFake (req) ->
- expect(req.type).toBe('PUT')
- expect(req.url).toBe('http://gitlab.com/issues/6/close')
- req.success id: 34
-
- $btnClose = $('a.btn-close')
- $btnReopen = $('a.btn-reopen')
- expect($btnReopen).toBeHidden()
- expect($btnClose.text()).toBe('Close')
- expect(typeof $btnClose.prop('disabled')).toBe('undefined')
-
- $btnClose.trigger('click')
-
- expect($btnReopen).toBeVisible()
- expect($btnClose).toBeHidden()
- expect($('div.status-box-closed')).toBeVisible()
- expect($('div.status-box-open')).toBeHidden()
-
- it 'fails to close an issue with success:false', ->
-
- spyOn(jQuery, 'ajax').and.callFake (req) ->
- expect(req.type).toBe('PUT')
- expect(req.url).toBe('http://goesnowhere.nothing/whereami')
- req.success saved: false
-
- $btnClose = $('a.btn-close')
- $btnReopen = $('a.btn-reopen')
- $btnClose.attr('href','http://goesnowhere.nothing/whereami')
- expect($btnReopen).toBeHidden()
- expect($btnClose.text()).toBe('Close')
- expect(typeof $btnClose.prop('disabled')).toBe('undefined')
-
- $btnClose.trigger('click')
-
- expect($btnReopen).toBeHidden()
- expect($btnClose).toBeVisible()
- expect($('div.status-box-closed')).toBeHidden()
- expect($('div.status-box-open')).toBeVisible()
- expect($('div.flash-alert')).toBeVisible()
- expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.')
-
- it 'fails to closes an issue with HTTP error', ->
-
- spyOn(jQuery, 'ajax').and.callFake (req) ->
- expect(req.type).toBe('PUT')
- expect(req.url).toBe('http://goesnowhere.nothing/whereami')
- req.error()
-
- $btnClose = $('a.btn-close')
- $btnReopen = $('a.btn-reopen')
- $btnClose.attr('href','http://goesnowhere.nothing/whereami')
- expect($btnReopen).toBeHidden()
- expect($btnClose.text()).toBe('Close')
- expect(typeof $btnClose.prop('disabled')).toBe('undefined')
-
- $btnClose.trigger('click')
-
- expect($btnReopen).toBeHidden()
- expect($btnClose).toBeVisible()
- expect($('div.status-box-closed')).toBeHidden()
- expect($('div.status-box-open')).toBeVisible()
- expect($('div.flash-alert')).toBeVisible()
- expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.')
-
- it 'reopens an issue', ->
- spyOn(jQuery, 'ajax').and.callFake (req) ->
- expect(req.type).toBe('PUT')
- expect(req.url).toBe('http://gitlab.com/issues/6/reopen')
- req.success id: 34
-
- $btnClose = $('a.btn-close')
- $btnReopen = $('a.btn-reopen')
- expect($btnReopen.text()).toBe('Reopen')
-
- $btnReopen.trigger('click')
-
- expect($btnReopen).toBeHidden()
- expect($btnClose).toBeVisible()
- expect($('div.status-box-open')).toBeVisible()
- expect($('div.status-box-closed')).toBeHidden()
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
new file mode 100644
index 00000000000..f7df5406f42
--- /dev/null
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -0,0 +1,225 @@
+
+/*= require line_highlighter */
+describe('LineHighlighter', function() {
+ var clickLine;
+ fixture.preload('line_highlighter.html');
+ clickLine = function(number, eventData) {
+ var e;
+ if (eventData == null) {
+ eventData = {};
+ }
+ if ($.isEmptyObject(eventData)) {
+ return $("#L" + number).mousedown().click();
+ } else {
+ e = $.Event('mousedown', eventData);
+ return $("#L" + number).trigger(e).click();
+ }
+ };
+ beforeEach(function() {
+ fixture.load('line_highlighter.html');
+ this["class"] = new LineHighlighter();
+ this.css = this["class"].highlightClass;
+ return this.spies = {
+ __setLocationHash__: spyOn(this["class"], '__setLocationHash__').and.callFake(function() {})
+ };
+ });
+ describe('behavior', function() {
+ it('highlights one line given in the URL hash', function() {
+ new LineHighlighter('#L13');
+ return expect($('#LC13')).toHaveClass(this.css);
+ });
+ it('highlights a range of lines given in the URL hash', function() {
+ var i, line, results;
+ new LineHighlighter('#L5-25');
+ expect($("." + this.css).length).toBe(21);
+ results = [];
+ for (line = i = 5; i <= 25; line = ++i) {
+ results.push(expect($("#LC" + line)).toHaveClass(this.css));
+ }
+ return results;
+ });
+ it('scrolls to the first highlighted line on initial load', function() {
+ var spy;
+ spy = spyOn($, 'scrollTo');
+ new LineHighlighter('#L5-25');
+ return expect(spy).toHaveBeenCalledWith('#L5', jasmine.anything());
+ });
+ it('discards click events', function() {
+ var spy;
+ spy = spyOnEvent('a[data-line-number]', 'click');
+ clickLine(13);
+ return expect(spy).toHaveBeenPrevented();
+ });
+ return it('handles garbage input from the hash', function() {
+ var func;
+ func = function() {
+ return new LineHighlighter('#blob-content-holder');
+ };
+ return expect(func).not.toThrow();
+ });
+ });
+ describe('#clickHandler', function() {
+ it('discards the mousedown event', function() {
+ var spy;
+ spy = spyOnEvent('a[data-line-number]', 'mousedown');
+ clickLine(13);
+ return expect(spy).toHaveBeenPrevented();
+ });
+ it('handles clicking on a child icon element', function() {
+ var spy;
+ spy = spyOn(this["class"], 'setHash').and.callThrough();
+ $('#L13 i').mousedown().click();
+ expect(spy).toHaveBeenCalledWith(13);
+ return expect($('#LC13')).toHaveClass(this.css);
+ });
+ describe('without shiftKey', function() {
+ it('highlights one line when clicked', function() {
+ clickLine(13);
+ return expect($('#LC13')).toHaveClass(this.css);
+ });
+ it('unhighlights previously highlighted lines', function() {
+ clickLine(13);
+ clickLine(20);
+ expect($('#LC13')).not.toHaveClass(this.css);
+ return expect($('#LC20')).toHaveClass(this.css);
+ });
+ return it('sets the hash', function() {
+ var spy;
+ spy = spyOn(this["class"], 'setHash').and.callThrough();
+ clickLine(13);
+ return expect(spy).toHaveBeenCalledWith(13);
+ });
+ });
+ return describe('with shiftKey', function() {
+ it('sets the hash', function() {
+ var spy;
+ spy = spyOn(this["class"], 'setHash').and.callThrough();
+ clickLine(13);
+ clickLine(20, {
+ shiftKey: true
+ });
+ expect(spy).toHaveBeenCalledWith(13);
+ return expect(spy).toHaveBeenCalledWith(13, 20);
+ });
+ describe('without existing highlight', function() {
+ it('highlights the clicked line', function() {
+ clickLine(13, {
+ shiftKey: true
+ });
+ expect($('#LC13')).toHaveClass(this.css);
+ return expect($("." + this.css).length).toBe(1);
+ });
+ return it('sets the hash', function() {
+ var spy;
+ spy = spyOn(this["class"], 'setHash');
+ clickLine(13, {
+ shiftKey: true
+ });
+ return expect(spy).toHaveBeenCalledWith(13);
+ });
+ });
+ describe('with existing single-line highlight', function() {
+ it('uses existing line as last line when target is lesser', function() {
+ var i, line, results;
+ clickLine(20);
+ clickLine(15, {
+ shiftKey: true
+ });
+ expect($("." + this.css).length).toBe(6);
+ results = [];
+ for (line = i = 15; i <= 20; line = ++i) {
+ results.push(expect($("#LC" + line)).toHaveClass(this.css));
+ }
+ return results;
+ });
+ return it('uses existing line as first line when target is greater', function() {
+ var i, line, results;
+ clickLine(5);
+ clickLine(10, {
+ shiftKey: true
+ });
+ expect($("." + this.css).length).toBe(6);
+ results = [];
+ for (line = i = 5; i <= 10; line = ++i) {
+ results.push(expect($("#LC" + line)).toHaveClass(this.css));
+ }
+ return results;
+ });
+ });
+ return describe('with existing multi-line highlight', function() {
+ beforeEach(function() {
+ clickLine(10, {
+ shiftKey: true
+ });
+ return clickLine(13, {
+ shiftKey: true
+ });
+ });
+ it('uses target as first line when it is less than existing first line', function() {
+ var i, line, results;
+ clickLine(5, {
+ shiftKey: true
+ });
+ expect($("." + this.css).length).toBe(6);
+ results = [];
+ for (line = i = 5; i <= 10; line = ++i) {
+ results.push(expect($("#LC" + line)).toHaveClass(this.css));
+ }
+ return results;
+ });
+ return it('uses target as last line when it is greater than existing first line', function() {
+ var i, line, results;
+ clickLine(15, {
+ shiftKey: true
+ });
+ expect($("." + this.css).length).toBe(6);
+ results = [];
+ for (line = i = 10; i <= 15; line = ++i) {
+ results.push(expect($("#LC" + line)).toHaveClass(this.css));
+ }
+ return results;
+ });
+ });
+ });
+ });
+ describe('#hashToRange', function() {
+ beforeEach(function() {
+ return this.subject = this["class"].hashToRange;
+ });
+ it('extracts a single line number from the hash', function() {
+ return expect(this.subject('#L5')).toEqual([5, null]);
+ });
+ it('extracts a range of line numbers from the hash', function() {
+ return expect(this.subject('#L5-15')).toEqual([5, 15]);
+ });
+ return it('returns [null, null] when the hash is not a line number', function() {
+ return expect(this.subject('#foo')).toEqual([null, null]);
+ });
+ });
+ describe('#highlightLine', function() {
+ beforeEach(function() {
+ return this.subject = this["class"].highlightLine;
+ });
+ it('highlights the specified line', function() {
+ this.subject(13);
+ return expect($('#LC13')).toHaveClass(this.css);
+ });
+ return it('accepts a String-based number', function() {
+ this.subject('13');
+ return expect($('#LC13')).toHaveClass(this.css);
+ });
+ });
+ return describe('#setHash', function() {
+ beforeEach(function() {
+ return this.subject = this["class"].setHash;
+ });
+ it('sets the location hash for a single line', function() {
+ this.subject(5);
+ return expect(this.spies.__setLocationHash__).toHaveBeenCalledWith('#L5');
+ });
+ return it('sets the location hash for a range', function() {
+ this.subject(5, 15);
+ return expect(this.spies.__setLocationHash__).toHaveBeenCalledWith('#L5-15');
+ });
+ });
+});
diff --git a/spec/javascripts/line_highlighter_spec.js.coffee b/spec/javascripts/line_highlighter_spec.js.coffee
deleted file mode 100644
index a073f21e7bc..00000000000
--- a/spec/javascripts/line_highlighter_spec.js.coffee
+++ /dev/null
@@ -1,158 +0,0 @@
-#= require line_highlighter
-
-describe 'LineHighlighter', ->
- fixture.preload('line_highlighter.html')
-
- clickLine = (number, eventData = {}) ->
- if $.isEmptyObject(eventData)
- $("#L#{number}").mousedown().click()
- else
- e = $.Event 'mousedown', eventData
- $("#L#{number}").trigger(e).click()
-
- beforeEach ->
- fixture.load('line_highlighter.html')
- @class = new LineHighlighter()
- @css = @class.highlightClass
- @spies = {
- __setLocationHash__: spyOn(@class, '__setLocationHash__').and.callFake ->
- }
-
- describe 'behavior', ->
- it 'highlights one line given in the URL hash', ->
- new LineHighlighter('#L13')
- expect($('#LC13')).toHaveClass(@css)
-
- it 'highlights a range of lines given in the URL hash', ->
- new LineHighlighter('#L5-25')
- expect($(".#{@css}").length).toBe(21)
- expect($("#LC#{line}")).toHaveClass(@css) for line in [5..25]
-
- it 'scrolls to the first highlighted line on initial load', ->
- spy = spyOn($, 'scrollTo')
- new LineHighlighter('#L5-25')
- expect(spy).toHaveBeenCalledWith('#L5', jasmine.anything())
-
- it 'discards click events', ->
- spy = spyOnEvent('a[data-line-number]', 'click')
- clickLine(13)
- expect(spy).toHaveBeenPrevented()
-
- it 'handles garbage input from the hash', ->
- func = -> new LineHighlighter('#blob-content-holder')
- expect(func).not.toThrow()
-
- describe '#clickHandler', ->
- it 'discards the mousedown event', ->
- spy = spyOnEvent('a[data-line-number]', 'mousedown')
- clickLine(13)
- expect(spy).toHaveBeenPrevented()
-
- it 'handles clicking on a child icon element', ->
- spy = spyOn(@class, 'setHash').and.callThrough()
-
- $('#L13 i').mousedown().click()
-
- expect(spy).toHaveBeenCalledWith(13)
- expect($('#LC13')).toHaveClass(@css)
-
- describe 'without shiftKey', ->
- it 'highlights one line when clicked', ->
- clickLine(13)
- expect($('#LC13')).toHaveClass(@css)
-
- it 'unhighlights previously highlighted lines', ->
- clickLine(13)
- clickLine(20)
-
- expect($('#LC13')).not.toHaveClass(@css)
- expect($('#LC20')).toHaveClass(@css)
-
- it 'sets the hash', ->
- spy = spyOn(@class, 'setHash').and.callThrough()
- clickLine(13)
- expect(spy).toHaveBeenCalledWith(13)
-
- describe 'with shiftKey', ->
- it 'sets the hash', ->
- spy = spyOn(@class, 'setHash').and.callThrough()
- clickLine(13)
- clickLine(20, shiftKey: true)
- expect(spy).toHaveBeenCalledWith(13)
- expect(spy).toHaveBeenCalledWith(13, 20)
-
- describe 'without existing highlight', ->
- it 'highlights the clicked line', ->
- clickLine(13, shiftKey: true)
- expect($('#LC13')).toHaveClass(@css)
- expect($(".#{@css}").length).toBe(1)
-
- it 'sets the hash', ->
- spy = spyOn(@class, 'setHash')
- clickLine(13, shiftKey: true)
- expect(spy).toHaveBeenCalledWith(13)
-
- describe 'with existing single-line highlight', ->
- it 'uses existing line as last line when target is lesser', ->
- clickLine(20)
- clickLine(15, shiftKey: true)
- expect($(".#{@css}").length).toBe(6)
- expect($("#LC#{line}")).toHaveClass(@css) for line in [15..20]
-
- it 'uses existing line as first line when target is greater', ->
- clickLine(5)
- clickLine(10, shiftKey: true)
- expect($(".#{@css}").length).toBe(6)
- expect($("#LC#{line}")).toHaveClass(@css) for line in [5..10]
-
- describe 'with existing multi-line highlight', ->
- beforeEach ->
- clickLine(10, shiftKey: true)
- clickLine(13, shiftKey: true)
-
- it 'uses target as first line when it is less than existing first line', ->
- clickLine(5, shiftKey: true)
- expect($(".#{@css}").length).toBe(6)
- expect($("#LC#{line}")).toHaveClass(@css) for line in [5..10]
-
- it 'uses target as last line when it is greater than existing first line', ->
- clickLine(15, shiftKey: true)
- expect($(".#{@css}").length).toBe(6)
- expect($("#LC#{line}")).toHaveClass(@css) for line in [10..15]
-
- describe '#hashToRange', ->
- beforeEach ->
- @subject = @class.hashToRange
-
- it 'extracts a single line number from the hash', ->
- expect(@subject('#L5')).toEqual([5, null])
-
- it 'extracts a range of line numbers from the hash', ->
- expect(@subject('#L5-15')).toEqual([5, 15])
-
- it 'returns [null, null] when the hash is not a line number', ->
- expect(@subject('#foo')).toEqual([null, null])
-
- describe '#highlightLine', ->
- beforeEach ->
- @subject = @class.highlightLine
-
- it 'highlights the specified line', ->
- @subject(13)
- expect($('#LC13')).toHaveClass(@css)
-
- it 'accepts a String-based number', ->
- @subject('13')
- expect($('#LC13')).toHaveClass(@css)
-
- describe '#setHash', ->
- beforeEach ->
- @subject = @class.setHash
-
- it 'sets the location hash for a single line', ->
- @subject(5)
- expect(@spies.__setLocationHash__).toHaveBeenCalledWith('#L5')
-
- it 'sets the location hash for a range', ->
- @subject(5, 15)
- expect(@spies.__setLocationHash__).toHaveBeenCalledWith('#L5-15')
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
new file mode 100644
index 00000000000..e8b4b7a4039
--- /dev/null
+++ b/spec/javascripts/merge_request_spec.js
@@ -0,0 +1,24 @@
+
+/*= require merge_request */
+describe('MergeRequest', function() {
+ return describe('task lists', function() {
+ fixture.preload('merge_requests_show.html');
+ beforeEach(function() {
+ fixture.load('merge_requests_show.html');
+ return this.merge = new MergeRequest();
+ });
+ it('modifies the Markdown field', function() {
+ spyOn(jQuery, 'ajax').and.stub();
+ $('input[type=checkbox]').attr('checked', true).trigger('change');
+ return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
+ });
+ return it('submits an ajax request on tasklist:changed', function() {
+ spyOn(jQuery, 'ajax').and.callFake(function(req) {
+ expect(req.type).toBe('PATCH');
+ expect(req.url).toBe('/foo');
+ return expect(req.data.merge_request.description).not.toBe(null);
+ });
+ return $('.js-task-list-field').trigger('tasklist:changed');
+ });
+ });
+});
diff --git a/spec/javascripts/merge_request_spec.js.coffee b/spec/javascripts/merge_request_spec.js.coffee
deleted file mode 100644
index 3cb67d51c85..00000000000
--- a/spec/javascripts/merge_request_spec.js.coffee
+++ /dev/null
@@ -1,23 +0,0 @@
-#= require merge_request
-
-describe 'MergeRequest', ->
- describe 'task lists', ->
- fixture.preload('merge_requests_show.html')
-
- beforeEach ->
- fixture.load('merge_requests_show.html')
- @merge = new MergeRequest()
-
- it 'modifies the Markdown field', ->
- spyOn(jQuery, 'ajax').and.stub()
-
- $('input[type=checkbox]').attr('checked', true).trigger('change')
- expect($('.js-task-list-field').val()).toBe('- [x] Task List Item')
-
- it 'submits an ajax request on tasklist:changed', ->
- spyOn(jQuery, 'ajax').and.callFake (req) ->
- expect(req.type).toBe('PATCH')
- expect(req.url).toBe('/foo')
- expect(req.data.merge_request.description).not.toBe(null)
-
- $('.js-task-list-field').trigger('tasklist:changed')
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
new file mode 100644
index 00000000000..ef71c5baf7d
--- /dev/null
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -0,0 +1,102 @@
+
+/*= require merge_request_tabs */
+describe('MergeRequestTabs', function() {
+ var stubLocation;
+ stubLocation = function(stubs) {
+ var defaults;
+ defaults = {
+ pathname: '',
+ search: '',
+ hash: ''
+ };
+ return $.extend(defaults, stubs);
+ };
+ fixture.preload('merge_request_tabs.html');
+ beforeEach(function() {
+ this["class"] = new MergeRequestTabs();
+ return this.spies = {
+ ajax: spyOn($, 'ajax').and.callFake(function() {}),
+ history: spyOn(history, 'replaceState').and.callFake(function() {})
+ };
+ });
+ describe('#activateTab', function() {
+ beforeEach(function() {
+ fixture.load('merge_request_tabs.html');
+ return this.subject = this["class"].activateTab;
+ });
+ it('shows the first tab when action is show', function() {
+ this.subject('show');
+ return expect($('#notes')).toHaveClass('active');
+ });
+ it('shows the notes tab when action is notes', function() {
+ this.subject('notes');
+ return expect($('#notes')).toHaveClass('active');
+ });
+ it('shows the commits tab when action is commits', function() {
+ this.subject('commits');
+ return expect($('#commits')).toHaveClass('active');
+ });
+ return it('shows the diffs tab when action is diffs', function() {
+ this.subject('diffs');
+ return expect($('#diffs')).toHaveClass('active');
+ });
+ });
+ return describe('#setCurrentAction', function() {
+ beforeEach(function() {
+ return this.subject = this["class"].setCurrentAction;
+ });
+ it('changes from commits', function() {
+ this["class"]._location = stubLocation({
+ pathname: '/foo/bar/merge_requests/1/commits'
+ });
+ expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
+ return expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
+ });
+ it('changes from diffs', function() {
+ this["class"]._location = stubLocation({
+ pathname: '/foo/bar/merge_requests/1/diffs'
+ });
+ expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
+ return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
+ });
+ it('changes from diffs.html', function() {
+ this["class"]._location = stubLocation({
+ pathname: '/foo/bar/merge_requests/1/diffs.html'
+ });
+ expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
+ return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
+ });
+ it('changes from notes', function() {
+ this["class"]._location = stubLocation({
+ pathname: '/foo/bar/merge_requests/1'
+ });
+ expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
+ return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
+ });
+ it('includes search parameters and hash string', function() {
+ this["class"]._location = stubLocation({
+ pathname: '/foo/bar/merge_requests/1/diffs',
+ search: '?view=parallel',
+ hash: '#L15-35'
+ });
+ return expect(this.subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35');
+ });
+ it('replaces the current history state', function() {
+ var new_state;
+ this["class"]._location = stubLocation({
+ pathname: '/foo/bar/merge_requests/1'
+ });
+ new_state = this.subject('commits');
+ return expect(this.spies.history).toHaveBeenCalledWith({
+ turbolinks: true,
+ url: new_state
+ }, document.title, new_state);
+ });
+ return it('treats "show" like "notes"', function() {
+ this["class"]._location = stubLocation({
+ pathname: '/foo/bar/merge_requests/1/commits'
+ });
+ return expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
+ });
+ });
+});
diff --git a/spec/javascripts/merge_request_tabs_spec.js.coffee b/spec/javascripts/merge_request_tabs_spec.js.coffee
deleted file mode 100644
index a0cfba455ea..00000000000
--- a/spec/javascripts/merge_request_tabs_spec.js.coffee
+++ /dev/null
@@ -1,88 +0,0 @@
-#= require merge_request_tabs
-
-describe 'MergeRequestTabs', ->
- stubLocation = (stubs) ->
- defaults = {pathname: '', search: '', hash: ''}
- $.extend(defaults, stubs)
-
- fixture.preload('merge_request_tabs.html')
-
- beforeEach ->
- @class = new MergeRequestTabs()
- @spies = {
- ajax: spyOn($, 'ajax').and.callFake ->
- history: spyOn(history, 'replaceState').and.callFake ->
- }
-
- describe '#activateTab', ->
- beforeEach ->
- fixture.load('merge_request_tabs.html')
- @subject = @class.activateTab
-
- it 'shows the first tab when action is show', ->
- @subject('show')
- expect($('#notes')).toHaveClass('active')
-
- it 'shows the notes tab when action is notes', ->
- @subject('notes')
- expect($('#notes')).toHaveClass('active')
-
- it 'shows the commits tab when action is commits', ->
- @subject('commits')
- expect($('#commits')).toHaveClass('active')
-
- it 'shows the diffs tab when action is diffs', ->
- @subject('diffs')
- expect($('#diffs')).toHaveClass('active')
-
- describe '#setCurrentAction', ->
- beforeEach ->
- @subject = @class.setCurrentAction
-
- it 'changes from commits', ->
- @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1/commits')
-
- expect(@subject('notes')).toBe('/foo/bar/merge_requests/1')
- expect(@subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs')
-
- it 'changes from diffs', ->
- @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1/diffs')
-
- expect(@subject('notes')).toBe('/foo/bar/merge_requests/1')
- expect(@subject('commits')).toBe('/foo/bar/merge_requests/1/commits')
-
- it 'changes from diffs.html', ->
- @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1/diffs.html')
-
- expect(@subject('notes')).toBe('/foo/bar/merge_requests/1')
- expect(@subject('commits')).toBe('/foo/bar/merge_requests/1/commits')
-
- it 'changes from notes', ->
- @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1')
-
- expect(@subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs')
- expect(@subject('commits')).toBe('/foo/bar/merge_requests/1/commits')
-
- it 'includes search parameters and hash string', ->
- @class._location = stubLocation({
- pathname: '/foo/bar/merge_requests/1/diffs'
- search: '?view=parallel'
- hash: '#L15-35'
- })
-
- expect(@subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35')
-
- it 'replaces the current history state', ->
- @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1')
- new_state = @subject('commits')
-
- expect(@spies.history).toHaveBeenCalledWith(
- {turbolinks: true, url: new_state},
- document.title,
- new_state
- )
-
- it 'treats "show" like "notes"', ->
- @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1/commits')
-
- expect(@subject('show')).toBe('/foo/bar/merge_requests/1')
diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js
new file mode 100644
index 00000000000..8060e36d73e
--- /dev/null
+++ b/spec/javascripts/merge_request_widget_spec.js
@@ -0,0 +1,70 @@
+
+/*= require merge_request_widget */
+describe('MergeRequestWidget', function() {
+ beforeEach(function() {
+ window.notifyPermissions = function() {};
+ window.notify = function() {};
+ this.opts = {
+ ci_status_url: "http://sampledomain.local/ci/getstatus",
+ ci_status: "",
+ ci_message: {
+ normal: "Build {{status}} for \"{{title}}\"",
+ preparing: "{{status}} build for \"{{title}}\""
+ },
+ ci_title: {
+ preparing: "{{status}} build",
+ normal: "Build {{status}}"
+ },
+ gitlab_icon: "gitlab_logo.png",
+ builds_path: "http://sampledomain.local/sampleBuildsPath"
+ };
+ this["class"] = new MergeRequestWidget(this.opts);
+ return this.ciStatusData = {
+ "title": "Sample MR title",
+ "sha": "12a34bc5",
+ "status": "success",
+ "coverage": 98
+ };
+ });
+ return describe('getCIStatus', function() {
+ beforeEach(function() {
+ return spyOn(jQuery, 'getJSON').and.callFake((function(_this) {
+ return function(req, cb) {
+ return cb(_this.ciStatusData);
+ };
+ })(this));
+ });
+ it('should call showCIStatus even if a notification should not be displayed', function() {
+ var spy;
+ spy = spyOn(this["class"], 'showCIStatus').and.stub();
+ this["class"].getCIStatus(false);
+ return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status);
+ });
+ it('should call showCIStatus when a notification should be displayed', function() {
+ var spy;
+ spy = spyOn(this["class"], 'showCIStatus').and.stub();
+ this["class"].getCIStatus(true);
+ return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status);
+ });
+ it('should call showCICoverage when the coverage rate is set', function() {
+ var spy;
+ spy = spyOn(this["class"], 'showCICoverage').and.stub();
+ this["class"].getCIStatus(false);
+ return expect(spy).toHaveBeenCalledWith(this.ciStatusData.coverage);
+ });
+ it('should not call showCICoverage when the coverage rate is not set', function() {
+ var spy;
+ this.ciStatusData.coverage = null;
+ spy = spyOn(this["class"], 'showCICoverage').and.stub();
+ this["class"].getCIStatus(false);
+ return expect(spy).not.toHaveBeenCalled();
+ });
+ return it('should not display a notification on the first check after the widget has been created', function() {
+ var spy;
+ spy = spyOn(window, 'notify');
+ this["class"] = new MergeRequestWidget(this.opts);
+ this["class"].getCIStatus(true);
+ return expect(spy).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/javascripts/merge_request_widget_spec.js.coffee b/spec/javascripts/merge_request_widget_spec.js.coffee
deleted file mode 100644
index 92b7eeb1116..00000000000
--- a/spec/javascripts/merge_request_widget_spec.js.coffee
+++ /dev/null
@@ -1,55 +0,0 @@
-#= require merge_request_widget
-
-describe 'MergeRequestWidget', ->
-
- beforeEach ->
- window.notifyPermissions = () ->
- window.notify = () ->
- @opts = {
- ci_status_url:"http://sampledomain.local/ci/getstatus",
- ci_status:"",
- ci_message: {
- normal: "Build {{status}} for \"{{title}}\"",
- preparing: "{{status}} build for \"{{title}}\""
- },
- ci_title: {
- preparing: "{{status}} build",
- normal: "Build {{status}}"
- },
- gitlab_icon:"gitlab_logo.png",
- builds_path:"http://sampledomain.local/sampleBuildsPath"
- }
- @class = new MergeRequestWidget(@opts)
- @ciStatusData = {"title":"Sample MR title","sha":"12a34bc5","status":"success","coverage":98}
-
- describe 'getCIStatus', ->
- beforeEach ->
- spyOn(jQuery, 'getJSON').and.callFake (req, cb) =>
- cb(@ciStatusData)
-
- it 'should call showCIStatus even if a notification should not be displayed', ->
- spy = spyOn(@class, 'showCIStatus').and.stub()
- @class.getCIStatus(false)
- expect(spy).toHaveBeenCalledWith(@ciStatusData.status)
-
- it 'should call showCIStatus when a notification should be displayed', ->
- spy = spyOn(@class, 'showCIStatus').and.stub()
- @class.getCIStatus(true)
- expect(spy).toHaveBeenCalledWith(@ciStatusData.status)
-
- it 'should call showCICoverage when the coverage rate is set', ->
- spy = spyOn(@class, 'showCICoverage').and.stub()
- @class.getCIStatus(false)
- expect(spy).toHaveBeenCalledWith(@ciStatusData.coverage)
-
- it 'should not call showCICoverage when the coverage rate is not set', ->
- @ciStatusData.coverage = null
- spy = spyOn(@class, 'showCICoverage').and.stub()
- @class.getCIStatus(false)
- expect(spy).not.toHaveBeenCalled()
-
- it 'should not display a notification on the first check after the widget has been created', ->
- spy = spyOn(window, 'notify')
- @class = new MergeRequestWidget(@opts)
- @class.getCIStatus(true)
- expect(spy).not.toHaveBeenCalled()
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
new file mode 100644
index 00000000000..980f401bdbc
--- /dev/null
+++ b/spec/javascripts/new_branch_spec.js
@@ -0,0 +1,165 @@
+
+/*= require jquery-ui/autocomplete */
+
+/*= require new_branch_form */
+describe('Branch', function() {
+ return describe('create a new branch', function() {
+ var expectToHaveError, fillNameWith;
+ fixture.preload('new_branch.html');
+ fillNameWith = function(value) {
+ return $('.js-branch-name').val(value).trigger('blur');
+ };
+ expectToHaveError = function(error) {
+ return expect($('.js-branch-name-error span').text()).toEqual(error);
+ };
+ beforeEach(function() {
+ fixture.load('new_branch.html');
+ $('form').on('submit', function(e) {
+ return e.preventDefault();
+ });
+ return this.form = new NewBranchForm($('.js-create-branch-form'), []);
+ });
+ it("can't start with a dot", function() {
+ fillNameWith('.foo');
+ return expectToHaveError("can't start with '.'");
+ });
+ it("can't start with a slash", function() {
+ fillNameWith('/foo');
+ return expectToHaveError("can't start with '/'");
+ });
+ it("can't have two consecutive dots", function() {
+ fillNameWith('foo..bar');
+ return expectToHaveError("can't contain '..'");
+ });
+ it("can't have spaces anywhere", function() {
+ fillNameWith(' foo');
+ expectToHaveError("can't contain spaces");
+ fillNameWith('foo bar');
+ expectToHaveError("can't contain spaces");
+ fillNameWith('foo ');
+ return expectToHaveError("can't contain spaces");
+ });
+ it("can't have ~ anywhere", function() {
+ fillNameWith('~foo');
+ expectToHaveError("can't contain '~'");
+ fillNameWith('foo~bar');
+ expectToHaveError("can't contain '~'");
+ fillNameWith('foo~');
+ return expectToHaveError("can't contain '~'");
+ });
+ it("can't have tilde anwhere", function() {
+ fillNameWith('~foo');
+ expectToHaveError("can't contain '~'");
+ fillNameWith('foo~bar');
+ expectToHaveError("can't contain '~'");
+ fillNameWith('foo~');
+ return expectToHaveError("can't contain '~'");
+ });
+ it("can't have caret anywhere", function() {
+ fillNameWith('^foo');
+ expectToHaveError("can't contain '^'");
+ fillNameWith('foo^bar');
+ expectToHaveError("can't contain '^'");
+ fillNameWith('foo^');
+ return expectToHaveError("can't contain '^'");
+ });
+ it("can't have : anywhere", function() {
+ fillNameWith(':foo');
+ expectToHaveError("can't contain ':'");
+ fillNameWith('foo:bar');
+ expectToHaveError("can't contain ':'");
+ fillNameWith(':foo');
+ return expectToHaveError("can't contain ':'");
+ });
+ it("can't have question mark anywhere", function() {
+ fillNameWith('?foo');
+ expectToHaveError("can't contain '?'");
+ fillNameWith('foo?bar');
+ expectToHaveError("can't contain '?'");
+ fillNameWith('foo?');
+ return expectToHaveError("can't contain '?'");
+ });
+ it("can't have asterisk anywhere", function() {
+ fillNameWith('*foo');
+ expectToHaveError("can't contain '*'");
+ fillNameWith('foo*bar');
+ expectToHaveError("can't contain '*'");
+ fillNameWith('foo*');
+ return expectToHaveError("can't contain '*'");
+ });
+ it("can't have open bracket anywhere", function() {
+ fillNameWith('[foo');
+ expectToHaveError("can't contain '['");
+ fillNameWith('foo[bar');
+ expectToHaveError("can't contain '['");
+ fillNameWith('foo[');
+ return expectToHaveError("can't contain '['");
+ });
+ it("can't have a backslash anywhere", function() {
+ fillNameWith('\\foo');
+ expectToHaveError("can't contain '\\'");
+ fillNameWith('foo\\bar');
+ expectToHaveError("can't contain '\\'");
+ fillNameWith('foo\\');
+ return expectToHaveError("can't contain '\\'");
+ });
+ it("can't contain a sequence @{ anywhere", function() {
+ fillNameWith('@{foo');
+ expectToHaveError("can't contain '@{'");
+ fillNameWith('foo@{bar');
+ expectToHaveError("can't contain '@{'");
+ fillNameWith('foo@{');
+ return expectToHaveError("can't contain '@{'");
+ });
+ it("can't have consecutive slashes", function() {
+ fillNameWith('foo//bar');
+ return expectToHaveError("can't contain consecutive slashes");
+ });
+ it("can't end with a slash", function() {
+ fillNameWith('foo/');
+ return expectToHaveError("can't end in '/'");
+ });
+ it("can't end with a dot", function() {
+ fillNameWith('foo.');
+ return expectToHaveError("can't end in '.'");
+ });
+ it("can't end with .lock", function() {
+ fillNameWith('foo.lock');
+ return expectToHaveError("can't end in '.lock'");
+ });
+ it("can't be the single character @", function() {
+ fillNameWith('@');
+ return expectToHaveError("can't be '@'");
+ });
+ it("concatenates all error messages", function() {
+ fillNameWith('/foo bar?~.');
+ return expectToHaveError("can't start with '/', can't contain spaces, '?', '~', can't end in '.'");
+ });
+ it("doesn't duplicate error messages", function() {
+ fillNameWith('?foo?bar?zoo?');
+ return expectToHaveError("can't contain '?'");
+ });
+ it("removes the error message when is a valid name", function() {
+ fillNameWith('foo?bar');
+ expect($('.js-branch-name-error span').length).toEqual(1);
+ fillNameWith('foobar');
+ return expect($('.js-branch-name-error span').length).toEqual(0);
+ });
+ it("can have dashes anywhere", function() {
+ fillNameWith('-foo-bar-zoo-');
+ return expect($('.js-branch-name-error span').length).toEqual(0);
+ });
+ it("can have underscores anywhere", function() {
+ fillNameWith('_foo_bar_zoo_');
+ return expect($('.js-branch-name-error span').length).toEqual(0);
+ });
+ it("can have numbers anywhere", function() {
+ fillNameWith('1foo2bar3zoo4');
+ return expect($('.js-branch-name-error span').length).toEqual(0);
+ });
+ return it("can be only letters", function() {
+ fillNameWith('foo');
+ return expect($('.js-branch-name-error span').length).toEqual(0);
+ });
+ });
+});
diff --git a/spec/javascripts/new_branch_spec.js.coffee b/spec/javascripts/new_branch_spec.js.coffee
deleted file mode 100644
index ce773793817..00000000000
--- a/spec/javascripts/new_branch_spec.js.coffee
+++ /dev/null
@@ -1,160 +0,0 @@
-#= require jquery-ui/autocomplete
-#= require new_branch_form
-
-describe 'Branch', ->
- describe 'create a new branch', ->
- fixture.preload('new_branch.html')
-
- fillNameWith = (value) ->
- $('.js-branch-name').val(value).trigger('blur')
-
- expectToHaveError = (error) ->
- expect($('.js-branch-name-error span').text()).toEqual(error)
-
- beforeEach ->
- fixture.load('new_branch.html')
- $('form').on 'submit', (e) -> e.preventDefault()
-
- @form = new NewBranchForm($('.js-create-branch-form'), [])
-
- it "can't start with a dot", ->
- fillNameWith '.foo'
- expectToHaveError "can't start with '.'"
-
- it "can't start with a slash", ->
- fillNameWith '/foo'
- expectToHaveError "can't start with '/'"
-
- it "can't have two consecutive dots", ->
- fillNameWith 'foo..bar'
- expectToHaveError "can't contain '..'"
-
- it "can't have spaces anywhere", ->
- fillNameWith ' foo'
- expectToHaveError "can't contain spaces"
- fillNameWith 'foo bar'
- expectToHaveError "can't contain spaces"
- fillNameWith 'foo '
- expectToHaveError "can't contain spaces"
-
- it "can't have ~ anywhere", ->
- fillNameWith '~foo'
- expectToHaveError "can't contain '~'"
- fillNameWith 'foo~bar'
- expectToHaveError "can't contain '~'"
- fillNameWith 'foo~'
- expectToHaveError "can't contain '~'"
-
- it "can't have tilde anwhere", ->
- fillNameWith '~foo'
- expectToHaveError "can't contain '~'"
- fillNameWith 'foo~bar'
- expectToHaveError "can't contain '~'"
- fillNameWith 'foo~'
- expectToHaveError "can't contain '~'"
-
- it "can't have caret anywhere", ->
- fillNameWith '^foo'
- expectToHaveError "can't contain '^'"
- fillNameWith 'foo^bar'
- expectToHaveError "can't contain '^'"
- fillNameWith 'foo^'
- expectToHaveError "can't contain '^'"
-
- it "can't have : anywhere", ->
- fillNameWith ':foo'
- expectToHaveError "can't contain ':'"
- fillNameWith 'foo:bar'
- expectToHaveError "can't contain ':'"
- fillNameWith ':foo'
- expectToHaveError "can't contain ':'"
-
- it "can't have question mark anywhere", ->
- fillNameWith '?foo'
- expectToHaveError "can't contain '?'"
- fillNameWith 'foo?bar'
- expectToHaveError "can't contain '?'"
- fillNameWith 'foo?'
- expectToHaveError "can't contain '?'"
-
- it "can't have asterisk anywhere", ->
- fillNameWith '*foo'
- expectToHaveError "can't contain '*'"
- fillNameWith 'foo*bar'
- expectToHaveError "can't contain '*'"
- fillNameWith 'foo*'
- expectToHaveError "can't contain '*'"
-
- it "can't have open bracket anywhere", ->
- fillNameWith '[foo'
- expectToHaveError "can't contain '['"
- fillNameWith 'foo[bar'
- expectToHaveError "can't contain '['"
- fillNameWith 'foo['
- expectToHaveError "can't contain '['"
-
- it "can't have a backslash anywhere", ->
- fillNameWith '\\foo'
- expectToHaveError "can't contain '\\'"
- fillNameWith 'foo\\bar'
- expectToHaveError "can't contain '\\'"
- fillNameWith 'foo\\'
- expectToHaveError "can't contain '\\'"
-
- it "can't contain a sequence @{ anywhere", ->
- fillNameWith '@{foo'
- expectToHaveError "can't contain '@{'"
- fillNameWith 'foo@{bar'
- expectToHaveError "can't contain '@{'"
- fillNameWith 'foo@{'
- expectToHaveError "can't contain '@{'"
-
- it "can't have consecutive slashes", ->
- fillNameWith 'foo//bar'
- expectToHaveError "can't contain consecutive slashes"
-
- it "can't end with a slash", ->
- fillNameWith 'foo/'
- expectToHaveError "can't end in '/'"
-
- it "can't end with a dot", ->
- fillNameWith 'foo.'
- expectToHaveError "can't end in '.'"
-
- it "can't end with .lock", ->
- fillNameWith 'foo.lock'
- expectToHaveError "can't end in '.lock'"
-
- it "can't be the single character @", ->
- fillNameWith '@'
- expectToHaveError "can't be '@'"
-
- it "concatenates all error messages", ->
- fillNameWith '/foo bar?~.'
- expectToHaveError "can't start with '/', can't contain spaces, '?', '~', can't end in '.'"
-
- it "doesn't duplicate error messages", ->
- fillNameWith '?foo?bar?zoo?'
- expectToHaveError "can't contain '?'"
-
- it "removes the error message when is a valid name", ->
- fillNameWith 'foo?bar'
- expect($('.js-branch-name-error span').length).toEqual(1)
- fillNameWith 'foobar'
- expect($('.js-branch-name-error span').length).toEqual(0)
-
- it "can have dashes anywhere", ->
- fillNameWith '-foo-bar-zoo-'
- expect($('.js-branch-name-error span').length).toEqual(0)
-
- it "can have underscores anywhere", ->
- fillNameWith '_foo_bar_zoo_'
- expect($('.js-branch-name-error span').length).toEqual(0)
-
- it "can have numbers anywhere", ->
- fillNameWith '1foo2bar3zoo4'
- expect($('.js-branch-name-error span').length).toEqual(0)
-
- it "can be only letters", ->
- fillNameWith 'foo'
- expect($('.js-branch-name-error span').length).toEqual(0)
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
new file mode 100644
index 00000000000..c05fd619a63
--- /dev/null
+++ b/spec/javascripts/notes_spec.js
@@ -0,0 +1,36 @@
+
+/*= require notes */
+
+/*= require gl_form */
+window.gon || (window.gon = {});
+
+window.disableButtonIfEmptyField = function() {
+ return null;
+};
+
+describe('Notes', function() {
+ return describe('task lists', function() {
+ fixture.preload('issue_note.html');
+ beforeEach(function() {
+ fixture.load('issue_note.html');
+ $('form').on('submit', function(e) {
+ return e.preventDefault();
+ });
+ return this.notes = new Notes();
+ });
+ it('modifies the Markdown field', function() {
+ $('input[type=checkbox]').attr('checked', true).trigger('change');
+ return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
+ });
+ return it('submits the form on tasklist:changed', function() {
+ var submitted;
+ submitted = false;
+ $('form').on('submit', function(e) {
+ submitted = true;
+ return e.preventDefault();
+ });
+ $('.js-task-list-field').trigger('tasklist:changed');
+ return expect(submitted).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/notes_spec.js.coffee b/spec/javascripts/notes_spec.js.coffee
deleted file mode 100644
index 3a3c8d63e82..00000000000
--- a/spec/javascripts/notes_spec.js.coffee
+++ /dev/null
@@ -1,26 +0,0 @@
-#= require notes
-#= require gl_form
-
-window.gon or= {}
-window.disableButtonIfEmptyField = -> null
-
-describe 'Notes', ->
- describe 'task lists', ->
- fixture.preload('issue_note.html')
-
- beforeEach ->
- fixture.load('issue_note.html')
- $('form').on 'submit', (e) -> e.preventDefault()
-
- @notes = new Notes()
-
- it 'modifies the Markdown field', ->
- $('input[type=checkbox]').attr('checked', true).trigger('change')
- expect($('.js-task-list-field').val()).toBe('- [x] Task List Item')
-
- it 'submits the form on tasklist:changed', ->
- submitted = false
- $('form').on 'submit', (e) -> submitted = true; e.preventDefault()
-
- $('.js-task-list-field').trigger('tasklist:changed')
- expect(submitted).toBe(true)
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
new file mode 100644
index 00000000000..00c54cbf0c2
--- /dev/null
+++ b/spec/javascripts/project_title_spec.js
@@ -0,0 +1,50 @@
+
+/*= require bootstrap */
+
+/*= require select2 */
+
+/*= require lib/utils/type_utility */
+
+/*= require gl_dropdown */
+
+/*= require api */
+
+/*= require project_select */
+
+/*= require project */
+window.gon || (window.gon = {});
+
+window.gon.api_version = 'v3';
+
+describe('Project Title', function() {
+ fixture.preload('project_title.html');
+ fixture.preload('projects.json');
+ beforeEach(function() {
+ fixture.load('project_title.html');
+ return this.project = new Project();
+ });
+ return describe('project list', function() {
+ beforeEach((function(_this) {
+ return function() {
+ _this.projects_data = fixture.load('projects.json')[0];
+ return spyOn(jQuery, 'ajax').and.callFake(function(req) {
+ var d;
+ expect(req.url).toBe('/api/v3/projects.json?simple=true');
+ d = $.Deferred();
+ d.resolve(_this.projects_data);
+ return d.promise();
+ });
+ };
+ })(this));
+ it('to show on toggle click', (function(_this) {
+ return function() {
+ $('.js-projects-dropdown-toggle').click();
+ return expect($('.header-content').hasClass('open')).toBe(true);
+ };
+ })(this));
+ return it('hide dropdown', function() {
+ $(".dropdown-menu-close-icon").click();
+ return expect($('.header-content').hasClass('open')).toBe(false);
+ });
+ });
+});
diff --git a/spec/javascripts/project_title_spec.js.coffee b/spec/javascripts/project_title_spec.js.coffee
deleted file mode 100644
index 0244119fa0e..00000000000
--- a/spec/javascripts/project_title_spec.js.coffee
+++ /dev/null
@@ -1,37 +0,0 @@
-#= require bootstrap
-#= require select2
-#= require lib/utils/type_utility
-#= require gl_dropdown
-#= require api
-#= require project_select
-#= require project
-
-window.gon or= {}
-window.gon.api_version = 'v3'
-
-describe 'Project Title', ->
- fixture.preload('project_title.html')
- fixture.preload('projects.json')
-
- beforeEach ->
- fixture.load('project_title.html')
- @project = new Project()
-
- describe 'project list', ->
- beforeEach =>
- @projects_data = fixture.load('projects.json')[0]
-
- spyOn(jQuery, 'ajax').and.callFake (req) =>
- expect(req.url).toBe('/api/v3/projects.json?simple=true')
- d = $.Deferred()
- d.resolve @projects_data
- d.promise()
-
- it 'to show on toggle click', =>
- $('.js-projects-dropdown-toggle').click()
- expect($('.header-content').hasClass('open')).toBe(true)
-
- it 'hide dropdown', ->
- $(".dropdown-menu-close-icon").click()
-
- expect($('.header-content').hasClass('open')).toBe(false)
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
new file mode 100644
index 00000000000..70c65c3c622
--- /dev/null
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -0,0 +1,64 @@
+
+/*= require right_sidebar */
+
+/*= require jquery */
+
+/*= require jquery.cookie */
+var $aside, $icon, $labelsIcon, $page, $toggle, assertSidebarState;
+
+this.sidebar = null;
+
+$aside = null;
+
+$toggle = null;
+
+$icon = null;
+
+$page = null;
+
+$labelsIcon = null;
+
+assertSidebarState = function(state) {
+ var shouldBeCollapsed, shouldBeExpanded;
+ shouldBeExpanded = state === 'expanded';
+ shouldBeCollapsed = state === 'collapsed';
+ expect($aside.hasClass('right-sidebar-expanded')).toBe(shouldBeExpanded);
+ expect($page.hasClass('right-sidebar-expanded')).toBe(shouldBeExpanded);
+ expect($icon.hasClass('fa-angle-double-right')).toBe(shouldBeExpanded);
+ expect($aside.hasClass('right-sidebar-collapsed')).toBe(shouldBeCollapsed);
+ expect($page.hasClass('right-sidebar-collapsed')).toBe(shouldBeCollapsed);
+ return expect($icon.hasClass('fa-angle-double-left')).toBe(shouldBeCollapsed);
+};
+
+describe('RightSidebar', function() {
+ fixture.preload('right_sidebar.html');
+ beforeEach(function() {
+ fixture.load('right_sidebar.html');
+ this.sidebar = new Sidebar;
+ $aside = $('.right-sidebar');
+ $page = $('.page-with-sidebar');
+ $icon = $aside.find('i');
+ $toggle = $aside.find('.js-sidebar-toggle');
+ return $labelsIcon = $aside.find('.sidebar-collapsed-icon');
+ });
+ it('should expand the sidebar when arrow is clicked', function() {
+ $toggle.click();
+ return assertSidebarState('expanded');
+ });
+ it('should collapse the sidebar when arrow is clicked', function() {
+ $toggle.click();
+ assertSidebarState('expanded');
+ $toggle.click();
+ return assertSidebarState('collapsed');
+ });
+ it('should float over the page and when sidebar icons clicked', function() {
+ $labelsIcon.click();
+ return assertSidebarState('expanded');
+ });
+ return it('should collapse when the icon arrow clicked while it is floating on page', function() {
+ $labelsIcon.click();
+ assertSidebarState('expanded');
+ $toggle.click();
+ return assertSidebarState('collapsed');
+ });
+});
diff --git a/spec/javascripts/right_sidebar_spec.js.coffee b/spec/javascripts/right_sidebar_spec.js.coffee
deleted file mode 100644
index 2075cacdb67..00000000000
--- a/spec/javascripts/right_sidebar_spec.js.coffee
+++ /dev/null
@@ -1,69 +0,0 @@
-#= require right_sidebar
-#= require jquery
-#= require jquery.cookie
-
-@sidebar = null
-$aside = null
-$toggle = null
-$icon = null
-$page = null
-$labelsIcon = null
-
-
-assertSidebarState = (state) ->
-
- shouldBeExpanded = state is 'expanded'
- shouldBeCollapsed = state is 'collapsed'
-
- expect($aside.hasClass('right-sidebar-expanded')).toBe shouldBeExpanded
- expect($page.hasClass('right-sidebar-expanded')).toBe shouldBeExpanded
- expect($icon.hasClass('fa-angle-double-right')).toBe shouldBeExpanded
-
- expect($aside.hasClass('right-sidebar-collapsed')).toBe shouldBeCollapsed
- expect($page.hasClass('right-sidebar-collapsed')).toBe shouldBeCollapsed
- expect($icon.hasClass('fa-angle-double-left')).toBe shouldBeCollapsed
-
-
-describe 'RightSidebar', ->
-
- fixture.preload 'right_sidebar.html'
-
- beforeEach ->
- fixture.load 'right_sidebar.html'
-
- @sidebar = new Sidebar
- $aside = $ '.right-sidebar'
- $page = $ '.page-with-sidebar'
- $icon = $aside.find 'i'
- $toggle = $aside.find '.js-sidebar-toggle'
- $labelsIcon = $aside.find '.sidebar-collapsed-icon'
-
-
- it 'should expand the sidebar when arrow is clicked', ->
-
- $toggle.click()
- assertSidebarState 'expanded'
-
-
- it 'should collapse the sidebar when arrow is clicked', ->
-
- $toggle.click()
- assertSidebarState 'expanded'
-
- $toggle.click()
- assertSidebarState 'collapsed'
-
-
- it 'should float over the page and when sidebar icons clicked', ->
-
- $labelsIcon.click()
- assertSidebarState 'expanded'
-
-
- it 'should collapse when the icon arrow clicked while it is floating on page', ->
-
- $labelsIcon.click()
- assertSidebarState 'expanded'
-
- $toggle.click()
- assertSidebarState 'collapsed'
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
new file mode 100644
index 00000000000..a609a2bd042
--- /dev/null
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -0,0 +1,150 @@
+
+/*= require gl_dropdown */
+
+/*= require search_autocomplete */
+
+/*= require jquery */
+
+/*= require lib/utils/common_utils */
+
+/*= require lib/utils/type_utility */
+
+/*= require fuzzaldrin-plus */
+var addBodyAttributes, assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget;
+
+widget = null;
+
+userId = 1;
+
+window.gon || (window.gon = {});
+
+window.gon.current_user_id = userId;
+
+dashboardIssuesPath = '/dashboard/issues';
+
+dashboardMRsPath = '/dashboard/merge_requests';
+
+projectIssuesPath = '/gitlab-org/gitlab-ce/issues';
+
+projectMRsPath = '/gitlab-org/gitlab-ce/merge_requests';
+
+groupIssuesPath = '/groups/gitlab-org/issues';
+
+groupMRsPath = '/groups/gitlab-org/merge_requests';
+
+projectName = 'GitLab Community Edition';
+
+groupName = 'Gitlab Org';
+
+addBodyAttributes = function(section) {
+ var $body;
+ if (section == null) {
+ section = 'dashboard';
+ }
+ $body = $('body');
+ $body.removeAttr('data-page');
+ $body.removeAttr('data-project');
+ $body.removeAttr('data-group');
+ switch (section) {
+ case 'dashboard':
+ return $body.data('page', 'root:index');
+ case 'group':
+ $body.data('page', 'groups:show');
+ return $body.data('group', 'gitlab-org');
+ case 'project':
+ $body.data('page', 'projects:show');
+ return $body.data('project', 'gitlab-ce');
+ }
+};
+
+mockDashboardOptions = function() {
+ window.gl || (window.gl = {});
+ return window.gl.dashboardOptions = {
+ issuesPath: dashboardIssuesPath,
+ mrPath: dashboardMRsPath
+ };
+};
+
+mockProjectOptions = function() {
+ window.gl || (window.gl = {});
+ return window.gl.projectOptions = {
+ 'gitlab-ce': {
+ issuesPath: projectIssuesPath,
+ mrPath: projectMRsPath,
+ projectName: projectName
+ }
+ };
+};
+
+mockGroupOptions = function() {
+ window.gl || (window.gl = {});
+ return window.gl.groupOptions = {
+ 'gitlab-org': {
+ issuesPath: groupIssuesPath,
+ mrPath: groupMRsPath,
+ projectName: groupName
+ }
+ };
+};
+
+assertLinks = function(list, issuesPath, mrsPath) {
+ var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink;
+ issuesAssignedToMeLink = issuesPath + "/?assignee_id=" + userId;
+ issuesIHaveCreatedLink = issuesPath + "/?author_id=" + userId;
+ mrsAssignedToMeLink = mrsPath + "/?assignee_id=" + userId;
+ mrsIHaveCreatedLink = mrsPath + "/?author_id=" + userId;
+ a1 = "a[href='" + issuesAssignedToMeLink + "']";
+ a2 = "a[href='" + issuesIHaveCreatedLink + "']";
+ a3 = "a[href='" + mrsAssignedToMeLink + "']";
+ a4 = "a[href='" + mrsIHaveCreatedLink + "']";
+ expect(list.find(a1).length).toBe(1);
+ expect(list.find(a1).text()).toBe(' Issues assigned to me ');
+ expect(list.find(a2).length).toBe(1);
+ expect(list.find(a2).text()).toBe(" Issues I've created ");
+ expect(list.find(a3).length).toBe(1);
+ expect(list.find(a3).text()).toBe(' Merge requests assigned to me ');
+ expect(list.find(a4).length).toBe(1);
+ return expect(list.find(a4).text()).toBe(" Merge requests I've created ");
+};
+
+describe('Search autocomplete dropdown', function() {
+ fixture.preload('search_autocomplete.html');
+ beforeEach(function() {
+ fixture.load('search_autocomplete.html');
+ return widget = new SearchAutocomplete;
+ });
+ it('should show Dashboard specific dropdown menu', function() {
+ var list;
+ addBodyAttributes();
+ mockDashboardOptions();
+ widget.searchInput.focus();
+ list = widget.wrap.find('.dropdown-menu').find('ul');
+ return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
+ });
+ it('should show Group specific dropdown menu', function() {
+ var list;
+ addBodyAttributes('group');
+ mockGroupOptions();
+ widget.searchInput.focus();
+ list = widget.wrap.find('.dropdown-menu').find('ul');
+ return assertLinks(list, groupIssuesPath, groupMRsPath);
+ });
+ it('should show Project specific dropdown menu', function() {
+ var list;
+ addBodyAttributes('project');
+ mockProjectOptions();
+ widget.searchInput.focus();
+ list = widget.wrap.find('.dropdown-menu').find('ul');
+ return assertLinks(list, projectIssuesPath, projectMRsPath);
+ });
+ return it('should not show category related menu if there is text in the input', function() {
+ var link, list;
+ addBodyAttributes('project');
+ mockProjectOptions();
+ widget.searchInput.val('help');
+ widget.searchInput.focus();
+ list = widget.wrap.find('.dropdown-menu').find('ul');
+ link = "a[href='" + projectIssuesPath + "/?assignee_id=" + userId + "']";
+ return expect(list.find(link).length).toBe(0);
+ });
+});
diff --git a/spec/javascripts/search_autocomplete_spec.js.coffee b/spec/javascripts/search_autocomplete_spec.js.coffee
deleted file mode 100644
index 1c1faca3333..00000000000
--- a/spec/javascripts/search_autocomplete_spec.js.coffee
+++ /dev/null
@@ -1,149 +0,0 @@
-#= require gl_dropdown
-#= require search_autocomplete
-#= require jquery
-#= require lib/utils/common_utils
-#= require lib/utils/type_utility
-#= require fuzzaldrin-plus
-
-
-widget = null
-userId = 1
-window.gon or= {}
-window.gon.current_user_id = userId
-
-dashboardIssuesPath = '/dashboard/issues'
-dashboardMRsPath = '/dashboard/merge_requests'
-projectIssuesPath = '/gitlab-org/gitlab-ce/issues'
-projectMRsPath = '/gitlab-org/gitlab-ce/merge_requests'
-groupIssuesPath = '/groups/gitlab-org/issues'
-groupMRsPath = '/groups/gitlab-org/merge_requests'
-projectName = 'GitLab Community Edition'
-groupName = 'Gitlab Org'
-
-
-# Add required attributes to body before starting the test.
-# section would be dashboard|group|project
-addBodyAttributes = (section = 'dashboard') ->
-
- $body = $ 'body'
-
- $body.removeAttr 'data-page'
- $body.removeAttr 'data-project'
- $body.removeAttr 'data-group'
-
- switch section
- when 'dashboard'
- $body.data 'page', 'root:index'
- when 'group'
- $body.data 'page', 'groups:show'
- $body.data 'group', 'gitlab-org'
- when 'project'
- $body.data 'page', 'projects:show'
- $body.data 'project', 'gitlab-ce'
-
-
-# Mock `gl` object in window for dashboard specific page. App code will need it.
-mockDashboardOptions = ->
-
- window.gl or= {}
- window.gl.dashboardOptions =
- issuesPath: dashboardIssuesPath
- mrPath : dashboardMRsPath
-
-
-# Mock `gl` object in window for project specific page. App code will need it.
-mockProjectOptions = ->
-
- window.gl or= {}
- window.gl.projectOptions =
- 'gitlab-ce' :
- issuesPath : projectIssuesPath
- mrPath : projectMRsPath
- projectName : projectName
-
-
-mockGroupOptions = ->
-
- window.gl or= {}
- window.gl.groupOptions =
- 'gitlab-org' :
- issuesPath : groupIssuesPath
- mrPath : groupMRsPath
- projectName : groupName
-
-
-assertLinks = (list, issuesPath, mrsPath) ->
-
- issuesAssignedToMeLink = "#{issuesPath}/?assignee_id=#{userId}"
- issuesIHaveCreatedLink = "#{issuesPath}/?author_id=#{userId}"
- mrsAssignedToMeLink = "#{mrsPath}/?assignee_id=#{userId}"
- mrsIHaveCreatedLink = "#{mrsPath}/?author_id=#{userId}"
-
- a1 = "a[href='#{issuesAssignedToMeLink}']"
- a2 = "a[href='#{issuesIHaveCreatedLink}']"
- a3 = "a[href='#{mrsAssignedToMeLink}']"
- a4 = "a[href='#{mrsIHaveCreatedLink}']"
-
- expect(list.find(a1).length).toBe 1
- expect(list.find(a1).text()).toBe ' Issues assigned to me '
-
- expect(list.find(a2).length).toBe 1
- expect(list.find(a2).text()).toBe " Issues I've created "
-
- expect(list.find(a3).length).toBe 1
- expect(list.find(a3).text()).toBe ' Merge requests assigned to me '
-
- expect(list.find(a4).length).toBe 1
- expect(list.find(a4).text()).toBe " Merge requests I've created "
-
-
-describe 'Search autocomplete dropdown', ->
-
- fixture.preload 'search_autocomplete.html'
-
- beforeEach ->
-
- fixture.load 'search_autocomplete.html'
- widget = new SearchAutocomplete
-
-
- it 'should show Dashboard specific dropdown menu', ->
-
- addBodyAttributes()
- mockDashboardOptions()
- widget.searchInput.focus()
-
- list = widget.wrap.find('.dropdown-menu').find 'ul'
- assertLinks list, dashboardIssuesPath, dashboardMRsPath
-
-
- it 'should show Group specific dropdown menu', ->
-
- addBodyAttributes 'group'
- mockGroupOptions()
- widget.searchInput.focus()
-
- list = widget.wrap.find('.dropdown-menu').find 'ul'
- assertLinks list, groupIssuesPath, groupMRsPath
-
-
- it 'should show Project specific dropdown menu', ->
-
- addBodyAttributes 'project'
- mockProjectOptions()
- widget.searchInput.focus()
-
- list = widget.wrap.find('.dropdown-menu').find 'ul'
- assertLinks list, projectIssuesPath, projectMRsPath
-
-
- it 'should not show category related menu if there is text in the input', ->
-
- addBodyAttributes 'project'
- mockProjectOptions()
- widget.searchInput.val 'help'
- widget.searchInput.focus()
-
- list = widget.wrap.find('.dropdown-menu').find 'ul'
- link = "a[href='#{projectIssuesPath}/?assignee_id=#{userId}']"
- expect(list.find(link).length).toBe 0
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
new file mode 100644
index 00000000000..eb1e55dc5d2
--- /dev/null
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -0,0 +1,70 @@
+
+/*= require shortcuts_issuable */
+describe('ShortcutsIssuable', function() {
+ fixture.preload('issuable.html');
+ beforeEach(function() {
+ fixture.load('issuable.html');
+ return this.shortcut = new ShortcutsIssuable();
+ });
+ return describe('#replyWithSelectedText', function() {
+ var stubSelection;
+ stubSelection = function(text) {
+ return window.getSelection = function() {
+ return text;
+ };
+ };
+ beforeEach(function() {
+ return this.selector = 'form.js-main-target-form textarea#note_note';
+ });
+ describe('with empty selection', function() {
+ return it('does nothing', function() {
+ stubSelection('');
+ this.shortcut.replyWithSelectedText();
+ return expect($(this.selector).val()).toBe('');
+ });
+ });
+ describe('with any selection', function() {
+ beforeEach(function() {
+ return stubSelection('Selected text.');
+ });
+ it('leaves existing input intact', function() {
+ $(this.selector).val('This text was already here.');
+ expect($(this.selector).val()).toBe('This text was already here.');
+ this.shortcut.replyWithSelectedText();
+ return expect($(this.selector).val()).toBe("This text was already here.\n> Selected text.\n\n");
+ });
+ it('triggers `input`', function() {
+ var triggered;
+ triggered = false;
+ $(this.selector).on('input', function() {
+ return triggered = true;
+ });
+ this.shortcut.replyWithSelectedText();
+ return expect(triggered).toBe(true);
+ });
+ return it('triggers `focus`', function() {
+ var focused;
+ focused = false;
+ $(this.selector).on('focus', function() {
+ return focused = true;
+ });
+ this.shortcut.replyWithSelectedText();
+ return expect(focused).toBe(true);
+ });
+ });
+ describe('with a one-line selection', function() {
+ return it('quotes the selection', function() {
+ stubSelection('This text has been selected.');
+ this.shortcut.replyWithSelectedText();
+ return expect($(this.selector).val()).toBe("> This text has been selected.\n\n");
+ });
+ });
+ return describe('with a multi-line selection', function() {
+ return it('quotes the selected lines as a group', function() {
+ stubSelection("Selected line one.\n\nSelected line two.\nSelected line three.\n");
+ this.shortcut.replyWithSelectedText();
+ return expect($(this.selector).val()).toBe("> Selected line one.\n> Selected line two.\n> Selected line three.\n\n");
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/shortcuts_issuable_spec.js.coffee b/spec/javascripts/shortcuts_issuable_spec.js.coffee
deleted file mode 100644
index a01ad7140dd..00000000000
--- a/spec/javascripts/shortcuts_issuable_spec.js.coffee
+++ /dev/null
@@ -1,82 +0,0 @@
-#= require shortcuts_issuable
-
-describe 'ShortcutsIssuable', ->
- fixture.preload('issuable.html')
-
- beforeEach ->
- fixture.load('issuable.html')
- @shortcut = new ShortcutsIssuable()
-
- describe '#replyWithSelectedText', ->
- # Stub window.getSelection to return the provided String.
- stubSelection = (text) ->
- window.getSelection = -> text
-
- beforeEach ->
- @selector = 'form.js-main-target-form textarea#note_note'
-
- describe 'with empty selection', ->
- it 'does nothing', ->
- stubSelection('')
- @shortcut.replyWithSelectedText()
- expect($(@selector).val()).toBe('')
-
- describe 'with any selection', ->
- beforeEach ->
- stubSelection('Selected text.')
-
- it 'leaves existing input intact', ->
- $(@selector).val('This text was already here.')
- expect($(@selector).val()).toBe('This text was already here.')
-
- @shortcut.replyWithSelectedText()
- expect($(@selector).val()).
- toBe("This text was already here.\n> Selected text.\n\n")
-
- it 'triggers `input`', ->
- triggered = false
- $(@selector).on 'input', -> triggered = true
- @shortcut.replyWithSelectedText()
-
- expect(triggered).toBe(true)
-
- it 'triggers `focus`', ->
- focused = false
- $(@selector).on 'focus', -> focused = true
- @shortcut.replyWithSelectedText()
-
- expect(focused).toBe(true)
-
- describe 'with a one-line selection', ->
- it 'quotes the selection', ->
- stubSelection('This text has been selected.')
-
- @shortcut.replyWithSelectedText()
-
- expect($(@selector).val()).
- toBe("> This text has been selected.\n\n")
-
- describe 'with a multi-line selection', ->
- it 'quotes the selected lines as a group', ->
- stubSelection(
- """
- Selected line one.
-
- Selected line two.
- Selected line three.
-
- """
- )
-
- @shortcut.replyWithSelectedText()
-
- expect($(@selector).val()).
- toBe(
- """
- > Selected line one.
- > Selected line two.
- > Selected line three.
-
-
- """
- )
diff --git a/spec/javascripts/spec_helper.coffee b/spec/javascripts/spec_helper.coffee
deleted file mode 100644
index 90b02a6aec5..00000000000
--- a/spec/javascripts/spec_helper.coffee
+++ /dev/null
@@ -1,47 +0,0 @@
-# PhantomJS (Teaspoons default driver) doesn't have support for
-# Function.prototype.bind, which has caused confusion. Use this polyfill to
-# avoid the confusion.
-
-#= require support/bind-poly
-
-# You can require your own javascript files here. By default this will include
-# everything in application, however you may get better load performance if you
-# require the specific files that are being used in the spec that tests them.
-
-#= require jquery
-#= require jquery.turbolinks
-#= require bootstrap
-#= require underscore
-
-# Teaspoon includes some support files, but you can use anything from your own
-# support path too.
-
-# require support/jasmine-jquery-1.7.0
-# require support/jasmine-jquery-2.0.0
-#= require support/jasmine-jquery-2.1.0
-# require support/sinon
-# require support/your-support-file
-
-# Deferring execution
-
-# If you're using CommonJS, RequireJS or some other asynchronous library you can
-# defer execution. Call Teaspoon.execute() after everything has been loaded.
-# Simple example of a timeout:
-
-# Teaspoon.defer = true
-# setTimeout(Teaspoon.execute, 1000)
-
-# Matching files
-
-# By default Teaspoon will look for files that match
-# _spec.{js,js.coffee,.coffee}. Add a filename_spec.js file in your spec path
-# and it'll be included in the default suite automatically. If you want to
-# customize suites, check out the configuration in teaspoon_env.rb
-
-# Manifest
-
-# If you'd rather require your spec files manually (to control order for
-# instance) you can disable the suite matcher in the configuration and use this
-# file as a manifest.
-
-# For more information: http://github.com/modeset/teaspoon
diff --git a/spec/javascripts/spec_helper.js b/spec/javascripts/spec_helper.js
new file mode 100644
index 00000000000..c1b82ef4c21
--- /dev/null
+++ b/spec/javascripts/spec_helper.js
@@ -0,0 +1,13 @@
+
+/*= require support/bind-poly */
+
+/*= require jquery */
+
+/*= require jquery.turbolinks */
+
+/*= require bootstrap */
+
+/*= require underscore */
+
+/*= require support/jasmine-jquery-2.1.0 */
+
diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js
new file mode 100644
index 00000000000..89d9e467d73
--- /dev/null
+++ b/spec/javascripts/syntax_highlight_spec.js
@@ -0,0 +1,40 @@
+
+/*= require syntax_highlight */
+describe('Syntax Highlighter', function() {
+ var stubUserColorScheme;
+ stubUserColorScheme = function(value) {
+ if (window.gon == null) {
+ window.gon = {};
+ }
+ return window.gon.user_color_scheme = value;
+ };
+ describe('on a js-syntax-highlight element', function() {
+ beforeEach(function() {
+ return fixture.set('<div class="js-syntax-highlight"></div>');
+ });
+ return it('applies syntax highlighting', function() {
+ stubUserColorScheme('monokai');
+ $('.js-syntax-highlight').syntaxHighlight();
+ return expect($('.js-syntax-highlight')).toHaveClass('monokai');
+ });
+ });
+ return describe('on a parent element', function() {
+ beforeEach(function() {
+ return fixture.set("<div class=\"parent\">\n <div class=\"js-syntax-highlight\"></div>\n <div class=\"foo\"></div>\n <div class=\"js-syntax-highlight\"></div>\n</div>");
+ });
+ it('applies highlighting to all applicable children', function() {
+ stubUserColorScheme('monokai');
+ $('.parent').syntaxHighlight();
+ expect($('.parent, .foo')).not.toHaveClass('monokai');
+ return expect($('.monokai').length).toBe(2);
+ });
+ return it('prevents an infinite loop when no matches exist', function() {
+ var highlight;
+ fixture.set('<div></div>');
+ highlight = function() {
+ return $('div').syntaxHighlight();
+ };
+ return expect(highlight).not.toThrow();
+ });
+ });
+});
diff --git a/spec/javascripts/syntax_highlight_spec.js.coffee b/spec/javascripts/syntax_highlight_spec.js.coffee
deleted file mode 100644
index 6a73b6bf32c..00000000000
--- a/spec/javascripts/syntax_highlight_spec.js.coffee
+++ /dev/null
@@ -1,42 +0,0 @@
-#= require syntax_highlight
-
-describe 'Syntax Highlighter', ->
- stubUserColorScheme = (value) ->
- window.gon ?= {}
- window.gon.user_color_scheme = value
-
- describe 'on a js-syntax-highlight element', ->
- beforeEach ->
- fixture.set('<div class="js-syntax-highlight"></div>')
-
- it 'applies syntax highlighting', ->
- stubUserColorScheme('monokai')
-
- $('.js-syntax-highlight').syntaxHighlight()
-
- expect($('.js-syntax-highlight')).toHaveClass('monokai')
-
- describe 'on a parent element', ->
- beforeEach ->
- fixture.set """
- <div class="parent">
- <div class="js-syntax-highlight"></div>
- <div class="foo"></div>
- <div class="js-syntax-highlight"></div>
- </div>
- """
-
- it 'applies highlighting to all applicable children', ->
- stubUserColorScheme('monokai')
-
- $('.parent').syntaxHighlight()
-
- expect($('.parent, .foo')).not.toHaveClass('monokai')
- expect($('.monokai').length).toBe(2)
-
- it 'prevents an infinite loop when no matches exist', ->
- fixture.set('<div></div>')
-
- highlight = -> $('div').syntaxHighlight()
-
- expect(highlight).not.toThrow()
diff --git a/spec/javascripts/u2f/authenticate_spec.coffee b/spec/javascripts/u2f/authenticate_spec.coffee
deleted file mode 100644
index 8ffeda11704..00000000000
--- a/spec/javascripts/u2f/authenticate_spec.coffee
+++ /dev/null
@@ -1,51 +0,0 @@
-#= require u2f/authenticate
-#= require u2f/util
-#= require u2f/error
-#= require u2f
-#= require ./mock_u2f_device
-
-describe 'U2FAuthenticate', ->
- fixture.load('u2f/authenticate')
-
- beforeEach ->
- @u2fDevice = new MockU2FDevice
- @container = $("#js-authenticate-u2f")
- @component = new U2FAuthenticate(@container, {sign_requests: []}, "token")
- @component.start()
-
- it 'allows authenticating via a U2F device', ->
- setupButton = @container.find("#js-login-u2f-device")
- setupMessage = @container.find("p")
- expect(setupMessage.text()).toContain('Insert your security key')
- expect(setupButton.text()).toBe('Login Via U2F Device')
- setupButton.trigger('click')
-
- inProgressMessage = @container.find("p")
- expect(inProgressMessage.text()).toContain("Trying to communicate with your device")
-
- @u2fDevice.respondToAuthenticateRequest({deviceData: "this is data from the device"})
- authenticatedMessage = @container.find("p")
- deviceResponse = @container.find('#js-device-response')
- expect(authenticatedMessage.text()).toContain("Click this button to authenticate with the GitLab server")
- expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}')
-
- describe "errors", ->
- it "displays an error message", ->
- setupButton = @container.find("#js-login-u2f-device")
- setupButton.trigger('click')
- @u2fDevice.respondToAuthenticateRequest({errorCode: "error!"})
- errorMessage = @container.find("p")
- expect(errorMessage.text()).toContain("There was a problem communicating with your device")
-
- it "allows retrying authentication after an error", ->
- setupButton = @container.find("#js-login-u2f-device")
- setupButton.trigger('click')
- @u2fDevice.respondToAuthenticateRequest({errorCode: "error!"})
- retryButton = @container.find("#js-u2f-try-again")
- retryButton.trigger('click')
-
- setupButton = @container.find("#js-login-u2f-device")
- setupButton.trigger('click')
- @u2fDevice.respondToAuthenticateRequest({deviceData: "this is data from the device"})
- authenticatedMessage = @container.find("p")
- expect(authenticatedMessage.text()).toContain("Click this button to authenticate with the GitLab server")
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
new file mode 100644
index 00000000000..06d84a9001e
--- /dev/null
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -0,0 +1,67 @@
+
+/*= require u2f/authenticate */
+
+/*= require u2f/util */
+
+/*= require u2f/error */
+
+/*= require u2f */
+
+/*= require ./mock_u2f_device */
+describe('U2FAuthenticate', function() {
+ fixture.load('u2f/authenticate');
+ beforeEach(function() {
+ this.u2fDevice = new MockU2FDevice;
+ this.container = $("#js-authenticate-u2f");
+ this.component = new U2FAuthenticate(this.container, {
+ sign_requests: []
+ }, "token");
+ return this.component.start();
+ });
+ it('allows authenticating via a U2F device', function() {
+ var authenticatedMessage, deviceResponse, inProgressMessage, setupButton, setupMessage;
+ setupButton = this.container.find("#js-login-u2f-device");
+ setupMessage = this.container.find("p");
+ expect(setupMessage.text()).toContain('Insert your security key');
+ expect(setupButton.text()).toBe('Login Via U2F Device');
+ setupButton.trigger('click');
+ inProgressMessage = this.container.find("p");
+ expect(inProgressMessage.text()).toContain("Trying to communicate with your device");
+ this.u2fDevice.respondToAuthenticateRequest({
+ deviceData: "this is data from the device"
+ });
+ authenticatedMessage = this.container.find("p");
+ deviceResponse = this.container.find('#js-device-response');
+ expect(authenticatedMessage.text()).toContain("Click this button to authenticate with the GitLab server");
+ return expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}');
+ });
+ return describe("errors", function() {
+ it("displays an error message", function() {
+ var errorMessage, setupButton;
+ setupButton = this.container.find("#js-login-u2f-device");
+ setupButton.trigger('click');
+ this.u2fDevice.respondToAuthenticateRequest({
+ errorCode: "error!"
+ });
+ errorMessage = this.container.find("p");
+ return expect(errorMessage.text()).toContain("There was a problem communicating with your device");
+ });
+ return it("allows retrying authentication after an error", function() {
+ var authenticatedMessage, retryButton, setupButton;
+ setupButton = this.container.find("#js-login-u2f-device");
+ setupButton.trigger('click');
+ this.u2fDevice.respondToAuthenticateRequest({
+ errorCode: "error!"
+ });
+ retryButton = this.container.find("#js-u2f-try-again");
+ retryButton.trigger('click');
+ setupButton = this.container.find("#js-login-u2f-device");
+ setupButton.trigger('click');
+ this.u2fDevice.respondToAuthenticateRequest({
+ deviceData: "this is data from the device"
+ });
+ authenticatedMessage = this.container.find("p");
+ return expect(authenticatedMessage.text()).toContain("Click this button to authenticate with the GitLab server");
+ });
+ });
+});
diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js
new file mode 100644
index 00000000000..8198ff28b84
--- /dev/null
+++ b/spec/javascripts/u2f/mock_u2f_device.js
@@ -0,0 +1,30 @@
+var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+this.MockU2FDevice = (function() {
+ function MockU2FDevice() {
+ this.respondToAuthenticateRequest = bind(this.respondToAuthenticateRequest, this);
+ this.respondToRegisterRequest = bind(this.respondToRegisterRequest, this);
+ window.u2f || (window.u2f = {});
+ window.u2f.register = (function(_this) {
+ return function(appId, registerRequests, signRequests, callback) {
+ return _this.registerCallback = callback;
+ };
+ })(this);
+ window.u2f.sign = (function(_this) {
+ return function(appId, challenges, signRequests, callback) {
+ return _this.authenticateCallback = callback;
+ };
+ })(this);
+ }
+
+ MockU2FDevice.prototype.respondToRegisterRequest = function(params) {
+ return this.registerCallback(params);
+ };
+
+ MockU2FDevice.prototype.respondToAuthenticateRequest = function(params) {
+ return this.authenticateCallback(params);
+ };
+
+ return MockU2FDevice;
+
+})();
diff --git a/spec/javascripts/u2f/mock_u2f_device.js.coffee b/spec/javascripts/u2f/mock_u2f_device.js.coffee
deleted file mode 100644
index 97ed0e83a0e..00000000000
--- a/spec/javascripts/u2f/mock_u2f_device.js.coffee
+++ /dev/null
@@ -1,15 +0,0 @@
-class @MockU2FDevice
- constructor: () ->
- window.u2f ||= {}
-
- window.u2f.register = (appId, registerRequests, signRequests, callback) =>
- @registerCallback = callback
-
- window.u2f.sign = (appId, challenges, signRequests, callback) =>
- @authenticateCallback = callback
-
- respondToRegisterRequest: (params) =>
- @registerCallback(params)
-
- respondToAuthenticateRequest: (params) =>
- @authenticateCallback(params)
diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js
new file mode 100644
index 00000000000..57ba3db0375
--- /dev/null
+++ b/spec/javascripts/u2f/register_spec.js
@@ -0,0 +1,73 @@
+
+/*= require u2f/register */
+
+/*= require u2f/util */
+
+/*= require u2f/error */
+
+/*= require u2f */
+
+/*= require ./mock_u2f_device */
+describe('U2FRegister', function() {
+ fixture.load('u2f/register');
+ beforeEach(function() {
+ this.u2fDevice = new MockU2FDevice;
+ this.container = $("#js-register-u2f");
+ this.component = new U2FRegister(this.container, $("#js-register-u2f-templates"), {}, "token");
+ return this.component.start();
+ });
+ it('allows registering a U2F device', function() {
+ var deviceResponse, inProgressMessage, registeredMessage, setupButton;
+ setupButton = this.container.find("#js-setup-u2f-device");
+ expect(setupButton.text()).toBe('Setup New U2F Device');
+ setupButton.trigger('click');
+ inProgressMessage = this.container.children("p");
+ expect(inProgressMessage.text()).toContain("Trying to communicate with your device");
+ this.u2fDevice.respondToRegisterRequest({
+ deviceData: "this is data from the device"
+ });
+ registeredMessage = this.container.find('p');
+ deviceResponse = this.container.find('#js-device-response');
+ expect(registeredMessage.text()).toContain("Your device was successfully set up!");
+ return expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}');
+ });
+ return describe("errors", function() {
+ it("doesn't allow the same device to be registered twice (for the same user", function() {
+ var errorMessage, setupButton;
+ setupButton = this.container.find("#js-setup-u2f-device");
+ setupButton.trigger('click');
+ this.u2fDevice.respondToRegisterRequest({
+ errorCode: 4
+ });
+ errorMessage = this.container.find("p");
+ return expect(errorMessage.text()).toContain("already been registered with us");
+ });
+ it("displays an error message for other errors", function() {
+ var errorMessage, setupButton;
+ setupButton = this.container.find("#js-setup-u2f-device");
+ setupButton.trigger('click');
+ this.u2fDevice.respondToRegisterRequest({
+ errorCode: "error!"
+ });
+ errorMessage = this.container.find("p");
+ return expect(errorMessage.text()).toContain("There was a problem communicating with your device");
+ });
+ return it("allows retrying registration after an error", function() {
+ var registeredMessage, retryButton, setupButton;
+ setupButton = this.container.find("#js-setup-u2f-device");
+ setupButton.trigger('click');
+ this.u2fDevice.respondToRegisterRequest({
+ errorCode: "error!"
+ });
+ retryButton = this.container.find("#U2FTryAgain");
+ retryButton.trigger('click');
+ setupButton = this.container.find("#js-setup-u2f-device");
+ setupButton.trigger('click');
+ this.u2fDevice.respondToRegisterRequest({
+ deviceData: "this is data from the device"
+ });
+ registeredMessage = this.container.find("p");
+ return expect(registeredMessage.text()).toContain("Your device was successfully set up!");
+ });
+ });
+});
diff --git a/spec/javascripts/u2f/register_spec.js.coffee b/spec/javascripts/u2f/register_spec.js.coffee
deleted file mode 100644
index 87dc769792b..00000000000
--- a/spec/javascripts/u2f/register_spec.js.coffee
+++ /dev/null
@@ -1,56 +0,0 @@
-#= require u2f/register
-#= require u2f/util
-#= require u2f/error
-#= require u2f
-#= require ./mock_u2f_device
-
-describe 'U2FRegister', ->
- fixture.load('u2f/register')
-
- beforeEach ->
- @u2fDevice = new MockU2FDevice
- @container = $("#js-register-u2f")
- @component = new U2FRegister(@container, $("#js-register-u2f-templates"), {}, "token")
- @component.start()
-
- it 'allows registering a U2F device', ->
- setupButton = @container.find("#js-setup-u2f-device")
- expect(setupButton.text()).toBe('Setup New U2F Device')
- setupButton.trigger('click')
-
- inProgressMessage = @container.children("p")
- expect(inProgressMessage.text()).toContain("Trying to communicate with your device")
-
- @u2fDevice.respondToRegisterRequest({deviceData: "this is data from the device"})
- registeredMessage = @container.find('p')
- deviceResponse = @container.find('#js-device-response')
- expect(registeredMessage.text()).toContain("Your device was successfully set up!")
- expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}')
-
- describe "errors", ->
- it "doesn't allow the same device to be registered twice (for the same user", ->
- setupButton = @container.find("#js-setup-u2f-device")
- setupButton.trigger('click')
- @u2fDevice.respondToRegisterRequest({errorCode: 4})
- errorMessage = @container.find("p")
- expect(errorMessage.text()).toContain("already been registered with us")
-
- it "displays an error message for other errors", ->
- setupButton = @container.find("#js-setup-u2f-device")
- setupButton.trigger('click')
- @u2fDevice.respondToRegisterRequest({errorCode: "error!"})
- errorMessage = @container.find("p")
- expect(errorMessage.text()).toContain("There was a problem communicating with your device")
-
- it "allows retrying registration after an error", ->
- setupButton = @container.find("#js-setup-u2f-device")
- setupButton.trigger('click')
- @u2fDevice.respondToRegisterRequest({errorCode: "error!"})
- retryButton = @container.find("#U2FTryAgain")
- retryButton.trigger('click')
-
- setupButton = @container.find("#js-setup-u2f-device")
- setupButton.trigger('click')
- @u2fDevice.respondToRegisterRequest({deviceData: "this is data from the device"})
- registeredMessage = @container.find("p")
- expect(registeredMessage.text()).toContain("Your device was successfully set up!")
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
new file mode 100644
index 00000000000..18ef9970fa2
--- /dev/null
+++ b/spec/javascripts/zen_mode_spec.js
@@ -0,0 +1,69 @@
+
+/*= require zen_mode */
+var enterZen, escapeKeydown, exitZen;
+
+describe('ZenMode', function() {
+ fixture.preload('zen_mode.html');
+ beforeEach(function() {
+ fixture.load('zen_mode.html');
+ spyOn(Dropzone, 'forElement').and.callFake(function() {
+ return {
+ enable: function() {
+ return true;
+ }
+ };
+ });
+ this.zen = new ZenMode();
+ return this.zen.scroll_position = 456;
+ });
+ describe('on enter', function() {
+ it('pauses Mousetrap', function() {
+ spyOn(Mousetrap, 'pause');
+ enterZen();
+ return expect(Mousetrap.pause).toHaveBeenCalled();
+ });
+ return it('removes textarea styling', function() {
+ $('textarea').attr('style', 'height: 400px');
+ enterZen();
+ return expect('textarea').not.toHaveAttr('style');
+ });
+ });
+ describe('in use', function() {
+ beforeEach(function() {
+ return enterZen();
+ });
+ return it('exits on Escape', function() {
+ escapeKeydown();
+ return expect($('.zen-backdrop')).not.toHaveClass('fullscreen');
+ });
+ });
+ return describe('on exit', function() {
+ beforeEach(function() {
+ return enterZen();
+ });
+ it('unpauses Mousetrap', function() {
+ spyOn(Mousetrap, 'unpause');
+ exitZen();
+ return expect(Mousetrap.unpause).toHaveBeenCalled();
+ });
+ return it('restores the scroll position', function() {
+ spyOn(this.zen, 'scrollTo');
+ exitZen();
+ return expect(this.zen.scrollTo).toHaveBeenCalled();
+ });
+ });
+});
+
+enterZen = function() {
+ return $('a.js-zen-enter').click();
+};
+
+exitZen = function() {
+ return $('a.js-zen-leave').click();
+};
+
+escapeKeydown = function() {
+ return $('textarea').trigger($.Event('keydown', {
+ keyCode: 27
+ }));
+};
diff --git a/spec/javascripts/zen_mode_spec.js.coffee b/spec/javascripts/zen_mode_spec.js.coffee
deleted file mode 100644
index b790fce01ed..00000000000
--- a/spec/javascripts/zen_mode_spec.js.coffee
+++ /dev/null
@@ -1,51 +0,0 @@
-#= require zen_mode
-
-describe 'ZenMode', ->
- fixture.preload('zen_mode.html')
-
- beforeEach ->
- fixture.load('zen_mode.html')
-
- # Stub Dropzone.forElement(...).enable()
- spyOn(Dropzone, 'forElement').and.callFake ->
- enable: -> true
-
- @zen = new ZenMode()
-
- # Set this manually because we can't actually scroll the window
- @zen.scroll_position = 456
-
- describe 'on enter', ->
- it 'pauses Mousetrap', ->
- spyOn(Mousetrap, 'pause')
- enterZen()
- expect(Mousetrap.pause).toHaveBeenCalled()
-
- it 'removes textarea styling', ->
- $('textarea').attr('style', 'height: 400px')
- enterZen()
- expect('textarea').not.toHaveAttr('style')
-
- describe 'in use', ->
- beforeEach -> enterZen()
-
- it 'exits on Escape', ->
- escapeKeydown()
- expect($('.zen-backdrop')).not.toHaveClass('fullscreen')
-
- describe 'on exit', ->
- beforeEach -> enterZen()
-
- it 'unpauses Mousetrap', ->
- spyOn(Mousetrap, 'unpause')
- exitZen()
- expect(Mousetrap.unpause).toHaveBeenCalled()
-
- it 'restores the scroll position', ->
- spyOn(@zen, 'scrollTo')
- exitZen()
- expect(@zen.scrollTo).toHaveBeenCalled()
-
-enterZen = -> $('a.js-zen-enter').click() # Ohmmmmmmm
-exitZen = -> $('a.js-zen-leave').click()
-escapeKeydown = -> $('textarea').trigger($.Event('keydown', {keyCode: 27}))
diff --git a/vendor/assets/javascripts/task_list.js b/vendor/assets/javascripts/task_list.js
new file mode 100644
index 00000000000..99d165c3270
--- /dev/null
+++ b/vendor/assets/javascripts/task_list.js
@@ -0,0 +1,112 @@
+
+/*= provides tasklist:enabled */
+
+/*= provides tasklist:disabled */
+
+/*= provides tasklist:change */
+
+/*= provides tasklist:changed */
+var codeFencesPattern, complete, completePattern, disableTaskList, disableTaskLists, enableTaskList, enableTaskLists, escapePattern, incomplete, incompletePattern, itemPattern, itemsInParasPattern, updateTaskList, updateTaskListItem,
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+incomplete = "[ ]";
+
+complete = "[x]";
+
+escapePattern = function(str) {
+ return str.replace(/([\[\]])/g, "\\$1").replace(/\s/, "\\s").replace("x", "[xX]");
+};
+
+incompletePattern = RegExp("" + (escapePattern(incomplete)));
+
+completePattern = RegExp("" + (escapePattern(complete)));
+
+itemPattern = RegExp("^(?:\\s*(?:>\\s*)*(?:[-+*]|(?:\\d+\\.)))\\s*(" + (escapePattern(complete)) + "|" + (escapePattern(incomplete)) + ")\\s+(?!\\(.*?\\))(?=(?:\\[.*?\\]\\s*(?:\\[.*?\\]|\\(.*?\\))\\s*)*(?:[^\\[]|$))");
+
+codeFencesPattern = /^`{3}(?:\s*\w+)?[\S\s].*[\S\s]^`{3}$/mg;
+
+itemsInParasPattern = RegExp("^(" + (escapePattern(complete)) + "|" + (escapePattern(incomplete)) + ").+$", "g");
+
+updateTaskListItem = function(source, itemIndex, checked) {
+ var clean, index, line, result;
+ clean = source.replace(/\r/g, '').replace(codeFencesPattern, '').replace(itemsInParasPattern, '').split("\n");
+ index = 0;
+ result = (function() {
+ var i, len, ref, results;
+ ref = source.split("\n");
+ results = [];
+ for (i = 0, len = ref.length; i < len; i++) {
+ line = ref[i];
+ if (indexOf.call(clean, line) >= 0 && line.match(itemPattern)) {
+ index += 1;
+ if (index === itemIndex) {
+ line = checked ? line.replace(incompletePattern, complete) : line.replace(completePattern, incomplete);
+ }
+ }
+ results.push(line);
+ }
+ return results;
+ })();
+ return result.join("\n");
+};
+
+updateTaskList = function($item) {
+ var $container, $field, checked, event, index;
+ $container = $item.closest('.js-task-list-container');
+ $field = $container.find('.js-task-list-field');
+ index = 1 + $container.find('.task-list-item-checkbox').index($item);
+ checked = $item.prop('checked');
+ event = $.Event('tasklist:change');
+ $field.trigger(event, [index, checked]);
+ if (!event.isDefaultPrevented()) {
+ $field.val(updateTaskListItem($field.val(), index, checked));
+ $field.trigger('change');
+ return $field.trigger('tasklist:changed', [index, checked]);
+ }
+};
+
+$(document).on('change', '.task-list-item-checkbox', function() {
+ return updateTaskList($(this));
+});
+
+enableTaskList = function($container) {
+ if ($container.find('.js-task-list-field').length > 0) {
+ $container.find('.task-list-item').addClass('enabled').find('.task-list-item-checkbox').attr('disabled', null);
+ return $container.addClass('is-task-list-enabled').trigger('tasklist:enabled');
+ }
+};
+
+enableTaskLists = function($containers) {
+ var container, i, len, results;
+ results = [];
+ for (i = 0, len = $containers.length; i < len; i++) {
+ container = $containers[i];
+ results.push(enableTaskList($(container)));
+ }
+ return results;
+};
+
+disableTaskList = function($container) {
+ $container.find('.task-list-item').removeClass('enabled').find('.task-list-item-checkbox').attr('disabled', 'disabled');
+ return $container.removeClass('is-task-list-enabled').trigger('tasklist:disabled');
+};
+
+disableTaskLists = function($containers) {
+ var container, i, len, results;
+ results = [];
+ for (i = 0, len = $containers.length; i < len; i++) {
+ container = $containers[i];
+ results.push(disableTaskList($(container)));
+ }
+ return results;
+};
+
+$.fn.taskList = function(method) {
+ var $container, methods;
+ $container = $(this).closest('.js-task-list-container');
+ methods = {
+ enable: enableTaskLists,
+ disable: disableTaskLists
+ };
+ return methods[method || 'enable']($container);
+};
diff --git a/vendor/assets/javascripts/task_list.js.coffee b/vendor/assets/javascripts/task_list.js.coffee
deleted file mode 100644
index 584751af8ea..00000000000
--- a/vendor/assets/javascripts/task_list.js.coffee
+++ /dev/null
@@ -1,258 +0,0 @@
-# The MIT License (MIT)
-#
-# Copyright (c) 2014 GitHub, Inc.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-# TaskList Behavior
-#
-#= provides tasklist:enabled
-#= provides tasklist:disabled
-#= provides tasklist:change
-#= provides tasklist:changed
-#
-#
-# Enables Task List update behavior.
-#
-# ### Example Markup
-#
-# <div class="js-task-list-container">
-# <ul class="task-list">
-# <li class="task-list-item">
-# <input type="checkbox" class="js-task-list-item-checkbox" disabled />
-# text
-# </li>
-# </ul>
-# <form>
-# <textarea class="js-task-list-field">- [ ] text</textarea>
-# </form>
-# </div>
-#
-# ### Specification
-#
-# TaskLists MUST be contained in a `(div).js-task-list-container`.
-#
-# TaskList Items SHOULD be an a list (`UL`/`OL`) element.
-#
-# Task list items MUST match `(input).task-list-item-checkbox` and MUST be
-# `disabled` by default.
-#
-# TaskLists MUST have a `(textarea).js-task-list-field` form element whose
-# `value` attribute is the source (Markdown) to be udpated. The source MUST
-# follow the syntax guidelines.
-#
-# TaskList updates trigger `tasklist:change` events. If the change is
-# successful, `tasklist:changed` is fired. The change can be canceled.
-#
-# jQuery is required.
-#
-# ### Methods
-#
-# `.taskList('enable')` or `.taskList()`
-#
-# Enables TaskList updates for the container.
-#
-# `.taskList('disable')`
-#
-# Disables TaskList updates for the container.
-#
-## ### Events
-#
-# `tasklist:enabled`
-#
-# Fired when the TaskList is enabled.
-#
-# * **Synchronicity** Sync
-# * **Bubbles** Yes
-# * **Cancelable** No
-# * **Target** `.js-task-list-container`
-#
-# `tasklist:disabled`
-#
-# Fired when the TaskList is disabled.
-#
-# * **Synchronicity** Sync
-# * **Bubbles** Yes
-# * **Cancelable** No
-# * **Target** `.js-task-list-container`
-#
-# `tasklist:change`
-#
-# Fired before the TaskList item change takes affect.
-#
-# * **Synchronicity** Sync
-# * **Bubbles** Yes
-# * **Cancelable** Yes
-# * **Target** `.js-task-list-field`
-#
-# `tasklist:changed`
-#
-# Fired once the TaskList item change has taken affect.
-#
-# * **Synchronicity** Sync
-# * **Bubbles** Yes
-# * **Cancelable** No
-# * **Target** `.js-task-list-field`
-#
-# ### NOTE
-#
-# Task list checkboxes are rendered as disabled by default because rendered
-# user content is cached without regard for the viewer.
-
-incomplete = "[ ]"
-complete = "[x]"
-
-# Escapes the String for regular expression matching.
-escapePattern = (str) ->
- str.
- replace(/([\[\]])/g, "\\$1"). # escape square brackets
- replace(/\s/, "\\s"). # match all white space
- replace("x", "[xX]") # match all cases
-
-incompletePattern = ///
- #{escapePattern(incomplete)}
-///
-completePattern = ///
- #{escapePattern(complete)}
-///
-
-# Pattern used to identify all task list items.
-# Useful when you need iterate over all items.
-itemPattern = ///
- ^
- (?: # prefix, consisting of
- \s* # optional leading whitespace
- (?:>\s*)* # zero or more blockquotes
- (?:[-+*]|(?:\d+\.)) # list item indicator
- )
- \s* # optional whitespace prefix
- ( # checkbox
- #{escapePattern(complete)}|
- #{escapePattern(incomplete)}
- )
- \s+ # is followed by whitespace
- (?!
- \(.*?\) # is not part of a [foo](url) link
- )
- (?= # and is followed by zero or more links
- (?:\[.*?\]\s*(?:\[.*?\]|\(.*?\))\s*)*
- (?:[^\[]|$) # and either a non-link or the end of the string
- )
-///
-
-# Used to filter out code fences from the source for comparison only.
-# http://rubular.com/r/x5EwZVrloI
-# Modified slightly due to issues with JS
-codeFencesPattern = ///
- ^`{3} # ```
- (?:\s*\w+)? # followed by optional language
- [\S\s] # whitespace
- .* # code
- [\S\s] # whitespace
- ^`{3}$ # ```
-///mg
-
-# Used to filter out potential mismatches (items not in lists).
-# http://rubular.com/r/OInl6CiePy
-itemsInParasPattern = ///
- ^
- (
- #{escapePattern(complete)}|
- #{escapePattern(incomplete)}
- )
- .+
- $
-///g
-
-# Given the source text, updates the appropriate task list item to match the
-# given checked value.
-#
-# Returns the updated String text.
-updateTaskListItem = (source, itemIndex, checked) ->
- clean = source.replace(/\r/g, '').replace(codeFencesPattern, '').
- replace(itemsInParasPattern, '').split("\n")
- index = 0
- result = for line in source.split("\n")
- if line in clean && line.match(itemPattern)
- index += 1
- if index == itemIndex
- line =
- if checked
- line.replace(incompletePattern, complete)
- else
- line.replace(completePattern, incomplete)
- line
- result.join("\n")
-
-# Updates the $field value to reflect the state of $item.
-# Triggers the `tasklist:change` event before the value has changed, and fires
-# a `tasklist:changed` event once the value has changed.
-updateTaskList = ($item) ->
- $container = $item.closest '.js-task-list-container'
- $field = $container.find '.js-task-list-field'
- index = 1 + $container.find('.task-list-item-checkbox').index($item)
- checked = $item.prop 'checked'
-
- event = $.Event 'tasklist:change'
- $field.trigger event, [index, checked]
-
- unless event.isDefaultPrevented()
- $field.val updateTaskListItem($field.val(), index, checked)
- $field.trigger 'change'
- $field.trigger 'tasklist:changed', [index, checked]
-
-# When the task list item checkbox is updated, submit the change
-$(document).on 'change', '.task-list-item-checkbox', ->
- updateTaskList $(this)
-
-# Enables TaskList item changes.
-enableTaskList = ($container) ->
- if $container.find('.js-task-list-field').length > 0
- $container.
- find('.task-list-item').addClass('enabled').
- find('.task-list-item-checkbox').attr('disabled', null)
- $container.addClass('is-task-list-enabled').
- trigger 'tasklist:enabled'
-
-# Enables a collection of TaskList containers.
-enableTaskLists = ($containers) ->
- for container in $containers
- enableTaskList $(container)
-
-# Disable TaskList item changes.
-disableTaskList = ($container) ->
- $container.
- find('.task-list-item').removeClass('enabled').
- find('.task-list-item-checkbox').attr('disabled', 'disabled')
- $container.removeClass('is-task-list-enabled').
- trigger 'tasklist:disabled'
-
-# Disables a collection of TaskList containers.
-disableTaskLists = ($containers) ->
- for container in $containers
- disableTaskList $(container)
-
-$.fn.taskList = (method) ->
- $container = $(this).closest('.js-task-list-container')
-
- methods =
- enable: enableTaskLists
- disable: disableTaskLists
-
- methods[method || 'enable']($container)