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

index.js 112 kB

Use AJAX for notifications table (#10961) * Use AJAX for notifications table Signed-off-by: Andrew Thornton <art27@cantab.net> * move to separate js Signed-off-by: Andrew Thornton <art27@cantab.net> * placate golangci-lint Signed-off-by: Andrew Thornton <art27@cantab.net> * Add autoupdating notification count Signed-off-by: Andrew Thornton <art27@cantab.net> * Fix wipeall Signed-off-by: Andrew Thornton <art27@cantab.net> * placate tests Signed-off-by: Andrew Thornton <art27@cantab.net> * Try hidden Signed-off-by: Andrew Thornton <art27@cantab.net> * Try hide and hidden Signed-off-by: Andrew Thornton <art27@cantab.net> * More auto-update improvements Only run checker on pages that have a count Change starting checker to 10s with a back-off to 60s if there is no change Signed-off-by: Andrew Thornton <art27@cantab.net> * string comparison! Signed-off-by: Andrew Thornton <art27@cantab.net> * as per @silverwind Signed-off-by: Andrew Thornton <art27@cantab.net> * add configurability as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> * Add documentation as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> * Use CSRF header not query Signed-off-by: Andrew Thornton <art27@cantab.net> * Further JS improvements Fix @etzelia update notification table request Fix @silverwind comments Co-Authored-By: silverwind <me@silverwind.io> Signed-off-by: Andrew Thornton <art27@cantab.net> * Simplify the notification count fns Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: silverwind <me@silverwind.io>
5 years ago
Add Organization Wide Labels (#10814) * Add organization wide labels Implement organization wide labels similar to organization wide webhooks. This lets you create individual labels for organizations that can be used for all repos under that organization (so being able to reuse the same label across multiple repos). This makes it possible for small organizations with many repos to use labels effectively. Fixes #7406 * Add migration * remove comments * fix tests * Update options/locale/locale_en-US.ini Removed unused translation string * show org labels in issue search label filter * Use more clear var name * rename migration after merge from master * comment typo * update migration again after rebase with master * check for orgID <=0 per guillep2k review * fmt * Apply suggestions from code review Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * remove unused code * Make sure RepoID is 0 when searching orgID per code review * more changes/code review requests * More descriptive translation var per code review * func description/delete comment when issue label deleted instead of hiding it * remove comment * only use issues in that repo when calculating number of open issues for org label on repo label page * Add integration test for IssuesSearch API with labels * remove unused function * Update models/issue_label.go Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Use subquery in GetLabelIDsInReposByNames * Fix tests to use correct orgID * fix more tests * IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well * update comment for clarity * Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition * Don't sort repos by date in IssuesSearch API After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45 Returns different results for MySQL than other engines. However, the similar query: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30 Returns the same results. This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function. * linter is back! * code review * remove now unused option * Fix newline at end of files * more unused code * update to master * check for matching ids before query * Update models/issue_label.go Co-Authored-By: 6543 <6543@obermui.de> * Update models/issue_label.go * update comments * Update routers/org/setting.go Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: 6543 <6543@obermui.de>
5 years ago
Add Organization Wide Labels (#10814) * Add organization wide labels Implement organization wide labels similar to organization wide webhooks. This lets you create individual labels for organizations that can be used for all repos under that organization (so being able to reuse the same label across multiple repos). This makes it possible for small organizations with many repos to use labels effectively. Fixes #7406 * Add migration * remove comments * fix tests * Update options/locale/locale_en-US.ini Removed unused translation string * show org labels in issue search label filter * Use more clear var name * rename migration after merge from master * comment typo * update migration again after rebase with master * check for orgID <=0 per guillep2k review * fmt * Apply suggestions from code review Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * remove unused code * Make sure RepoID is 0 when searching orgID per code review * more changes/code review requests * More descriptive translation var per code review * func description/delete comment when issue label deleted instead of hiding it * remove comment * only use issues in that repo when calculating number of open issues for org label on repo label page * Add integration test for IssuesSearch API with labels * remove unused function * Update models/issue_label.go Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Use subquery in GetLabelIDsInReposByNames * Fix tests to use correct orgID * fix more tests * IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well * update comment for clarity * Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition * Don't sort repos by date in IssuesSearch API After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45 Returns different results for MySQL than other engines. However, the similar query: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30 Returns the same results. This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function. * linter is back! * code review * remove now unused option * Fix newline at end of files * more unused code * update to master * check for matching ids before query * Update models/issue_label.go Co-Authored-By: 6543 <6543@obermui.de> * Update models/issue_label.go * update comments * Update routers/org/setting.go Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: 6543 <6543@obermui.de>
5 years ago
Add Organization Wide Labels (#10814) * Add organization wide labels Implement organization wide labels similar to organization wide webhooks. This lets you create individual labels for organizations that can be used for all repos under that organization (so being able to reuse the same label across multiple repos). This makes it possible for small organizations with many repos to use labels effectively. Fixes #7406 * Add migration * remove comments * fix tests * Update options/locale/locale_en-US.ini Removed unused translation string * show org labels in issue search label filter * Use more clear var name * rename migration after merge from master * comment typo * update migration again after rebase with master * check for orgID <=0 per guillep2k review * fmt * Apply suggestions from code review Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * remove unused code * Make sure RepoID is 0 when searching orgID per code review * more changes/code review requests * More descriptive translation var per code review * func description/delete comment when issue label deleted instead of hiding it * remove comment * only use issues in that repo when calculating number of open issues for org label on repo label page * Add integration test for IssuesSearch API with labels * remove unused function * Update models/issue_label.go Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Use subquery in GetLabelIDsInReposByNames * Fix tests to use correct orgID * fix more tests * IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well * update comment for clarity * Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition * Don't sort repos by date in IssuesSearch API After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45 Returns different results for MySQL than other engines. However, the similar query: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30 Returns the same results. This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function. * linter is back! * code review * remove now unused option * Fix newline at end of files * more unused code * update to master * check for matching ids before query * Update models/issue_label.go Co-Authored-By: 6543 <6543@obermui.de> * Update models/issue_label.go * update comments * Update routers/org/setting.go Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: 6543 <6543@obermui.de>
5 years ago
Add Organization Wide Labels (#10814) * Add organization wide labels Implement organization wide labels similar to organization wide webhooks. This lets you create individual labels for organizations that can be used for all repos under that organization (so being able to reuse the same label across multiple repos). This makes it possible for small organizations with many repos to use labels effectively. Fixes #7406 * Add migration * remove comments * fix tests * Update options/locale/locale_en-US.ini Removed unused translation string * show org labels in issue search label filter * Use more clear var name * rename migration after merge from master * comment typo * update migration again after rebase with master * check for orgID <=0 per guillep2k review * fmt * Apply suggestions from code review Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * remove unused code * Make sure RepoID is 0 when searching orgID per code review * more changes/code review requests * More descriptive translation var per code review * func description/delete comment when issue label deleted instead of hiding it * remove comment * only use issues in that repo when calculating number of open issues for org label on repo label page * Add integration test for IssuesSearch API with labels * remove unused function * Update models/issue_label.go Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Use subquery in GetLabelIDsInReposByNames * Fix tests to use correct orgID * fix more tests * IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well * update comment for clarity * Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition * Don't sort repos by date in IssuesSearch API After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45 Returns different results for MySQL than other engines. However, the similar query: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30 Returns the same results. This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function. * linter is back! * code review * remove now unused option * Fix newline at end of files * more unused code * update to master * check for matching ids before query * Update models/issue_label.go Co-Authored-By: 6543 <6543@obermui.de> * Update models/issue_label.go * update comments * Update routers/org/setting.go Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: 6543 <6543@obermui.de>
5 years ago
Add Organization Wide Labels (#10814) * Add organization wide labels Implement organization wide labels similar to organization wide webhooks. This lets you create individual labels for organizations that can be used for all repos under that organization (so being able to reuse the same label across multiple repos). This makes it possible for small organizations with many repos to use labels effectively. Fixes #7406 * Add migration * remove comments * fix tests * Update options/locale/locale_en-US.ini Removed unused translation string * show org labels in issue search label filter * Use more clear var name * rename migration after merge from master * comment typo * update migration again after rebase with master * check for orgID <=0 per guillep2k review * fmt * Apply suggestions from code review Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * remove unused code * Make sure RepoID is 0 when searching orgID per code review * more changes/code review requests * More descriptive translation var per code review * func description/delete comment when issue label deleted instead of hiding it * remove comment * only use issues in that repo when calculating number of open issues for org label on repo label page * Add integration test for IssuesSearch API with labels * remove unused function * Update models/issue_label.go Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Use subquery in GetLabelIDsInReposByNames * Fix tests to use correct orgID * fix more tests * IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well * update comment for clarity * Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition * Don't sort repos by date in IssuesSearch API After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45 Returns different results for MySQL than other engines. However, the similar query: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30 Returns the same results. This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function. * linter is back! * code review * remove now unused option * Fix newline at end of files * more unused code * update to master * check for matching ids before query * Update models/issue_label.go Co-Authored-By: 6543 <6543@obermui.de> * Update models/issue_label.go * update comments * Update routers/org/setting.go Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: 6543 <6543@obermui.de>
5 years ago
Add Organization Wide Labels (#10814) * Add organization wide labels Implement organization wide labels similar to organization wide webhooks. This lets you create individual labels for organizations that can be used for all repos under that organization (so being able to reuse the same label across multiple repos). This makes it possible for small organizations with many repos to use labels effectively. Fixes #7406 * Add migration * remove comments * fix tests * Update options/locale/locale_en-US.ini Removed unused translation string * show org labels in issue search label filter * Use more clear var name * rename migration after merge from master * comment typo * update migration again after rebase with master * check for orgID <=0 per guillep2k review * fmt * Apply suggestions from code review Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * remove unused code * Make sure RepoID is 0 when searching orgID per code review * more changes/code review requests * More descriptive translation var per code review * func description/delete comment when issue label deleted instead of hiding it * remove comment * only use issues in that repo when calculating number of open issues for org label on repo label page * Add integration test for IssuesSearch API with labels * remove unused function * Update models/issue_label.go Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Use subquery in GetLabelIDsInReposByNames * Fix tests to use correct orgID * fix more tests * IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well * update comment for clarity * Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition * Don't sort repos by date in IssuesSearch API After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45 Returns different results for MySQL than other engines. However, the similar query: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30 Returns the same results. This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function. * linter is back! * code review * remove now unused option * Fix newline at end of files * more unused code * update to master * check for matching ids before query * Update models/issue_label.go Co-Authored-By: 6543 <6543@obermui.de> * Update models/issue_label.go * update comments * Update routers/org/setting.go Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: 6543 <6543@obermui.de>
5 years ago
Add Octicon SVG spritemap (#10107) * Add octicon SVG sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Static prefix Signed-off-by: jolheiser <john.olheiser@gmail.com> * SVG for all repo icons Signed-off-by: jolheiser <john.olheiser@gmail.com> * make vendor Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap out octicons Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move octicons to top of less imports Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix JS Signed-off-by: jolheiser <john.olheiser@gmail.com> * Definitely not a search/replace Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed regex Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move to more generic calls and webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * make svg -> make webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg-sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed a test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg from makefile Signed-off-by: jolheiser <john.olheiser@gmail.com> * Suggestions Signed-off-by: jolheiser <john.olheiser@gmail.com> * Attempt to fix test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert timetracking test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap .octicon for .svg in less Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add aria-hidden Signed-off-by: jolheiser <john.olheiser@gmail.com> * Replace mega-octicon Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix webpack globbing on Windows Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert Co-Authored-By: silverwind <me@silverwind.io> * Fix octions from upstream Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix Vue and missed JS function Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add JS helper and PWA Signed-off-by: jolheiser <john.olheiser@gmail.com> * Preload SVG Signed-off-by: jolheiser <john.olheiser@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: techknowlogick <matti@mdranta.net>
5 years ago
Add Octicon SVG spritemap (#10107) * Add octicon SVG sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Static prefix Signed-off-by: jolheiser <john.olheiser@gmail.com> * SVG for all repo icons Signed-off-by: jolheiser <john.olheiser@gmail.com> * make vendor Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap out octicons Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move octicons to top of less imports Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix JS Signed-off-by: jolheiser <john.olheiser@gmail.com> * Definitely not a search/replace Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed regex Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move to more generic calls and webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * make svg -> make webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg-sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed a test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg from makefile Signed-off-by: jolheiser <john.olheiser@gmail.com> * Suggestions Signed-off-by: jolheiser <john.olheiser@gmail.com> * Attempt to fix test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert timetracking test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap .octicon for .svg in less Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add aria-hidden Signed-off-by: jolheiser <john.olheiser@gmail.com> * Replace mega-octicon Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix webpack globbing on Windows Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert Co-Authored-By: silverwind <me@silverwind.io> * Fix octions from upstream Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix Vue and missed JS function Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add JS helper and PWA Signed-off-by: jolheiser <john.olheiser@gmail.com> * Preload SVG Signed-off-by: jolheiser <john.olheiser@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: techknowlogick <matti@mdranta.net>
5 years ago
Add Octicon SVG spritemap (#10107) * Add octicon SVG sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Static prefix Signed-off-by: jolheiser <john.olheiser@gmail.com> * SVG for all repo icons Signed-off-by: jolheiser <john.olheiser@gmail.com> * make vendor Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap out octicons Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move octicons to top of less imports Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix JS Signed-off-by: jolheiser <john.olheiser@gmail.com> * Definitely not a search/replace Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed regex Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move to more generic calls and webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * make svg -> make webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg-sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed a test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg from makefile Signed-off-by: jolheiser <john.olheiser@gmail.com> * Suggestions Signed-off-by: jolheiser <john.olheiser@gmail.com> * Attempt to fix test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert timetracking test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap .octicon for .svg in less Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add aria-hidden Signed-off-by: jolheiser <john.olheiser@gmail.com> * Replace mega-octicon Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix webpack globbing on Windows Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert Co-Authored-By: silverwind <me@silverwind.io> * Fix octions from upstream Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix Vue and missed JS function Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add JS helper and PWA Signed-off-by: jolheiser <john.olheiser@gmail.com> * Preload SVG Signed-off-by: jolheiser <john.olheiser@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: techknowlogick <matti@mdranta.net>
5 years ago
Add Organization Wide Labels (#10814) * Add organization wide labels Implement organization wide labels similar to organization wide webhooks. This lets you create individual labels for organizations that can be used for all repos under that organization (so being able to reuse the same label across multiple repos). This makes it possible for small organizations with many repos to use labels effectively. Fixes #7406 * Add migration * remove comments * fix tests * Update options/locale/locale_en-US.ini Removed unused translation string * show org labels in issue search label filter * Use more clear var name * rename migration after merge from master * comment typo * update migration again after rebase with master * check for orgID <=0 per guillep2k review * fmt * Apply suggestions from code review Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * remove unused code * Make sure RepoID is 0 when searching orgID per code review * more changes/code review requests * More descriptive translation var per code review * func description/delete comment when issue label deleted instead of hiding it * remove comment * only use issues in that repo when calculating number of open issues for org label on repo label page * Add integration test for IssuesSearch API with labels * remove unused function * Update models/issue_label.go Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Use subquery in GetLabelIDsInReposByNames * Fix tests to use correct orgID * fix more tests * IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well * update comment for clarity * Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition * Don't sort repos by date in IssuesSearch API After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45 Returns different results for MySQL than other engines. However, the similar query: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30 Returns the same results. This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function. * linter is back! * code review * remove now unused option * Fix newline at end of files * more unused code * update to master * check for matching ids before query * Update models/issue_label.go Co-Authored-By: 6543 <6543@obermui.de> * Update models/issue_label.go * update comments * Update routers/org/setting.go Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: 6543 <6543@obermui.de>
5 years ago
Change target branch for pull request (#6488) * Adds functionality to change target branch of created pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use const instead of var in JavaScript additions Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Check if branches are equal and if PR already exists before changing target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Make sure to check all commits Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Print error messages for user as error flash message Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Disallow changing target branch of closed or merged pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Resolve conflicts after merge of upstream/master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Change order of branch select fields Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes duplicate check Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use ctx.Tr for translations Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Recompile JS Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use correct translation namespace Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove redundant if condition Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves most change branch logic into pull service Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Completes comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Add Ref to ChangesPayload for logging changed target branches instead of creating a new struct Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Revert changes to go.mod Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Directly use createComment method Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return 404 if pull request is not found. Move written check up Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove variable declaration Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return client errors on change pull request target errors Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return error in commit.HasPreviousCommit Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds blank line Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Test patch before persisting new target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update patch before testing (not working) Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes patch calls when changeing pull request target Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes unneeded check for base name Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves ChangeTargetBranch completely to pull service. Update patch status. Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Set webhook mode after errors were validated Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update PR in one transaction Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Move logic for check if head is equal with branch to pull model Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds missing comment and simplify return Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjust CreateComment method call Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>
5 years ago
Change target branch for pull request (#6488) * Adds functionality to change target branch of created pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use const instead of var in JavaScript additions Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Check if branches are equal and if PR already exists before changing target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Make sure to check all commits Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Print error messages for user as error flash message Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Disallow changing target branch of closed or merged pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Resolve conflicts after merge of upstream/master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Change order of branch select fields Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes duplicate check Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use ctx.Tr for translations Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Recompile JS Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use correct translation namespace Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove redundant if condition Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves most change branch logic into pull service Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Completes comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Add Ref to ChangesPayload for logging changed target branches instead of creating a new struct Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Revert changes to go.mod Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Directly use createComment method Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return 404 if pull request is not found. Move written check up Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove variable declaration Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return client errors on change pull request target errors Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return error in commit.HasPreviousCommit Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds blank line Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Test patch before persisting new target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update patch before testing (not working) Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes patch calls when changeing pull request target Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes unneeded check for base name Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves ChangeTargetBranch completely to pull service. Update patch status. Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Set webhook mode after errors were validated Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update PR in one transaction Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Move logic for check if head is equal with branch to pull model Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds missing comment and simplify return Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjust CreateComment method call Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>
5 years ago
Change target branch for pull request (#6488) * Adds functionality to change target branch of created pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use const instead of var in JavaScript additions Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Check if branches are equal and if PR already exists before changing target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Make sure to check all commits Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Print error messages for user as error flash message Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Disallow changing target branch of closed or merged pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Resolve conflicts after merge of upstream/master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Change order of branch select fields Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes duplicate check Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use ctx.Tr for translations Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Recompile JS Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use correct translation namespace Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove redundant if condition Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves most change branch logic into pull service Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Completes comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Add Ref to ChangesPayload for logging changed target branches instead of creating a new struct Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Revert changes to go.mod Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Directly use createComment method Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return 404 if pull request is not found. Move written check up Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove variable declaration Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return client errors on change pull request target errors Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return error in commit.HasPreviousCommit Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds blank line Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Test patch before persisting new target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update patch before testing (not working) Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes patch calls when changeing pull request target Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes unneeded check for base name Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves ChangeTargetBranch completely to pull service. Update patch status. Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Set webhook mode after errors were validated Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update PR in one transaction Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Move logic for check if head is equal with branch to pull model Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds missing comment and simplify return Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjust CreateComment method call Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>
5 years ago
Change target branch for pull request (#6488) * Adds functionality to change target branch of created pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use const instead of var in JavaScript additions Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Check if branches are equal and if PR already exists before changing target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Make sure to check all commits Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Print error messages for user as error flash message Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Disallow changing target branch of closed or merged pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Resolve conflicts after merge of upstream/master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Change order of branch select fields Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes duplicate check Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use ctx.Tr for translations Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Recompile JS Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use correct translation namespace Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove redundant if condition Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves most change branch logic into pull service Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Completes comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Add Ref to ChangesPayload for logging changed target branches instead of creating a new struct Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Revert changes to go.mod Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Directly use createComment method Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return 404 if pull request is not found. Move written check up Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove variable declaration Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return client errors on change pull request target errors Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return error in commit.HasPreviousCommit Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds blank line Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Test patch before persisting new target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update patch before testing (not working) Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes patch calls when changeing pull request target Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes unneeded check for base name Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves ChangeTargetBranch completely to pull service. Update patch status. Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Set webhook mode after errors were validated Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update PR in one transaction Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Move logic for check if head is equal with branch to pull model Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds missing comment and simplify return Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjust CreateComment method call Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>
5 years ago
Change target branch for pull request (#6488) * Adds functionality to change target branch of created pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use const instead of var in JavaScript additions Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Check if branches are equal and if PR already exists before changing target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Make sure to check all commits Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Print error messages for user as error flash message Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Disallow changing target branch of closed or merged pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Resolve conflicts after merge of upstream/master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Change order of branch select fields Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes duplicate check Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use ctx.Tr for translations Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Recompile JS Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use correct translation namespace Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove redundant if condition Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves most change branch logic into pull service Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Completes comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Add Ref to ChangesPayload for logging changed target branches instead of creating a new struct Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Revert changes to go.mod Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Directly use createComment method Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return 404 if pull request is not found. Move written check up Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove variable declaration Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return client errors on change pull request target errors Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return error in commit.HasPreviousCommit Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds blank line Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Test patch before persisting new target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update patch before testing (not working) Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes patch calls when changeing pull request target Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes unneeded check for base name Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves ChangeTargetBranch completely to pull service. Update patch status. Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Set webhook mode after errors were validated Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update PR in one transaction Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Move logic for check if head is equal with branch to pull model Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds missing comment and simplify return Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjust CreateComment method call Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>
5 years ago
Change target branch for pull request (#6488) * Adds functionality to change target branch of created pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use const instead of var in JavaScript additions Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Check if branches are equal and if PR already exists before changing target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Make sure to check all commits Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Print error messages for user as error flash message Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Disallow changing target branch of closed or merged pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Resolve conflicts after merge of upstream/master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Change order of branch select fields Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes duplicate check Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use ctx.Tr for translations Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Recompile JS Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use correct translation namespace Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove redundant if condition Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves most change branch logic into pull service Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Completes comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Add Ref to ChangesPayload for logging changed target branches instead of creating a new struct Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Revert changes to go.mod Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Directly use createComment method Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return 404 if pull request is not found. Move written check up Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove variable declaration Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return client errors on change pull request target errors Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return error in commit.HasPreviousCommit Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds blank line Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Test patch before persisting new target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update patch before testing (not working) Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes patch calls when changeing pull request target Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes unneeded check for base name Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves ChangeTargetBranch completely to pull service. Update patch status. Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Set webhook mode after errors were validated Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update PR in one transaction Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Move logic for check if head is equal with branch to pull model Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds missing comment and simplify return Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjust CreateComment method call Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>
5 years ago
Change target branch for pull request (#6488) * Adds functionality to change target branch of created pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use const instead of var in JavaScript additions Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Check if branches are equal and if PR already exists before changing target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Make sure to check all commits Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Print error messages for user as error flash message Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Disallow changing target branch of closed or merged pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Resolve conflicts after merge of upstream/master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Change order of branch select fields Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes duplicate check Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use ctx.Tr for translations Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Recompile JS Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use correct translation namespace Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove redundant if condition Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves most change branch logic into pull service Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Completes comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Add Ref to ChangesPayload for logging changed target branches instead of creating a new struct Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Revert changes to go.mod Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Directly use createComment method Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return 404 if pull request is not found. Move written check up Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove variable declaration Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return client errors on change pull request target errors Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return error in commit.HasPreviousCommit Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds blank line Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Test patch before persisting new target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update patch before testing (not working) Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes patch calls when changeing pull request target Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes unneeded check for base name Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves ChangeTargetBranch completely to pull service. Update patch status. Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Set webhook mode after errors were validated Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update PR in one transaction Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Move logic for check if head is equal with branch to pull model Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds missing comment and simplify return Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjust CreateComment method call Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>
5 years ago
Add Organization Wide Labels (#10814) * Add organization wide labels Implement organization wide labels similar to organization wide webhooks. This lets you create individual labels for organizations that can be used for all repos under that organization (so being able to reuse the same label across multiple repos). This makes it possible for small organizations with many repos to use labels effectively. Fixes #7406 * Add migration * remove comments * fix tests * Update options/locale/locale_en-US.ini Removed unused translation string * show org labels in issue search label filter * Use more clear var name * rename migration after merge from master * comment typo * update migration again after rebase with master * check for orgID <=0 per guillep2k review * fmt * Apply suggestions from code review Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * remove unused code * Make sure RepoID is 0 when searching orgID per code review * more changes/code review requests * More descriptive translation var per code review * func description/delete comment when issue label deleted instead of hiding it * remove comment * only use issues in that repo when calculating number of open issues for org label on repo label page * Add integration test for IssuesSearch API with labels * remove unused function * Update models/issue_label.go Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Use subquery in GetLabelIDsInReposByNames * Fix tests to use correct orgID * fix more tests * IssuesSearch api now uses new BuildLabelNamesIssueIDsCondition. Add a few more tests as well * update comment for clarity * Revert previous code change now that we can use the new BuildLabelNamesIssueIDsCondition * Don't sort repos by date in IssuesSearch API After much debugging I've found a strange issue where in some cases MySQL will return a different result than other enigines if a query is sorted by a null collumn. For example with our integration test data where we don't set updated_unix in repository fixtures: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 45 Returns different results for MySQL than other engines. However, the similar query: SELECT `id`, `owner_id`, `owner_name`, `lower_name`, `name`, `description`, `website`, `original_service_type`, `original_url`, `default_branch`, `num_watches`, `num_stars`, `num_forks`, `num_issues`, `num_closed_issues`, `num_pulls`, `num_closed_pulls`, `num_milestones`, `num_closed_milestones`, `is_private`, `is_empty`, `is_archived`, `is_mirror`, `status`, `is_fork`, `fork_id`, `is_template`, `template_id`, `size`, `is_fsck_enabled`, `close_issues_via_commit_in_any_branch`, `topics`, `avatar`, `created_unix`, `updated_unix` FROM `repository` ORDER BY updated_unix DESC LIMIT 15 OFFSET 30 Returns the same results. This causes integration tests to fail on MySQL in certain cases but would never show up in a real installation. Since this API call always returns issues based on the optionally provided repo_priority_id or the issueID itself, there is no change to results by changing the repo sorting method used to get ids earlier in the function. * linter is back! * code review * remove now unused option * Fix newline at end of files * more unused code * update to master * check for matching ids before query * Update models/issue_label.go Co-Authored-By: 6543 <6543@obermui.de> * Update models/issue_label.go * update comments * Update routers/org/setting.go Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: 6543 <6543@obermui.de>
5 years ago
Add single sign-on support via SSPI on Windows (#8463) * Add single sign-on support via SSPI on Windows * Ensure plugins implement interface * Ensure plugins implement interface * Move functions used only by the SSPI auth method to sspi_windows.go * Field SSPISeparatorReplacement of AuthenticationForm should not be required via binding, as binding will insist the field is non-empty even if another login type is selected * Fix breaking of oauth authentication on download links. Do not create new session with SSPI authentication on download links. * Update documentation for the new 'SPNEGO with SSPI' login source * Mention in documentation that ROOT_URL should contain the FQDN of the server * Make sure that Contexter is not checking for active login sources when the ORM engine is not initialized (eg. when installing) * Always initialize and free SSO methods, even if they are not enabled, as a method can be activated while the app is running (from Authentication sources) * Add option in SSPIConfig for removing of domains from logon names * Update helper text for StripDomainNames option * Make sure handleSignIn() is called after a new user object is created by SSPI auth method * Remove default value from text of form field helper Co-Authored-By: Lauris BH <lauris@nix.lv> * Remove default value from text of form field helper Co-Authored-By: Lauris BH <lauris@nix.lv> * Remove default value from text of form field helper Co-Authored-By: Lauris BH <lauris@nix.lv> * Only make a query to the DB to check if SSPI is enabled on handlers that need that information for templates * Remove code duplication * Log errors in ActiveLoginSources Co-Authored-By: Lauris BH <lauris@nix.lv> * Revert suffix of randomly generated E-mails for Reverse proxy authentication Co-Authored-By: Lauris BH <lauris@nix.lv> * Revert unneeded white-space change in template Co-Authored-By: Lauris BH <lauris@nix.lv> * Add copyright comments at the top of new files * Use loopback name for randomly generated emails * Add locale tag for the SSPISeparatorReplacement field with proper casing * Revert casing of SSPISeparatorReplacement field in locale file, moving it up, next to other form fields * Update docs/content/doc/features/authentication.en-us.md Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Remove Priority() method and define the order in which SSO auth methods should be executed in one place * Log authenticated username only if it's not empty * Rephrase helper text for automatic creation of users * Return error if more than one active SSPI auth source is found * Change newUser() function to return error, letting caller log/handle the error * Move isPublicResource, isPublicPage and handleSignIn functions outside SSPI auth method to allow other SSO methods to reuse them if needed * Refactor initialization of the list containing SSO auth methods * Validate SSPI settings on POST * Change SSPI to only perform authentication on its own login page, API paths and download links. Leave Toggle middleware to redirect non authenticated users to login page * Make 'Default language' in SSPI config empty, unless changed by admin * Show error if admin tries to add a second authentication source of type SSPI * Simplify declaration of global variable * Rebuild gitgraph.js on Linux * Make sure config values containing only whitespace are not accepted
5 years ago
Add single sign-on support via SSPI on Windows (#8463) * Add single sign-on support via SSPI on Windows * Ensure plugins implement interface * Ensure plugins implement interface * Move functions used only by the SSPI auth method to sspi_windows.go * Field SSPISeparatorReplacement of AuthenticationForm should not be required via binding, as binding will insist the field is non-empty even if another login type is selected * Fix breaking of oauth authentication on download links. Do not create new session with SSPI authentication on download links. * Update documentation for the new 'SPNEGO with SSPI' login source * Mention in documentation that ROOT_URL should contain the FQDN of the server * Make sure that Contexter is not checking for active login sources when the ORM engine is not initialized (eg. when installing) * Always initialize and free SSO methods, even if they are not enabled, as a method can be activated while the app is running (from Authentication sources) * Add option in SSPIConfig for removing of domains from logon names * Update helper text for StripDomainNames option * Make sure handleSignIn() is called after a new user object is created by SSPI auth method * Remove default value from text of form field helper Co-Authored-By: Lauris BH <lauris@nix.lv> * Remove default value from text of form field helper Co-Authored-By: Lauris BH <lauris@nix.lv> * Remove default value from text of form field helper Co-Authored-By: Lauris BH <lauris@nix.lv> * Only make a query to the DB to check if SSPI is enabled on handlers that need that information for templates * Remove code duplication * Log errors in ActiveLoginSources Co-Authored-By: Lauris BH <lauris@nix.lv> * Revert suffix of randomly generated E-mails for Reverse proxy authentication Co-Authored-By: Lauris BH <lauris@nix.lv> * Revert unneeded white-space change in template Co-Authored-By: Lauris BH <lauris@nix.lv> * Add copyright comments at the top of new files * Use loopback name for randomly generated emails * Add locale tag for the SSPISeparatorReplacement field with proper casing * Revert casing of SSPISeparatorReplacement field in locale file, moving it up, next to other form fields * Update docs/content/doc/features/authentication.en-us.md Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Remove Priority() method and define the order in which SSO auth methods should be executed in one place * Log authenticated username only if it's not empty * Rephrase helper text for automatic creation of users * Return error if more than one active SSPI auth source is found * Change newUser() function to return error, letting caller log/handle the error * Move isPublicResource, isPublicPage and handleSignIn functions outside SSPI auth method to allow other SSO methods to reuse them if needed * Refactor initialization of the list containing SSO auth methods * Validate SSPI settings on POST * Change SSPI to only perform authentication on its own login page, API paths and download links. Leave Toggle middleware to redirect non authenticated users to login page * Make 'Default language' in SSPI config empty, unless changed by admin * Show error if admin tries to add a second authentication source of type SSPI * Simplify declaration of global variable * Rebuild gitgraph.js on Linux * Make sure config values containing only whitespace are not accepted
5 years ago
Use AJAX for notifications table (#10961) * Use AJAX for notifications table Signed-off-by: Andrew Thornton <art27@cantab.net> * move to separate js Signed-off-by: Andrew Thornton <art27@cantab.net> * placate golangci-lint Signed-off-by: Andrew Thornton <art27@cantab.net> * Add autoupdating notification count Signed-off-by: Andrew Thornton <art27@cantab.net> * Fix wipeall Signed-off-by: Andrew Thornton <art27@cantab.net> * placate tests Signed-off-by: Andrew Thornton <art27@cantab.net> * Try hidden Signed-off-by: Andrew Thornton <art27@cantab.net> * Try hide and hidden Signed-off-by: Andrew Thornton <art27@cantab.net> * More auto-update improvements Only run checker on pages that have a count Change starting checker to 10s with a back-off to 60s if there is no change Signed-off-by: Andrew Thornton <art27@cantab.net> * string comparison! Signed-off-by: Andrew Thornton <art27@cantab.net> * as per @silverwind Signed-off-by: Andrew Thornton <art27@cantab.net> * add configurability as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> * Add documentation as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> * Use CSRF header not query Signed-off-by: Andrew Thornton <art27@cantab.net> * Further JS improvements Fix @etzelia update notification table request Fix @silverwind comments Co-Authored-By: silverwind <me@silverwind.io> Signed-off-by: Andrew Thornton <art27@cantab.net> * Simplify the notification count fns Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: silverwind <me@silverwind.io>
5 years ago
Use AJAX for notifications table (#10961) * Use AJAX for notifications table Signed-off-by: Andrew Thornton <art27@cantab.net> * move to separate js Signed-off-by: Andrew Thornton <art27@cantab.net> * placate golangci-lint Signed-off-by: Andrew Thornton <art27@cantab.net> * Add autoupdating notification count Signed-off-by: Andrew Thornton <art27@cantab.net> * Fix wipeall Signed-off-by: Andrew Thornton <art27@cantab.net> * placate tests Signed-off-by: Andrew Thornton <art27@cantab.net> * Try hidden Signed-off-by: Andrew Thornton <art27@cantab.net> * Try hide and hidden Signed-off-by: Andrew Thornton <art27@cantab.net> * More auto-update improvements Only run checker on pages that have a count Change starting checker to 10s with a back-off to 60s if there is no change Signed-off-by: Andrew Thornton <art27@cantab.net> * string comparison! Signed-off-by: Andrew Thornton <art27@cantab.net> * as per @silverwind Signed-off-by: Andrew Thornton <art27@cantab.net> * add configurability as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> * Add documentation as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> * Use CSRF header not query Signed-off-by: Andrew Thornton <art27@cantab.net> * Further JS improvements Fix @etzelia update notification table request Fix @silverwind comments Co-Authored-By: silverwind <me@silverwind.io> Signed-off-by: Andrew Thornton <art27@cantab.net> * Simplify the notification count fns Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: silverwind <me@silverwind.io>
5 years ago
Add Octicon SVG spritemap (#10107) * Add octicon SVG sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Static prefix Signed-off-by: jolheiser <john.olheiser@gmail.com> * SVG for all repo icons Signed-off-by: jolheiser <john.olheiser@gmail.com> * make vendor Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap out octicons Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move octicons to top of less imports Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix JS Signed-off-by: jolheiser <john.olheiser@gmail.com> * Definitely not a search/replace Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed regex Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move to more generic calls and webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * make svg -> make webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg-sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed a test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg from makefile Signed-off-by: jolheiser <john.olheiser@gmail.com> * Suggestions Signed-off-by: jolheiser <john.olheiser@gmail.com> * Attempt to fix test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert timetracking test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap .octicon for .svg in less Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add aria-hidden Signed-off-by: jolheiser <john.olheiser@gmail.com> * Replace mega-octicon Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix webpack globbing on Windows Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert Co-Authored-By: silverwind <me@silverwind.io> * Fix octions from upstream Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix Vue and missed JS function Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add JS helper and PWA Signed-off-by: jolheiser <john.olheiser@gmail.com> * Preload SVG Signed-off-by: jolheiser <john.olheiser@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: techknowlogick <matti@mdranta.net>
5 years ago
Add Octicon SVG spritemap (#10107) * Add octicon SVG sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Static prefix Signed-off-by: jolheiser <john.olheiser@gmail.com> * SVG for all repo icons Signed-off-by: jolheiser <john.olheiser@gmail.com> * make vendor Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap out octicons Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move octicons to top of less imports Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix JS Signed-off-by: jolheiser <john.olheiser@gmail.com> * Definitely not a search/replace Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed regex Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move to more generic calls and webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * make svg -> make webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg-sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed a test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg from makefile Signed-off-by: jolheiser <john.olheiser@gmail.com> * Suggestions Signed-off-by: jolheiser <john.olheiser@gmail.com> * Attempt to fix test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert timetracking test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap .octicon for .svg in less Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add aria-hidden Signed-off-by: jolheiser <john.olheiser@gmail.com> * Replace mega-octicon Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix webpack globbing on Windows Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert Co-Authored-By: silverwind <me@silverwind.io> * Fix octions from upstream Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix Vue and missed JS function Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add JS helper and PWA Signed-off-by: jolheiser <john.olheiser@gmail.com> * Preload SVG Signed-off-by: jolheiser <john.olheiser@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: techknowlogick <matti@mdranta.net>
5 years ago
Add Octicon SVG spritemap (#10107) * Add octicon SVG sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Static prefix Signed-off-by: jolheiser <john.olheiser@gmail.com> * SVG for all repo icons Signed-off-by: jolheiser <john.olheiser@gmail.com> * make vendor Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap out octicons Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move octicons to top of less imports Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix JS Signed-off-by: jolheiser <john.olheiser@gmail.com> * Definitely not a search/replace Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed regex Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move to more generic calls and webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * make svg -> make webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg-sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed a test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg from makefile Signed-off-by: jolheiser <john.olheiser@gmail.com> * Suggestions Signed-off-by: jolheiser <john.olheiser@gmail.com> * Attempt to fix test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert timetracking test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap .octicon for .svg in less Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add aria-hidden Signed-off-by: jolheiser <john.olheiser@gmail.com> * Replace mega-octicon Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix webpack globbing on Windows Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert Co-Authored-By: silverwind <me@silverwind.io> * Fix octions from upstream Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix Vue and missed JS function Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add JS helper and PWA Signed-off-by: jolheiser <john.olheiser@gmail.com> * Preload SVG Signed-off-by: jolheiser <john.olheiser@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: techknowlogick <matti@mdranta.net>
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606
  1. /* exported timeAddManual, toggleStopwatch, cancelStopwatch */
  2. /* exported toggleDeadlineForm, setDeadline, updateDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */
  3. import './publicpath.js';
  4. import Vue from 'vue';
  5. import {htmlEscape} from 'escape-goat';
  6. import 'jquery.are-you-sure';
  7. import './vendor/semanticdropdown.js';
  8. import initMigration from './features/migration.js';
  9. import initContextPopups from './features/contextpopup.js';
  10. import initGitGraph from './features/gitgraph.js';
  11. import initClipboard from './features/clipboard.js';
  12. import initUserHeatmap from './features/userheatmap.js';
  13. import initProject from './features/projects.js';
  14. import initServiceWorker from './features/serviceworker.js';
  15. import initMarkdownAnchors from './markdown/anchors.js';
  16. import renderMarkdownContent from './markdown/content.js';
  17. import attachTribute from './features/tribute.js';
  18. import createColorPicker from './features/colorpicker.js';
  19. import createDropzone from './features/dropzone.js';
  20. import initTableSort from './features/tablesort.js';
  21. import ActivityTopAuthors from './components/ActivityTopAuthors.vue';
  22. import {initNotificationsTable, initNotificationCount} from './features/notification.js';
  23. import {createCodeEditor} from './features/codeeditor.js';
  24. import {svg, svgs} from './svg.js';
  25. import {stripTags} from './utils.js';
  26. const {AppSubUrl, StaticUrlPrefix, csrf} = window.config;
  27. let previewFileModes;
  28. const commentMDEditors = {};
  29. // Silence fomantic's error logging when tabs are used without a target content element
  30. $.fn.tab.settings.silent = true;
  31. function initCommentPreviewTab($form) {
  32. const $tabMenu = $form.find('.tabular.menu');
  33. $tabMenu.find('.item').tab();
  34. $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`).on('click', function () {
  35. const $this = $(this);
  36. $.post($this.data('url'), {
  37. _csrf: csrf,
  38. mode: 'comment',
  39. context: $this.data('context'),
  40. text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val()
  41. }, (data) => {
  42. const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`);
  43. $previewPanel.html(data);
  44. renderMarkdownContent();
  45. });
  46. });
  47. buttonsClickOnEnter();
  48. }
  49. function initEditPreviewTab($form) {
  50. const $tabMenu = $form.find('.tabular.menu');
  51. $tabMenu.find('.item').tab();
  52. const $previewTab = $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`);
  53. if ($previewTab.length) {
  54. previewFileModes = $previewTab.data('preview-file-modes').split(',');
  55. $previewTab.on('click', function () {
  56. const $this = $(this);
  57. let context = `${$this.data('context')}/`;
  58. const mode = $this.data('markdown-mode') || 'comment';
  59. const treePathEl = $form.find('input#tree_path');
  60. if (treePathEl.length > 0) {
  61. context += treePathEl.val();
  62. }
  63. context = context.substring(0, context.lastIndexOf('/'));
  64. $.post($this.data('url'), {
  65. _csrf: csrf,
  66. mode,
  67. context,
  68. text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val()
  69. }, (data) => {
  70. const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`);
  71. $previewPanel.html(data);
  72. renderMarkdownContent();
  73. });
  74. });
  75. }
  76. }
  77. function initEditDiffTab($form) {
  78. const $tabMenu = $form.find('.tabular.menu');
  79. $tabMenu.find('.item').tab();
  80. $tabMenu.find(`.item[data-tab="${$tabMenu.data('diff')}"]`).on('click', function () {
  81. const $this = $(this);
  82. $.post($this.data('url'), {
  83. _csrf: csrf,
  84. context: $this.data('context'),
  85. content: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val()
  86. }, (data) => {
  87. const $diffPreviewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('diff')}"]`);
  88. $diffPreviewPanel.html(data);
  89. });
  90. });
  91. }
  92. function initEditForm() {
  93. if ($('.edit.form').length === 0) {
  94. return;
  95. }
  96. initEditPreviewTab($('.edit.form'));
  97. initEditDiffTab($('.edit.form'));
  98. }
  99. function initBranchSelector() {
  100. const $selectBranch = $('.ui.select-branch');
  101. const $branchMenu = $selectBranch.find('.reference-list-menu');
  102. $branchMenu.find('.item:not(.no-select)').click(function () {
  103. const selectedValue = $(this).data('id');
  104. const editMode = $('#editing_mode').val();
  105. $($(this).data('id-selector')).val(selectedValue);
  106. if (editMode === 'true') {
  107. const form = $('#update_issueref_form');
  108. $.post(form.attr('action'), {
  109. _csrf: csrf,
  110. ref: selectedValue
  111. },
  112. () => {
  113. window.location.reload();
  114. });
  115. } else if (editMode === '') {
  116. $selectBranch.find('.ui .branch-name').text(selectedValue);
  117. }
  118. });
  119. $selectBranch.find('.reference.column').on('click', function () {
  120. $selectBranch.find('.scrolling.reference-list-menu').css('display', 'none');
  121. $selectBranch.find('.reference .text').removeClass('black');
  122. $($(this).data('target')).css('display', 'block');
  123. $(this).find('.text').addClass('black');
  124. return false;
  125. });
  126. }
  127. function initLabelEdit() {
  128. // Create label
  129. const $newLabelPanel = $('.new-label.segment');
  130. $('.new-label.button').on('click', () => {
  131. $newLabelPanel.show();
  132. });
  133. $('.new-label.segment .cancel').on('click', () => {
  134. $newLabelPanel.hide();
  135. });
  136. createColorPicker($('.color-picker'));
  137. $('.precolors .color').on('click', function () {
  138. const color_hex = $(this).data('color-hex');
  139. $('.color-picker').val(color_hex);
  140. $('.minicolors-swatch-color').css('background-color', color_hex);
  141. });
  142. $('.edit-label-button').on('click', function () {
  143. $('.color-picker').minicolors('value', $(this).data('color'));
  144. $('#label-modal-id').val($(this).data('id'));
  145. $('.edit-label .new-label-input').val($(this).data('title'));
  146. $('.edit-label .new-label-desc-input').val($(this).data('description'));
  147. $('.edit-label .color-picker').val($(this).data('color'));
  148. $('.minicolors-swatch-color').css('background-color', $(this).data('color'));
  149. $('.edit-label.modal').modal({
  150. onApprove() {
  151. $('.edit-label.form').trigger('submit');
  152. }
  153. }).modal('show');
  154. return false;
  155. });
  156. }
  157. function updateIssuesMeta(url, action, issueIds, elementId) {
  158. return new Promise(((resolve) => {
  159. $.ajax({
  160. type: 'POST',
  161. url,
  162. data: {
  163. _csrf: csrf,
  164. action,
  165. issue_ids: issueIds,
  166. id: elementId,
  167. },
  168. success: resolve
  169. });
  170. }));
  171. }
  172. function initRepoStatusChecker() {
  173. const migrating = $('#repo_migrating');
  174. $('#repo_migrating_failed').hide();
  175. $('#repo_migrating_failed_image').hide();
  176. if (migrating) {
  177. const task = migrating.attr('task');
  178. if (typeof task === 'undefined') {
  179. return;
  180. }
  181. $.ajax({
  182. type: 'GET',
  183. url: `${AppSubUrl}/user/task/${task}`,
  184. data: {
  185. _csrf: csrf,
  186. },
  187. complete(xhr) {
  188. if (xhr.status === 200) {
  189. if (xhr.responseJSON) {
  190. if (xhr.responseJSON.status === 4) {
  191. window.location.reload();
  192. return;
  193. } else if (xhr.responseJSON.status === 3) {
  194. $('#repo_migrating_progress').hide();
  195. $('#repo_migrating').hide();
  196. $('#repo_migrating_failed').show();
  197. $('#repo_migrating_failed_image').show();
  198. $('#repo_migrating_failed_error').text(xhr.responseJSON.err);
  199. return;
  200. }
  201. setTimeout(() => {
  202. initRepoStatusChecker();
  203. }, 2000);
  204. return;
  205. }
  206. }
  207. $('#repo_migrating_progress').hide();
  208. $('#repo_migrating').hide();
  209. $('#repo_migrating_failed').show();
  210. $('#repo_migrating_failed_image').show();
  211. }
  212. });
  213. }
  214. }
  215. function initReactionSelector(parent) {
  216. let reactions = '';
  217. if (!parent) {
  218. parent = $(document);
  219. reactions = '.reactions > ';
  220. }
  221. parent.find(`${reactions}a.label`).popup({position: 'bottom left', metadata: {content: 'title', title: 'none'}});
  222. parent.find(`.select-reaction > .menu > .item, ${reactions}a.label`).on('click', function (e) {
  223. const vm = this;
  224. e.preventDefault();
  225. if ($(this).hasClass('disabled')) return;
  226. const actionURL = $(this).hasClass('item') ? $(this).closest('.select-reaction').data('action-url') : $(this).data('action-url');
  227. const url = `${actionURL}/${$(this).hasClass('blue') ? 'unreact' : 'react'}`;
  228. $.ajax({
  229. type: 'POST',
  230. url,
  231. data: {
  232. _csrf: csrf,
  233. content: $(this).data('content')
  234. }
  235. }).done((resp) => {
  236. if (resp && (resp.html || resp.empty)) {
  237. const content = $(vm).closest('.content');
  238. let react = content.find('.segment.reactions');
  239. if ((!resp.empty || resp.html === '') && react.length > 0) {
  240. react.remove();
  241. }
  242. if (!resp.empty) {
  243. react = $('<div class="ui attached segment reactions"></div>');
  244. const attachments = content.find('.segment.bottom:first');
  245. if (attachments.length > 0) {
  246. react.insertBefore(attachments);
  247. } else {
  248. react.appendTo(content);
  249. }
  250. react.html(resp.html);
  251. react.find('.dropdown').dropdown();
  252. initReactionSelector(react);
  253. }
  254. }
  255. });
  256. });
  257. }
  258. function insertAtCursor(field, value) {
  259. if (field.selectionStart || field.selectionStart === 0) {
  260. const startPos = field.selectionStart;
  261. const endPos = field.selectionEnd;
  262. field.value = field.value.substring(0, startPos) + value + field.value.substring(endPos, field.value.length);
  263. field.selectionStart = startPos + value.length;
  264. field.selectionEnd = startPos + value.length;
  265. } else {
  266. field.value += value;
  267. }
  268. }
  269. function replaceAndKeepCursor(field, oldval, newval) {
  270. if (field.selectionStart || field.selectionStart === 0) {
  271. const startPos = field.selectionStart;
  272. const endPos = field.selectionEnd;
  273. field.value = field.value.replace(oldval, newval);
  274. field.selectionStart = startPos + newval.length - oldval.length;
  275. field.selectionEnd = endPos + newval.length - oldval.length;
  276. } else {
  277. field.value = field.value.replace(oldval, newval);
  278. }
  279. }
  280. function getPastedImages(e) {
  281. if (!e.clipboardData) return [];
  282. const files = [];
  283. for (const item of e.clipboardData.items || []) {
  284. if (!item.type || !item.type.startsWith('image/')) continue;
  285. files.push(item.getAsFile());
  286. }
  287. if (files.length) {
  288. e.preventDefault();
  289. e.stopPropagation();
  290. }
  291. return files;
  292. }
  293. async function uploadFile(file) {
  294. const formData = new FormData();
  295. formData.append('file', file, file.name);
  296. const res = await fetch($('#dropzone').data('upload-url'), {
  297. method: 'POST',
  298. headers: {'X-Csrf-Token': csrf},
  299. body: formData,
  300. });
  301. return await res.json();
  302. }
  303. function reload() {
  304. window.location.reload();
  305. }
  306. function initImagePaste(target) {
  307. target.each(function () {
  308. const field = this;
  309. field.addEventListener('paste', async (e) => {
  310. for (const img of getPastedImages(e)) {
  311. const name = img.name.substr(0, img.name.lastIndexOf('.'));
  312. insertAtCursor(field, `![${name}]()`);
  313. const data = await uploadFile(img);
  314. replaceAndKeepCursor(field, `![${name}]()`, `![${name}](${AppSubUrl}/attachments/${data.uuid})`);
  315. const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
  316. $('.files').append(input);
  317. }
  318. }, false);
  319. });
  320. }
  321. function initSimpleMDEImagePaste(simplemde, files) {
  322. simplemde.codemirror.on('paste', async (_, e) => {
  323. for (const img of getPastedImages(e)) {
  324. const name = img.name.substr(0, img.name.lastIndexOf('.'));
  325. const data = await uploadFile(img);
  326. const pos = simplemde.codemirror.getCursor();
  327. simplemde.codemirror.replaceRange(`![${name}](${AppSubUrl}/attachments/${data.uuid})`, pos);
  328. const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
  329. files.append(input);
  330. }
  331. });
  332. }
  333. let autoSimpleMDE;
  334. function initCommentForm() {
  335. if ($('.comment.form').length === 0) {
  336. return;
  337. }
  338. autoSimpleMDE = setCommentSimpleMDE($('.comment.form textarea:not(.review-textarea)'));
  339. initBranchSelector();
  340. initCommentPreviewTab($('.comment.form'));
  341. initImagePaste($('.comment.form textarea'));
  342. // Listsubmit
  343. function initListSubmits(selector, outerSelector) {
  344. const $list = $(`.ui.${outerSelector}.list`);
  345. const $noSelect = $list.find('.no-select');
  346. const $listMenu = $(`.${selector} .menu`);
  347. let hasUpdateAction = $listMenu.data('action') === 'update';
  348. const items = {};
  349. $(`.${selector}`).dropdown('setting', 'onHide', () => {
  350. hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
  351. if (hasUpdateAction) {
  352. const promises = [];
  353. Object.keys(items).forEach((elementId) => {
  354. const item = items[elementId];
  355. const promise = updateIssuesMeta(
  356. item['update-url'],
  357. item.action,
  358. item['issue-id'],
  359. elementId,
  360. );
  361. promises.push(promise);
  362. });
  363. Promise.all(promises).then(reload);
  364. }
  365. });
  366. $listMenu.find('.item:not(.no-select)').on('click', function (e) {
  367. e.preventDefault();
  368. if ($(this).hasClass('ban-change')) {
  369. return false;
  370. }
  371. hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
  372. if ($(this).hasClass('checked')) {
  373. $(this).removeClass('checked');
  374. $(this).find('.octicon-check').addClass('invisible');
  375. if (hasUpdateAction) {
  376. if (!($(this).data('id') in items)) {
  377. items[$(this).data('id')] = {
  378. 'update-url': $listMenu.data('update-url'),
  379. action: 'detach',
  380. 'issue-id': $listMenu.data('issue-id'),
  381. };
  382. } else {
  383. delete items[$(this).data('id')];
  384. }
  385. }
  386. } else {
  387. $(this).addClass('checked');
  388. $(this).find('.octicon-check').removeClass('invisible');
  389. if (hasUpdateAction) {
  390. if (!($(this).data('id') in items)) {
  391. items[$(this).data('id')] = {
  392. 'update-url': $listMenu.data('update-url'),
  393. action: 'attach',
  394. 'issue-id': $listMenu.data('issue-id'),
  395. };
  396. } else {
  397. delete items[$(this).data('id')];
  398. }
  399. }
  400. }
  401. // TODO: Which thing should be done for choosing review requests
  402. // to make choosed items be shown on time here?
  403. if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
  404. return false;
  405. }
  406. const listIds = [];
  407. $(this).parent().find('.item').each(function () {
  408. if ($(this).hasClass('checked')) {
  409. listIds.push($(this).data('id'));
  410. $($(this).data('id-selector')).removeClass('hide');
  411. } else {
  412. $($(this).data('id-selector')).addClass('hide');
  413. }
  414. });
  415. if (listIds.length === 0) {
  416. $noSelect.removeClass('hide');
  417. } else {
  418. $noSelect.addClass('hide');
  419. }
  420. $($(this).parent().data('id')).val(listIds.join(','));
  421. return false;
  422. });
  423. $listMenu.find('.no-select.item').on('click', function (e) {
  424. e.preventDefault();
  425. if (hasUpdateAction) {
  426. updateIssuesMeta(
  427. $listMenu.data('update-url'),
  428. 'clear',
  429. $listMenu.data('issue-id'),
  430. '',
  431. ).then(reload);
  432. }
  433. $(this).parent().find('.item').each(function () {
  434. $(this).removeClass('checked');
  435. $(this).find('.octicon').addClass('invisible');
  436. });
  437. if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
  438. return false;
  439. }
  440. $list.find('.item').each(function () {
  441. $(this).addClass('hide');
  442. });
  443. $noSelect.removeClass('hide');
  444. $($(this).parent().data('id')).val('');
  445. });
  446. }
  447. // Init labels and assignees
  448. initListSubmits('select-label', 'labels');
  449. initListSubmits('select-assignees', 'assignees');
  450. initListSubmits('select-assignees-modify', 'assignees');
  451. initListSubmits('select-reviewers-modify', 'assignees');
  452. function selectItem(select_id, input_id) {
  453. const $menu = $(`${select_id} .menu`);
  454. const $list = $(`.ui${select_id}.list`);
  455. const hasUpdateAction = $menu.data('action') === 'update';
  456. $menu.find('.item:not(.no-select)').on('click', function () {
  457. $(this).parent().find('.item').each(function () {
  458. $(this).removeClass('selected active');
  459. });
  460. $(this).addClass('selected active');
  461. if (hasUpdateAction) {
  462. updateIssuesMeta(
  463. $menu.data('update-url'),
  464. '',
  465. $menu.data('issue-id'),
  466. $(this).data('id'),
  467. ).then(reload);
  468. }
  469. switch (input_id) {
  470. case '#milestone_id':
  471. $list.find('.selected').html(`<a class="item" href=${$(this).data('href')}>${
  472. htmlEscape($(this).text())}</a>`);
  473. break;
  474. case '#project_id':
  475. $list.find('.selected').html(`<a class="item" href=${$(this).data('href')}>${
  476. htmlEscape($(this).text())}</a>`);
  477. break;
  478. case '#assignee_id':
  479. $list.find('.selected').html(`<a class="item" href=${$(this).data('href')}>` +
  480. `<img class="ui avatar image" src=${$(this).data('avatar')}>${
  481. htmlEscape($(this).text())}</a>`);
  482. }
  483. $(`.ui${select_id}.list .no-select`).addClass('hide');
  484. $(input_id).val($(this).data('id'));
  485. });
  486. $menu.find('.no-select.item').on('click', function () {
  487. $(this).parent().find('.item:not(.no-select)').each(function () {
  488. $(this).removeClass('selected active');
  489. });
  490. if (hasUpdateAction) {
  491. updateIssuesMeta(
  492. $menu.data('update-url'),
  493. '',
  494. $menu.data('issue-id'),
  495. $(this).data('id'),
  496. ).then(reload);
  497. }
  498. $list.find('.selected').html('');
  499. $list.find('.no-select').removeClass('hide');
  500. $(input_id).val('');
  501. });
  502. }
  503. // Milestone, Assignee, Project
  504. selectItem('.select-project', '#project_id');
  505. selectItem('.select-milestone', '#milestone_id');
  506. selectItem('.select-assignee', '#assignee_id');
  507. }
  508. function initInstall() {
  509. if ($('.install').length === 0) {
  510. return;
  511. }
  512. if ($('#db_host').val() === '') {
  513. $('#db_host').val('127.0.0.1:3306');
  514. $('#db_user').val('gitea');
  515. $('#db_name').val('gitea');
  516. }
  517. // Database type change detection.
  518. $('#db_type').on('change', function () {
  519. const sqliteDefault = 'data/gitea.db';
  520. const tidbDefault = 'data/gitea_tidb';
  521. const dbType = $(this).val();
  522. if (dbType === 'SQLite3') {
  523. $('#sql_settings').hide();
  524. $('#pgsql_settings').hide();
  525. $('#mysql_settings').hide();
  526. $('#sqlite_settings').show();
  527. if (dbType === 'SQLite3' && $('#db_path').val() === tidbDefault) {
  528. $('#db_path').val(sqliteDefault);
  529. }
  530. return;
  531. }
  532. const dbDefaults = {
  533. MySQL: '127.0.0.1:3306',
  534. PostgreSQL: '127.0.0.1:5432',
  535. MSSQL: '127.0.0.1:1433'
  536. };
  537. $('#sqlite_settings').hide();
  538. $('#sql_settings').show();
  539. $('#pgsql_settings').toggle(dbType === 'PostgreSQL');
  540. $('#mysql_settings').toggle(dbType === 'MySQL');
  541. $.each(dbDefaults, (_type, defaultHost) => {
  542. if ($('#db_host').val() === defaultHost) {
  543. $('#db_host').val(dbDefaults[dbType]);
  544. return false;
  545. }
  546. });
  547. });
  548. // TODO: better handling of exclusive relations.
  549. $('#offline-mode input').on('change', function () {
  550. if ($(this).is(':checked')) {
  551. $('#disable-gravatar').checkbox('check');
  552. $('#federated-avatar-lookup').checkbox('uncheck');
  553. }
  554. });
  555. $('#disable-gravatar input').on('change', function () {
  556. if ($(this).is(':checked')) {
  557. $('#federated-avatar-lookup').checkbox('uncheck');
  558. } else {
  559. $('#offline-mode').checkbox('uncheck');
  560. }
  561. });
  562. $('#federated-avatar-lookup input').on('change', function () {
  563. if ($(this).is(':checked')) {
  564. $('#disable-gravatar').checkbox('uncheck');
  565. $('#offline-mode').checkbox('uncheck');
  566. }
  567. });
  568. $('#enable-openid-signin input').on('change', function () {
  569. if ($(this).is(':checked')) {
  570. if (!$('#disable-registration input').is(':checked')) {
  571. $('#enable-openid-signup').checkbox('check');
  572. }
  573. } else {
  574. $('#enable-openid-signup').checkbox('uncheck');
  575. }
  576. });
  577. $('#disable-registration input').on('change', function () {
  578. if ($(this).is(':checked')) {
  579. $('#enable-captcha').checkbox('uncheck');
  580. $('#enable-openid-signup').checkbox('uncheck');
  581. } else {
  582. $('#enable-openid-signup').checkbox('check');
  583. }
  584. });
  585. $('#enable-captcha input').on('change', function () {
  586. if ($(this).is(':checked')) {
  587. $('#disable-registration').checkbox('uncheck');
  588. }
  589. });
  590. }
  591. function initIssueComments() {
  592. if ($('.repository.view.issue .timeline').length === 0) return;
  593. $('.re-request-review').on('click', function (event) {
  594. const url = $(this).data('update-url');
  595. const issueId = $(this).data('issue-id');
  596. const id = $(this).data('id');
  597. const isChecked = $(this).hasClass('checked');
  598. event.preventDefault();
  599. updateIssuesMeta(
  600. url,
  601. isChecked ? 'detach' : 'attach',
  602. issueId,
  603. id,
  604. ).then(reload);
  605. return false;
  606. });
  607. $(document).on('click', (event) => {
  608. const urlTarget = $(':target');
  609. if (urlTarget.length === 0) return;
  610. const urlTargetId = urlTarget.attr('id');
  611. if (!urlTargetId) return;
  612. if (!/^(issue|pull)(comment)?-\d+$/.test(urlTargetId)) return;
  613. const $target = $(event.target);
  614. if ($target.closest(`#${urlTargetId}`).length === 0) {
  615. const scrollPosition = $(window).scrollTop();
  616. window.location.hash = '';
  617. $(window).scrollTop(scrollPosition);
  618. window.history.pushState(null, null, ' ');
  619. }
  620. });
  621. }
  622. async function initRepository() {
  623. if ($('.repository').length === 0) {
  624. return;
  625. }
  626. function initFilterSearchDropdown(selector) {
  627. const $dropdown = $(selector);
  628. $dropdown.dropdown({
  629. fullTextSearch: true,
  630. selectOnKeydown: false,
  631. onChange(_text, _value, $choice) {
  632. if ($choice.data('url')) {
  633. window.location.href = $choice.data('url');
  634. }
  635. },
  636. message: {noResults: $dropdown.data('no-results')}
  637. });
  638. }
  639. // File list and commits
  640. if ($('.repository.file.list').length > 0 || ('.repository.commits').length > 0) {
  641. initFilterBranchTagDropdown('.choose.reference .dropdown');
  642. }
  643. // Wiki
  644. if ($('.repository.wiki.view').length > 0) {
  645. initFilterSearchDropdown('.choose.page .dropdown');
  646. }
  647. // Options
  648. if ($('.repository.settings.options').length > 0) {
  649. // Enable or select internal/external wiki system and issue tracker.
  650. $('.enable-system').on('change', function () {
  651. if (this.checked) {
  652. $($(this).data('target')).removeClass('disabled');
  653. if (!$(this).data('context')) $($(this).data('context')).addClass('disabled');
  654. } else {
  655. $($(this).data('target')).addClass('disabled');
  656. if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled');
  657. }
  658. });
  659. $('.enable-system-radio').on('change', function () {
  660. if (this.value === 'false') {
  661. $($(this).data('target')).addClass('disabled');
  662. if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).removeClass('disabled');
  663. } else if (this.value === 'true') {
  664. $($(this).data('target')).removeClass('disabled');
  665. if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).addClass('disabled');
  666. }
  667. });
  668. }
  669. // Labels
  670. if ($('.repository.labels').length > 0) {
  671. initLabelEdit();
  672. }
  673. // Milestones
  674. if ($('.repository.new.milestone').length > 0) {
  675. $('#clear-date').on('click', () => {
  676. $('#deadline').val('');
  677. return false;
  678. });
  679. }
  680. // Repo Creation
  681. if ($('.repository.new.repo').length > 0) {
  682. $('input[name="gitignores"], input[name="license"]').on('change', () => {
  683. const gitignores = $('input[name="gitignores"]').val();
  684. const license = $('input[name="license"]').val();
  685. if (gitignores || license) {
  686. $('input[name="auto_init"]').prop('checked', true);
  687. }
  688. });
  689. }
  690. // Issues
  691. if ($('.repository.view.issue').length > 0) {
  692. // Edit issue title
  693. const $issueTitle = $('#issue-title');
  694. const $editInput = $('#edit-title-input input');
  695. const editTitleToggle = function () {
  696. $issueTitle.toggle();
  697. $('.not-in-edit').toggle();
  698. $('#edit-title-input').toggle();
  699. $('#pull-desc').toggle();
  700. $('#pull-desc-edit').toggle();
  701. $('.in-edit').toggle();
  702. $('#issue-title-wrapper').toggleClass('edit-active');
  703. $editInput.focus();
  704. return false;
  705. };
  706. const changeBranchSelect = function () {
  707. const selectionTextField = $('#pull-target-branch');
  708. const baseName = selectionTextField.data('basename');
  709. const branchNameNew = $(this).data('branch');
  710. const branchNameOld = selectionTextField.data('branch');
  711. // Replace branch name to keep translation from HTML template
  712. selectionTextField.html(selectionTextField.html().replace(
  713. `${baseName}:${branchNameOld}`,
  714. `${baseName}:${branchNameNew}`
  715. ));
  716. selectionTextField.data('branch', branchNameNew); // update branch name in setting
  717. };
  718. $('#branch-select > .item').on('click', changeBranchSelect);
  719. $('#edit-title').on('click', editTitleToggle);
  720. $('#cancel-edit-title').on('click', editTitleToggle);
  721. $('#save-edit-title').on('click', editTitleToggle).on('click', function () {
  722. const pullrequest_targetbranch_change = function (update_url) {
  723. const targetBranch = $('#pull-target-branch').data('branch');
  724. const $branchTarget = $('#branch_target');
  725. if (targetBranch === $branchTarget.text()) {
  726. return false;
  727. }
  728. $.post(update_url, {
  729. _csrf: csrf,
  730. target_branch: targetBranch
  731. }).done((data) => {
  732. $branchTarget.text(data.base_branch);
  733. }).always(() => {
  734. reload();
  735. });
  736. };
  737. const pullrequest_target_update_url = $(this).data('target-update-url');
  738. if ($editInput.val().length === 0 || $editInput.val() === $issueTitle.text()) {
  739. $editInput.val($issueTitle.text());
  740. pullrequest_targetbranch_change(pullrequest_target_update_url);
  741. } else {
  742. $.post($(this).data('update-url'), {
  743. _csrf: csrf,
  744. title: $editInput.val()
  745. }, (data) => {
  746. $editInput.val(data.title);
  747. $issueTitle.text(data.title);
  748. pullrequest_targetbranch_change(pullrequest_target_update_url);
  749. reload();
  750. });
  751. }
  752. return false;
  753. });
  754. // Issue Comments
  755. initIssueComments();
  756. // Issue/PR Context Menus
  757. $('.context-dropdown').dropdown({
  758. action: 'hide'
  759. });
  760. // Quote reply
  761. $('.quote-reply').on('click', function (event) {
  762. $(this).closest('.dropdown').find('.menu').toggle('visible');
  763. const target = $(this).data('target');
  764. const quote = $(`#comment-${target}`).text().replace(/\n/g, '\n> ');
  765. const content = `> ${quote}\n\n`;
  766. let $content;
  767. if ($(this).hasClass('quote-reply-diff')) {
  768. const $parent = $(this).closest('.comment-code-cloud');
  769. $parent.find('button.comment-form-reply').trigger('click');
  770. $content = $parent.find('[name="content"]');
  771. if ($content.val() !== '') {
  772. $content.val(`${$content.val()}\n\n${content}`);
  773. } else {
  774. $content.val(`${content}`);
  775. }
  776. $content.focus();
  777. } else if (autoSimpleMDE !== null) {
  778. if (autoSimpleMDE.value() !== '') {
  779. autoSimpleMDE.value(`${autoSimpleMDE.value()}\n\n${content}`);
  780. } else {
  781. autoSimpleMDE.value(`${content}`);
  782. }
  783. }
  784. event.preventDefault();
  785. });
  786. // Edit issue or comment content
  787. $('.edit-content').on('click', async function (event) {
  788. $(this).closest('.dropdown').find('.menu').toggle('visible');
  789. const $segment = $(this).closest('.header').next();
  790. const $editContentZone = $segment.find('.edit-content-zone');
  791. const $renderContent = $segment.find('.render-content');
  792. const $rawContent = $segment.find('.raw-content');
  793. let $textarea;
  794. let $simplemde;
  795. // Setup new form
  796. if ($editContentZone.html().length === 0) {
  797. $editContentZone.html($('#edit-content-form').html());
  798. $textarea = $editContentZone.find('textarea');
  799. attachTribute($textarea.get(), {mentions: true, emoji: true});
  800. let dz;
  801. const $dropzone = $editContentZone.find('.dropzone');
  802. const $files = $editContentZone.find('.comment-files');
  803. if ($dropzone.length > 0) {
  804. $dropzone.data('saved', false);
  805. const filenameDict = {};
  806. dz = await createDropzone($dropzone[0], {
  807. url: $dropzone.data('upload-url'),
  808. headers: {'X-Csrf-Token': csrf},
  809. maxFiles: $dropzone.data('max-file'),
  810. maxFilesize: $dropzone.data('max-size'),
  811. acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'),
  812. addRemoveLinks: true,
  813. dictDefaultMessage: $dropzone.data('default-message'),
  814. dictInvalidFileType: $dropzone.data('invalid-input-type'),
  815. dictFileTooBig: $dropzone.data('file-too-big'),
  816. dictRemoveFile: $dropzone.data('remove-file'),
  817. timeout: 0,
  818. init() {
  819. this.on('success', (file, data) => {
  820. filenameDict[file.name] = {
  821. uuid: data.uuid,
  822. submitted: false
  823. };
  824. const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
  825. $files.append(input);
  826. });
  827. this.on('removedfile', (file) => {
  828. if (!(file.name in filenameDict)) {
  829. return;
  830. }
  831. $(`#${filenameDict[file.name].uuid}`).remove();
  832. if ($dropzone.data('remove-url') && !filenameDict[file.name].submitted) {
  833. $.post($dropzone.data('remove-url'), {
  834. file: filenameDict[file.name].uuid,
  835. _csrf: csrf,
  836. });
  837. }
  838. });
  839. this.on('submit', () => {
  840. $.each(filenameDict, (name) => {
  841. filenameDict[name].submitted = true;
  842. });
  843. });
  844. this.on('reload', () => {
  845. $.getJSON($editContentZone.data('attachment-url'), (data) => {
  846. dz.removeAllFiles(true);
  847. $files.empty();
  848. $.each(data, function () {
  849. const imgSrc = `${$dropzone.data('link-url')}/${this.uuid}`;
  850. dz.emit('addedfile', this);
  851. dz.emit('thumbnail', this, imgSrc);
  852. dz.emit('complete', this);
  853. dz.files.push(this);
  854. filenameDict[this.name] = {
  855. submitted: true,
  856. uuid: this.uuid
  857. };
  858. $dropzone.find(`img[src='${imgSrc}']`).css('max-width', '100%');
  859. const input = $(`<input id="${this.uuid}" name="files" type="hidden">`).val(this.uuid);
  860. $files.append(input);
  861. });
  862. });
  863. });
  864. }
  865. });
  866. dz.emit('reload');
  867. }
  868. // Give new write/preview data-tab name to distinguish from others
  869. const $editContentForm = $editContentZone.find('.ui.comment.form');
  870. const $tabMenu = $editContentForm.find('.tabular.menu');
  871. $tabMenu.attr('data-write', $editContentZone.data('write'));
  872. $tabMenu.attr('data-preview', $editContentZone.data('preview'));
  873. $tabMenu.find('.write.item').attr('data-tab', $editContentZone.data('write'));
  874. $tabMenu.find('.preview.item').attr('data-tab', $editContentZone.data('preview'));
  875. $editContentForm.find('.write').attr('data-tab', $editContentZone.data('write'));
  876. $editContentForm.find('.preview').attr('data-tab', $editContentZone.data('preview'));
  877. $simplemde = setCommentSimpleMDE($textarea);
  878. commentMDEditors[$editContentZone.data('write')] = $simplemde;
  879. initCommentPreviewTab($editContentForm);
  880. initSimpleMDEImagePaste($simplemde, $files);
  881. $editContentZone.find('.cancel.button').on('click', () => {
  882. $renderContent.show();
  883. $editContentZone.hide();
  884. if (dz) {
  885. dz.emit('reload');
  886. }
  887. });
  888. $editContentZone.find('.save.button').on('click', () => {
  889. $renderContent.show();
  890. $editContentZone.hide();
  891. const $attachments = $files.find('[name=files]').map(function () {
  892. return $(this).val();
  893. }).get();
  894. $.post($editContentZone.data('update-url'), {
  895. _csrf: csrf,
  896. content: $textarea.val(),
  897. context: $editContentZone.data('context'),
  898. files: $attachments
  899. }, (data) => {
  900. if (data.length === 0 || data.content.length === 0) {
  901. $renderContent.html($('#no-content').html());
  902. } else {
  903. $renderContent.html(data.content);
  904. }
  905. const $content = $segment;
  906. if (!$content.find('.dropzone-attachments').length) {
  907. if (data.attachments !== '') {
  908. $content.append(`
  909. <div class="dropzone-attachments">
  910. <div class="ui clearing divider"></div>
  911. <div class="ui middle aligned padded grid">
  912. </div>
  913. </div>
  914. `);
  915. $content.find('.dropzone-attachments .grid').html(data.attachments);
  916. }
  917. } else if (data.attachments === '') {
  918. $content.find('.dropzone-attachments').remove();
  919. } else {
  920. $content.find('.dropzone-attachments .grid').html(data.attachments);
  921. }
  922. if (dz) {
  923. dz.emit('submit');
  924. dz.emit('reload');
  925. }
  926. renderMarkdownContent();
  927. });
  928. });
  929. } else {
  930. $textarea = $segment.find('textarea');
  931. $simplemde = commentMDEditors[$editContentZone.data('write')];
  932. }
  933. // Show write/preview tab and copy raw content as needed
  934. $editContentZone.show();
  935. $renderContent.hide();
  936. if ($textarea.val().length === 0) {
  937. $textarea.val($rawContent.text());
  938. $simplemde.value($rawContent.text());
  939. }
  940. $textarea.focus();
  941. $simplemde.codemirror.focus();
  942. event.preventDefault();
  943. });
  944. // Delete comment
  945. $('.delete-comment').on('click', function () {
  946. const $this = $(this);
  947. if (window.confirm($this.data('locale'))) {
  948. $.post($this.data('url'), {
  949. _csrf: csrf
  950. }).done(() => {
  951. $(`#${$this.data('comment-id')}`).remove();
  952. });
  953. }
  954. return false;
  955. });
  956. // Change status
  957. const $statusButton = $('#status-button');
  958. $('#comment-form .edit_area').on('keyup', function () {
  959. if ($(this).val().length === 0) {
  960. $statusButton.text($statusButton.data('status'));
  961. } else {
  962. $statusButton.text($statusButton.data('status-and-comment'));
  963. }
  964. });
  965. $statusButton.on('click', () => {
  966. $('#status').val($statusButton.data('status-val'));
  967. $('#comment-form').trigger('submit');
  968. });
  969. // Pull Request merge button
  970. const $mergeButton = $('.merge-button > button');
  971. $mergeButton.on('click', function (e) {
  972. e.preventDefault();
  973. $(`.${$(this).data('do')}-fields`).show();
  974. $(this).parent().hide();
  975. });
  976. $('.merge-button > .dropdown').dropdown({
  977. onChange(_text, _value, $choice) {
  978. if ($choice.data('do')) {
  979. $mergeButton.find('.button-text').text($choice.text());
  980. $mergeButton.data('do', $choice.data('do'));
  981. }
  982. }
  983. });
  984. $('.merge-cancel').on('click', function (e) {
  985. e.preventDefault();
  986. $(this).closest('.form').hide();
  987. $mergeButton.parent().show();
  988. });
  989. initReactionSelector();
  990. }
  991. // Diff
  992. if ($('.repository.diff').length > 0) {
  993. $('.diff-counter').each(function () {
  994. const $item = $(this);
  995. const addLine = $item.find('span[data-line].add').data('line');
  996. const delLine = $item.find('span[data-line].del').data('line');
  997. const addPercent = parseFloat(addLine) / (parseFloat(addLine) + parseFloat(delLine)) * 100;
  998. $item.find('.bar .add').css('width', `${addPercent}%`);
  999. });
  1000. }
  1001. // Quick start and repository home
  1002. $('#repo-clone-ssh').on('click', function () {
  1003. $('.clone-url').text($(this).data('link'));
  1004. $('#repo-clone-url').val($(this).data('link'));
  1005. $(this).addClass('blue');
  1006. $('#repo-clone-https').removeClass('blue');
  1007. localStorage.setItem('repo-clone-protocol', 'ssh');
  1008. });
  1009. $('#repo-clone-https').on('click', function () {
  1010. $('.clone-url').text($(this).data('link'));
  1011. $('#repo-clone-url').val($(this).data('link'));
  1012. $(this).addClass('blue');
  1013. if ($('#repo-clone-ssh').length > 0) {
  1014. $('#repo-clone-ssh').removeClass('blue');
  1015. localStorage.setItem('repo-clone-protocol', 'https');
  1016. }
  1017. });
  1018. $('#repo-clone-url').on('click', function () {
  1019. $(this).select();
  1020. });
  1021. // Pull request
  1022. const $repoComparePull = $('.repository.compare.pull');
  1023. if ($repoComparePull.length > 0) {
  1024. initFilterSearchDropdown('.choose.branch .dropdown');
  1025. // show pull request form
  1026. $repoComparePull.find('button.show-form').on('click', function (e) {
  1027. e.preventDefault();
  1028. $repoComparePull.find('.pullrequest-form').show();
  1029. autoSimpleMDE.codemirror.refresh();
  1030. $(this).parent().hide();
  1031. });
  1032. }
  1033. // Branches
  1034. if ($('.repository.settings.branches').length > 0) {
  1035. initFilterSearchDropdown('.protected-branches .dropdown');
  1036. $('.enable-protection, .enable-whitelist, .enable-statuscheck').on('change', function () {
  1037. if (this.checked) {
  1038. $($(this).data('target')).removeClass('disabled');
  1039. } else {
  1040. $($(this).data('target')).addClass('disabled');
  1041. }
  1042. });
  1043. $('.disable-whitelist').on('change', function () {
  1044. if (this.checked) {
  1045. $($(this).data('target')).addClass('disabled');
  1046. }
  1047. });
  1048. }
  1049. // Language stats
  1050. if ($('.language-stats').length > 0) {
  1051. $('.language-stats').on('click', (e) => {
  1052. e.preventDefault();
  1053. $('.language-stats-details, .repository-menu').slideToggle();
  1054. });
  1055. }
  1056. }
  1057. function initPullRequestReview() {
  1058. if (window.location.hash && window.location.hash.startsWith('#issuecomment-')) {
  1059. const commentDiv = $(window.location.hash);
  1060. if (commentDiv) {
  1061. // get the name of the parent id
  1062. const groupID = commentDiv.closest('div[id^="code-comments-"]').attr('id');
  1063. if (groupID && groupID.startsWith('code-comments-')) {
  1064. const id = groupID.substr(14);
  1065. $(`#show-outdated-${id}`).addClass('hide');
  1066. $(`#code-comments-${id}`).removeClass('hide');
  1067. $(`#code-preview-${id}`).removeClass('hide');
  1068. $(`#hide-outdated-${id}`).removeClass('hide');
  1069. $(window).scrollTop(commentDiv.offset().top);
  1070. }
  1071. }
  1072. }
  1073. $('.show-outdated').on('click', function (e) {
  1074. e.preventDefault();
  1075. const id = $(this).data('comment');
  1076. $(this).addClass('hide');
  1077. $(`#code-comments-${id}`).removeClass('hide');
  1078. $(`#code-preview-${id}`).removeClass('hide');
  1079. $(`#hide-outdated-${id}`).removeClass('hide');
  1080. });
  1081. $('.hide-outdated').on('click', function (e) {
  1082. e.preventDefault();
  1083. const id = $(this).data('comment');
  1084. $(this).addClass('hide');
  1085. $(`#code-comments-${id}`).addClass('hide');
  1086. $(`#code-preview-${id}`).addClass('hide');
  1087. $(`#show-outdated-${id}`).removeClass('hide');
  1088. });
  1089. $('button.comment-form-reply').on('click', function (e) {
  1090. e.preventDefault();
  1091. $(this).hide();
  1092. const form = $(this).parent().find('.comment-form');
  1093. form.removeClass('hide');
  1094. const $textarea = form.find('textarea');
  1095. let $simplemde;
  1096. if ($textarea.data('simplemde')) {
  1097. $simplemde = $textarea.data('simplemde');
  1098. } else {
  1099. attachTribute($textarea.get(), {mentions: true, emoji: true});
  1100. $simplemde = setCommentSimpleMDE($textarea);
  1101. $textarea.data('simplemde', $simplemde);
  1102. }
  1103. $textarea.focus();
  1104. $simplemde.codemirror.focus();
  1105. assingMenuAttributes(form.find('.menu'));
  1106. });
  1107. // The following part is only for diff views
  1108. if ($('.repository.pull.diff').length === 0) {
  1109. return;
  1110. }
  1111. $('.btn-review').on('click', function (e) {
  1112. e.preventDefault();
  1113. $(this).closest('.dropdown').find('.menu').toggle('visible');
  1114. }).closest('.dropdown').find('.link.close')
  1115. .on('click', function (e) {
  1116. e.preventDefault();
  1117. $(this).closest('.menu').toggle('visible');
  1118. });
  1119. $('.add-code-comment').on('click', function (e) {
  1120. if ($(e.target).hasClass('btn-add-single')) return; // https://github.com/go-gitea/gitea/issues/4745
  1121. e.preventDefault();
  1122. const isSplit = $(this).closest('.code-diff').hasClass('code-diff-split');
  1123. const side = $(this).data('side');
  1124. const idx = $(this).data('idx');
  1125. const path = $(this).data('path');
  1126. const form = $('#pull_review_add_comment').html();
  1127. const tr = $(this).closest('tr');
  1128. const oldLineNum = tr.find('.lines-num-old').data('line-num');
  1129. const newLineNum = tr.find('.lines-num-new').data('line-num');
  1130. const addCommentKey = `${oldLineNum}|${newLineNum}`;
  1131. if (document.querySelector(`[data-add-comment-key="${addCommentKey}"]`)) return; // don't add same comment box twice
  1132. let ntr = tr.next();
  1133. if (!ntr.hasClass('add-comment')) {
  1134. ntr = $(`
  1135. <tr class="add-comment" data-add-comment-key="${addCommentKey}">
  1136. ${isSplit ? `
  1137. <td class="lines-num"></td>
  1138. <td class="lines-type-marker"></td>
  1139. <td class="add-comment-left"></td>
  1140. <td class="lines-num"></td>
  1141. <td class="lines-type-marker"></td>
  1142. <td class="add-comment-right"></td>
  1143. ` : `
  1144. <td class="lines-num"></td>
  1145. <td class="lines-num"></td>
  1146. <td class="add-comment-left add-comment-right" colspan="2"></td>
  1147. `}
  1148. </tr>`);
  1149. tr.after(ntr);
  1150. }
  1151. const td = ntr.find(`.add-comment-${side}`);
  1152. let commentCloud = td.find('.comment-code-cloud');
  1153. if (commentCloud.length === 0) {
  1154. td.html(form);
  1155. commentCloud = td.find('.comment-code-cloud');
  1156. assingMenuAttributes(commentCloud.find('.menu'));
  1157. td.find("input[name='line']").val(idx);
  1158. td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed');
  1159. td.find("input[name='path']").val(path);
  1160. }
  1161. const $textarea = commentCloud.find('textarea');
  1162. attachTribute($textarea.get(), {mentions: true, emoji: true});
  1163. const $simplemde = setCommentSimpleMDE($textarea);
  1164. $textarea.focus();
  1165. $simplemde.codemirror.focus();
  1166. });
  1167. }
  1168. function assingMenuAttributes(menu) {
  1169. const id = Math.floor(Math.random() * Math.floor(1000000));
  1170. menu.attr('data-write', menu.attr('data-write') + id);
  1171. menu.attr('data-preview', menu.attr('data-preview') + id);
  1172. menu.find('.item').each(function () {
  1173. const tab = $(this).attr('data-tab') + id;
  1174. $(this).attr('data-tab', tab);
  1175. });
  1176. menu.parent().find("*[data-tab='write']").attr('data-tab', `write${id}`);
  1177. menu.parent().find("*[data-tab='preview']").attr('data-tab', `preview${id}`);
  1178. initCommentPreviewTab(menu.parent('.form'));
  1179. return id;
  1180. }
  1181. function initRepositoryCollaboration() {
  1182. // Change collaborator access mode
  1183. $('.access-mode.menu .item').on('click', function () {
  1184. const $menu = $(this).parent();
  1185. $.post($menu.data('url'), {
  1186. _csrf: csrf,
  1187. uid: $menu.data('uid'),
  1188. mode: $(this).data('value')
  1189. });
  1190. });
  1191. }
  1192. function initTeamSettings() {
  1193. // Change team access mode
  1194. $('.organization.new.team input[name=permission]').on('change', () => {
  1195. const val = $('input[name=permission]:checked', '.organization.new.team').val();
  1196. if (val === 'admin') {
  1197. $('.organization.new.team .team-units').hide();
  1198. } else {
  1199. $('.organization.new.team .team-units').show();
  1200. }
  1201. });
  1202. }
  1203. function initWikiForm() {
  1204. const $editArea = $('.repository.wiki textarea#edit_area');
  1205. let sideBySideChanges = 0;
  1206. let sideBySideTimeout = null;
  1207. if ($editArea.length > 0) {
  1208. const simplemde = new SimpleMDE({
  1209. autoDownloadFontAwesome: false,
  1210. element: $editArea[0],
  1211. forceSync: true,
  1212. previewRender(plainText, preview) { // Async method
  1213. // FIXME: still send render request when return back to edit mode
  1214. const render = function () {
  1215. sideBySideChanges = 0;
  1216. if (sideBySideTimeout !== null) {
  1217. clearTimeout(sideBySideTimeout);
  1218. sideBySideTimeout = null;
  1219. }
  1220. $.post($editArea.data('url'), {
  1221. _csrf: csrf,
  1222. mode: 'gfm',
  1223. context: $editArea.data('context'),
  1224. text: plainText,
  1225. wiki: true
  1226. }, (data) => {
  1227. preview.innerHTML = `<div class="markdown ui segment">${data}</div>`;
  1228. renderMarkdownContent();
  1229. });
  1230. };
  1231. setTimeout(() => {
  1232. if (!simplemde.isSideBySideActive()) {
  1233. render();
  1234. } else {
  1235. // delay preview by keystroke counting
  1236. sideBySideChanges++;
  1237. if (sideBySideChanges > 10) {
  1238. render();
  1239. }
  1240. // or delay preview by timeout
  1241. if (sideBySideTimeout !== null) {
  1242. clearTimeout(sideBySideTimeout);
  1243. sideBySideTimeout = null;
  1244. }
  1245. sideBySideTimeout = setTimeout(render, 600);
  1246. }
  1247. }, 0);
  1248. if (!simplemde.isSideBySideActive()) {
  1249. return 'Loading...';
  1250. }
  1251. return preview.innerHTML;
  1252. },
  1253. renderingConfig: {
  1254. singleLineBreaks: false
  1255. },
  1256. indentWithTabs: false,
  1257. tabSize: 4,
  1258. spellChecker: false,
  1259. toolbar: ['bold', 'italic', 'strikethrough', '|',
  1260. 'heading-1', 'heading-2', 'heading-3', 'heading-bigger', 'heading-smaller', '|',
  1261. {
  1262. name: 'code-inline',
  1263. action(e) {
  1264. const cm = e.codemirror;
  1265. const selection = cm.getSelection();
  1266. cm.replaceSelection(`\`${selection}\``);
  1267. if (!selection) {
  1268. const cursorPos = cm.getCursor();
  1269. cm.setCursor(cursorPos.line, cursorPos.ch - 1);
  1270. }
  1271. cm.focus();
  1272. },
  1273. className: 'fa fa-angle-right',
  1274. title: 'Add Inline Code',
  1275. }, 'code', 'quote', '|', {
  1276. name: 'checkbox-empty',
  1277. action(e) {
  1278. const cm = e.codemirror;
  1279. cm.replaceSelection(`\n- [ ] ${cm.getSelection()}`);
  1280. cm.focus();
  1281. },
  1282. className: 'fa fa-square-o',
  1283. title: 'Add Checkbox (empty)',
  1284. },
  1285. {
  1286. name: 'checkbox-checked',
  1287. action(e) {
  1288. const cm = e.codemirror;
  1289. cm.replaceSelection(`\n- [x] ${cm.getSelection()}`);
  1290. cm.focus();
  1291. },
  1292. className: 'fa fa-check-square-o',
  1293. title: 'Add Checkbox (checked)',
  1294. }, '|',
  1295. 'unordered-list', 'ordered-list', '|',
  1296. 'link', 'image', 'table', 'horizontal-rule', '|',
  1297. 'clean-block', 'preview', 'fullscreen', 'side-by-side', '|',
  1298. {
  1299. name: 'revert-to-textarea',
  1300. action(e) {
  1301. e.toTextArea();
  1302. },
  1303. className: 'fa fa-file',
  1304. title: 'Revert to simple textarea',
  1305. },
  1306. ]
  1307. });
  1308. $(simplemde.codemirror.getInputField()).addClass('js-quick-submit');
  1309. setTimeout(() => {
  1310. const $bEdit = $('.repository.wiki.new .previewtabs a[data-tab="write"]');
  1311. const $bPrev = $('.repository.wiki.new .previewtabs a[data-tab="preview"]');
  1312. const $toolbar = $('.editor-toolbar');
  1313. const $bPreview = $('.editor-toolbar a.fa-eye');
  1314. const $bSideBySide = $('.editor-toolbar a.fa-columns');
  1315. $bEdit.on('click', () => {
  1316. if ($toolbar.hasClass('disabled-for-preview')) {
  1317. $bPreview.trigger('click');
  1318. }
  1319. });
  1320. $bPrev.on('click', () => {
  1321. if (!$toolbar.hasClass('disabled-for-preview')) {
  1322. $bPreview.trigger('click');
  1323. }
  1324. });
  1325. $bPreview.on('click', () => {
  1326. setTimeout(() => {
  1327. if ($toolbar.hasClass('disabled-for-preview')) {
  1328. if ($bEdit.hasClass('active')) {
  1329. $bEdit.removeClass('active');
  1330. }
  1331. if (!$bPrev.hasClass('active')) {
  1332. $bPrev.addClass('active');
  1333. }
  1334. } else {
  1335. if (!$bEdit.hasClass('active')) {
  1336. $bEdit.addClass('active');
  1337. }
  1338. if ($bPrev.hasClass('active')) {
  1339. $bPrev.removeClass('active');
  1340. }
  1341. }
  1342. }, 0);
  1343. });
  1344. $bSideBySide.on('click', () => {
  1345. sideBySideChanges = 10;
  1346. });
  1347. }, 0);
  1348. }
  1349. }
  1350. // Adding function to get the cursor position in a text field to jQuery object.
  1351. $.fn.getCursorPosition = function () {
  1352. const el = $(this).get(0);
  1353. let pos = 0;
  1354. if ('selectionStart' in el) {
  1355. pos = el.selectionStart;
  1356. } else if ('selection' in document) {
  1357. el.focus();
  1358. const Sel = document.selection.createRange();
  1359. const SelLength = document.selection.createRange().text.length;
  1360. Sel.moveStart('character', -el.value.length);
  1361. pos = Sel.text.length - SelLength;
  1362. }
  1363. return pos;
  1364. };
  1365. function setCommentSimpleMDE($editArea) {
  1366. const simplemde = new SimpleMDE({
  1367. autoDownloadFontAwesome: false,
  1368. element: $editArea[0],
  1369. forceSync: true,
  1370. renderingConfig: {
  1371. singleLineBreaks: false
  1372. },
  1373. indentWithTabs: false,
  1374. tabSize: 4,
  1375. spellChecker: false,
  1376. toolbar: ['bold', 'italic', 'strikethrough', '|',
  1377. 'heading-1', 'heading-2', 'heading-3', 'heading-bigger', 'heading-smaller', '|',
  1378. 'code', 'quote', '|', {
  1379. name: 'checkbox-empty',
  1380. action(e) {
  1381. const cm = e.codemirror;
  1382. cm.replaceSelection(`\n- [ ] ${cm.getSelection()}`);
  1383. cm.focus();
  1384. },
  1385. className: 'fa fa-square-o',
  1386. title: 'Add Checkbox (empty)',
  1387. },
  1388. {
  1389. name: 'checkbox-checked',
  1390. action(e) {
  1391. const cm = e.codemirror;
  1392. cm.replaceSelection(`\n- [x] ${cm.getSelection()}`);
  1393. cm.focus();
  1394. },
  1395. className: 'fa fa-check-square-o',
  1396. title: 'Add Checkbox (checked)',
  1397. }, '|',
  1398. 'unordered-list', 'ordered-list', '|',
  1399. 'link', 'image', 'table', 'horizontal-rule', '|',
  1400. 'clean-block', '|',
  1401. {
  1402. name: 'revert-to-textarea',
  1403. action(e) {
  1404. e.toTextArea();
  1405. },
  1406. className: 'fa fa-file',
  1407. title: 'Revert to simple textarea',
  1408. },
  1409. ]
  1410. });
  1411. $(simplemde.codemirror.getInputField()).addClass('js-quick-submit');
  1412. simplemde.codemirror.setOption('extraKeys', {
  1413. Enter: () => {
  1414. const tributeContainer = document.querySelector('.tribute-container');
  1415. if (!tributeContainer || tributeContainer.style.display === 'none') {
  1416. return CodeMirror.Pass;
  1417. }
  1418. },
  1419. Backspace: (cm) => {
  1420. if (cm.getInputField().trigger) {
  1421. cm.getInputField().trigger('input');
  1422. }
  1423. cm.execCommand('delCharBefore');
  1424. }
  1425. });
  1426. attachTribute(simplemde.codemirror.getInputField(), {mentions: true, emoji: true});
  1427. return simplemde;
  1428. }
  1429. async function initEditor() {
  1430. $('.js-quick-pull-choice-option').on('change', function () {
  1431. if ($(this).val() === 'commit-to-new-branch') {
  1432. $('.quick-pull-branch-name').show();
  1433. $('.quick-pull-branch-name input').prop('required', true);
  1434. } else {
  1435. $('.quick-pull-branch-name').hide();
  1436. $('.quick-pull-branch-name input').prop('required', false);
  1437. }
  1438. $('#commit-button').text($(this).attr('button_text'));
  1439. });
  1440. const $editFilename = $('#file-name');
  1441. $editFilename.on('keyup', function (e) {
  1442. const $section = $('.breadcrumb span.section');
  1443. const $divider = $('.breadcrumb div.divider');
  1444. let value;
  1445. let parts;
  1446. if (e.keyCode === 8) {
  1447. if ($(this).getCursorPosition() === 0) {
  1448. if ($section.length > 0) {
  1449. value = $section.last().find('a').text();
  1450. $(this).val(value + $(this).val());
  1451. $(this)[0].setSelectionRange(value.length, value.length);
  1452. $section.last().remove();
  1453. $divider.last().remove();
  1454. }
  1455. }
  1456. }
  1457. if (e.keyCode === 191) {
  1458. parts = $(this).val().split('/');
  1459. for (let i = 0; i < parts.length; ++i) {
  1460. value = parts[i];
  1461. if (i < parts.length - 1) {
  1462. if (value.length) {
  1463. $(`<span class="section"><a href="#">${value}</a></span>`).insertBefore($(this));
  1464. $('<div class="divider"> / </div>').insertBefore($(this));
  1465. }
  1466. } else {
  1467. $(this).val(value);
  1468. }
  1469. $(this)[0].setSelectionRange(0, 0);
  1470. }
  1471. }
  1472. parts = [];
  1473. $('.breadcrumb span.section').each(function () {
  1474. const element = $(this);
  1475. if (element.find('a').length) {
  1476. parts.push(element.find('a').text());
  1477. } else {
  1478. parts.push(element.text());
  1479. }
  1480. });
  1481. if ($(this).val()) parts.push($(this).val());
  1482. $('#tree_path').val(parts.join('/'));
  1483. }).trigger('keyup');
  1484. const $editArea = $('.repository.editor textarea#edit_area');
  1485. if (!$editArea.length) return;
  1486. await createCodeEditor($editArea[0], $editFilename[0], previewFileModes);
  1487. // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
  1488. // to enable or disable the commit button
  1489. const $commitButton = $('#commit-button');
  1490. const $editForm = $('.ui.edit.form');
  1491. const dirtyFileClass = 'dirty-file';
  1492. // Disabling the button at the start
  1493. if ($('input[name="page_has_posted"]').val() !== 'true') {
  1494. $commitButton.prop('disabled', true);
  1495. }
  1496. // Registering a custom listener for the file path and the file content
  1497. $editForm.areYouSure({
  1498. silent: true,
  1499. dirtyClass: dirtyFileClass,
  1500. fieldSelector: ':input:not(.commit-form-wrapper :input)',
  1501. change() {
  1502. const dirty = $(this).hasClass(dirtyFileClass);
  1503. $commitButton.prop('disabled', !dirty);
  1504. }
  1505. });
  1506. $commitButton.on('click', (event) => {
  1507. // A modal which asks if an empty file should be committed
  1508. if ($editArea.val().length === 0) {
  1509. $('#edit-empty-content-modal').modal({
  1510. onApprove() {
  1511. $('.edit.form').trigger('submit');
  1512. }
  1513. }).modal('show');
  1514. event.preventDefault();
  1515. }
  1516. });
  1517. }
  1518. function initOrganization() {
  1519. if ($('.organization').length === 0) {
  1520. return;
  1521. }
  1522. // Options
  1523. if ($('.organization.settings.options').length > 0) {
  1524. $('#org_name').on('keyup', function () {
  1525. const $prompt = $('#org-name-change-prompt');
  1526. if ($(this).val().toString().toLowerCase() !== $(this).data('org-name').toString().toLowerCase()) {
  1527. $prompt.show();
  1528. } else {
  1529. $prompt.hide();
  1530. }
  1531. });
  1532. }
  1533. // Labels
  1534. if ($('.organization.settings.labels').length > 0) {
  1535. initLabelEdit();
  1536. }
  1537. }
  1538. function initUserSettings() {
  1539. // Options
  1540. if ($('.user.settings.profile').length > 0) {
  1541. $('#username').on('keyup', function () {
  1542. const $prompt = $('#name-change-prompt');
  1543. if ($(this).val().toString().toLowerCase() !== $(this).data('name').toString().toLowerCase()) {
  1544. $prompt.show();
  1545. } else {
  1546. $prompt.hide();
  1547. }
  1548. });
  1549. }
  1550. }
  1551. function initGithook() {
  1552. if ($('.edit.githook').length === 0) {
  1553. return;
  1554. }
  1555. CodeMirror.autoLoadMode(CodeMirror.fromTextArea($('#content')[0], {
  1556. lineNumbers: true,
  1557. mode: 'shell'
  1558. }), 'shell');
  1559. }
  1560. function initWebhook() {
  1561. if ($('.new.webhook').length === 0) {
  1562. return;
  1563. }
  1564. $('.events.checkbox input').on('change', function () {
  1565. if ($(this).is(':checked')) {
  1566. $('.events.fields').show();
  1567. }
  1568. });
  1569. $('.non-events.checkbox input').on('change', function () {
  1570. if ($(this).is(':checked')) {
  1571. $('.events.fields').hide();
  1572. }
  1573. });
  1574. const updateContentType = function () {
  1575. const visible = $('#http_method').val() === 'POST';
  1576. $('#content_type').parent().parent()[visible ? 'show' : 'hide']();
  1577. };
  1578. updateContentType();
  1579. $('#http_method').on('change', () => {
  1580. updateContentType();
  1581. });
  1582. // Test delivery
  1583. $('#test-delivery').on('click', function () {
  1584. const $this = $(this);
  1585. $this.addClass('loading disabled');
  1586. $.post($this.data('link'), {
  1587. _csrf: csrf
  1588. }).done(
  1589. setTimeout(() => {
  1590. window.location.href = $this.data('redirect');
  1591. }, 5000)
  1592. );
  1593. });
  1594. }
  1595. function initAdmin() {
  1596. if ($('.admin').length === 0) {
  1597. return;
  1598. }
  1599. // New user
  1600. if ($('.admin.new.user').length > 0 || $('.admin.edit.user').length > 0) {
  1601. $('#login_type').on('change', function () {
  1602. if ($(this).val().substring(0, 1) === '0') {
  1603. $('#login_name').removeAttr('required');
  1604. $('.non-local').hide();
  1605. $('.local').show();
  1606. $('#user_name').focus();
  1607. if ($(this).data('password') === 'required') {
  1608. $('#password').attr('required', 'required');
  1609. }
  1610. } else {
  1611. $('#login_name').attr('required', 'required');
  1612. $('.non-local').show();
  1613. $('.local').hide();
  1614. $('#login_name').focus();
  1615. $('#password').removeAttr('required');
  1616. }
  1617. });
  1618. }
  1619. function onSecurityProtocolChange() {
  1620. if ($('#security_protocol').val() > 0) {
  1621. $('.has-tls').show();
  1622. } else {
  1623. $('.has-tls').hide();
  1624. }
  1625. }
  1626. function onUsePagedSearchChange() {
  1627. if ($('#use_paged_search').prop('checked')) {
  1628. $('.search-page-size').show()
  1629. .find('input').attr('required', 'required');
  1630. } else {
  1631. $('.search-page-size').hide()
  1632. .find('input').removeAttr('required');
  1633. }
  1634. }
  1635. function onOAuth2Change() {
  1636. $('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url').hide();
  1637. $('.open_id_connect_auto_discovery_url input[required]').removeAttr('required');
  1638. const provider = $('#oauth2_provider').val();
  1639. switch (provider) {
  1640. case 'github':
  1641. case 'gitlab':
  1642. case 'gitea':
  1643. case 'nextcloud':
  1644. case 'mastodon':
  1645. $('.oauth2_use_custom_url').show();
  1646. break;
  1647. case 'openidConnect':
  1648. $('.open_id_connect_auto_discovery_url input').attr('required', 'required');
  1649. $('.open_id_connect_auto_discovery_url').show();
  1650. break;
  1651. }
  1652. onOAuth2UseCustomURLChange();
  1653. }
  1654. function onOAuth2UseCustomURLChange() {
  1655. const provider = $('#oauth2_provider').val();
  1656. $('.oauth2_use_custom_url_field').hide();
  1657. $('.oauth2_use_custom_url_field input[required]').removeAttr('required');
  1658. if ($('#oauth2_use_custom_url').is(':checked')) {
  1659. $('#oauth2_token_url').val($(`#${provider}_token_url`).val());
  1660. $('#oauth2_auth_url').val($(`#${provider}_auth_url`).val());
  1661. $('#oauth2_profile_url').val($(`#${provider}_profile_url`).val());
  1662. $('#oauth2_email_url').val($(`#${provider}_email_url`).val());
  1663. switch (provider) {
  1664. case 'github':
  1665. $('.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input, .oauth2_email_url input').attr('required', 'required');
  1666. $('.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url, .oauth2_email_url').show();
  1667. break;
  1668. case 'nextcloud':
  1669. case 'gitea':
  1670. case 'gitlab':
  1671. $('.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input').attr('required', 'required');
  1672. $('.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url').show();
  1673. $('#oauth2_email_url').val('');
  1674. break;
  1675. case 'mastodon':
  1676. $('.oauth2_auth_url input').attr('required', 'required');
  1677. $('.oauth2_auth_url').show();
  1678. break;
  1679. }
  1680. }
  1681. }
  1682. function onVerifyGroupMembershipChange() {
  1683. if ($('#groups_enabled').is(':checked')) {
  1684. $('#groups_enabled_change').show();
  1685. } else {
  1686. $('#groups_enabled_change').hide();
  1687. }
  1688. }
  1689. // New authentication
  1690. if ($('.admin.new.authentication').length > 0) {
  1691. $('#auth_type').on('change', function () {
  1692. $('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi').hide();
  1693. $('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required], .sspi input[required]').removeAttr('required');
  1694. $('.binddnrequired').removeClass('required');
  1695. const authType = $(this).val();
  1696. switch (authType) {
  1697. case '2': // LDAP
  1698. $('.ldap').show();
  1699. $('.binddnrequired input, .ldap div.required:not(.dldap) input').attr('required', 'required');
  1700. $('.binddnrequired').addClass('required');
  1701. break;
  1702. case '3': // SMTP
  1703. $('.smtp').show();
  1704. $('.has-tls').show();
  1705. $('.smtp div.required input, .has-tls').attr('required', 'required');
  1706. break;
  1707. case '4': // PAM
  1708. $('.pam').show();
  1709. $('.pam input').attr('required', 'required');
  1710. break;
  1711. case '5': // LDAP
  1712. $('.dldap').show();
  1713. $('.dldap div.required:not(.ldap) input').attr('required', 'required');
  1714. break;
  1715. case '6': // OAuth2
  1716. $('.oauth2').show();
  1717. $('.oauth2 div.required:not(.oauth2_use_custom_url,.oauth2_use_custom_url_field,.open_id_connect_auto_discovery_url) input').attr('required', 'required');
  1718. onOAuth2Change();
  1719. break;
  1720. case '7': // SSPI
  1721. $('.sspi').show();
  1722. $('.sspi div.required input').attr('required', 'required');
  1723. break;
  1724. }
  1725. if (authType === '2' || authType === '5') {
  1726. onSecurityProtocolChange();
  1727. onVerifyGroupMembershipChange();
  1728. }
  1729. if (authType === '2') {
  1730. onUsePagedSearchChange();
  1731. }
  1732. });
  1733. $('#auth_type').trigger('change');
  1734. $('#security_protocol').on('change', onSecurityProtocolChange);
  1735. $('#use_paged_search').on('change', onUsePagedSearchChange);
  1736. $('#oauth2_provider').on('change', onOAuth2Change);
  1737. $('#oauth2_use_custom_url').on('change', onOAuth2UseCustomURLChange);
  1738. $('#groups_enabled').on('change', onVerifyGroupMembershipChange);
  1739. }
  1740. // Edit authentication
  1741. if ($('.admin.edit.authentication').length > 0) {
  1742. const authType = $('#auth_type').val();
  1743. if (authType === '2' || authType === '5') {
  1744. $('#security_protocol').on('change', onSecurityProtocolChange);
  1745. $('#groups_enabled').on('change', onVerifyGroupMembershipChange);
  1746. onVerifyGroupMembershipChange();
  1747. if (authType === '2') {
  1748. $('#use_paged_search').on('change', onUsePagedSearchChange);
  1749. }
  1750. } else if (authType === '6') {
  1751. $('#oauth2_provider').on('change', onOAuth2Change);
  1752. $('#oauth2_use_custom_url').on('change', onOAuth2UseCustomURLChange);
  1753. onOAuth2Change();
  1754. }
  1755. }
  1756. // Notice
  1757. if ($('.admin.notice')) {
  1758. const $detailModal = $('#detail-modal');
  1759. // Attach view detail modals
  1760. $('.view-detail').on('click', function () {
  1761. $detailModal.find('.content pre').text($(this).parents('tr').find('.notice-description').text());
  1762. $detailModal.find('.sub.header').text($(this).parents('tr').find('.notice-created-time').text());
  1763. $detailModal.modal('show');
  1764. return false;
  1765. });
  1766. // Select actions
  1767. const $checkboxes = $('.select.table .ui.checkbox');
  1768. $('.select.action').on('click', function () {
  1769. switch ($(this).data('action')) {
  1770. case 'select-all':
  1771. $checkboxes.checkbox('check');
  1772. break;
  1773. case 'deselect-all':
  1774. $checkboxes.checkbox('uncheck');
  1775. break;
  1776. case 'inverse':
  1777. $checkboxes.checkbox('toggle');
  1778. break;
  1779. }
  1780. });
  1781. $('#delete-selection').on('click', function () {
  1782. const $this = $(this);
  1783. $this.addClass('loading disabled');
  1784. const ids = [];
  1785. $checkboxes.each(function () {
  1786. if ($(this).checkbox('is checked')) {
  1787. ids.push($(this).data('id'));
  1788. }
  1789. });
  1790. $.post($this.data('link'), {
  1791. _csrf: csrf,
  1792. ids
  1793. }).done(() => {
  1794. window.location.href = $this.data('redirect');
  1795. });
  1796. });
  1797. }
  1798. }
  1799. function buttonsClickOnEnter() {
  1800. $('.ui.button').on('keypress', function (e) {
  1801. if (e.keyCode === 13 || e.keyCode === 32) { // enter key or space bar
  1802. $(this).trigger('click');
  1803. }
  1804. });
  1805. }
  1806. function searchUsers() {
  1807. const $searchUserBox = $('#search-user-box');
  1808. $searchUserBox.search({
  1809. minCharacters: 2,
  1810. apiSettings: {
  1811. url: `${AppSubUrl}/api/v1/users/search?q={query}`,
  1812. onResponse(response) {
  1813. const items = [];
  1814. $.each(response.data, (_i, item) => {
  1815. let title = item.login;
  1816. if (item.full_name && item.full_name.length > 0) {
  1817. title += ` (${htmlEscape(item.full_name)})`;
  1818. }
  1819. items.push({
  1820. title,
  1821. image: item.avatar_url
  1822. });
  1823. });
  1824. return {results: items};
  1825. }
  1826. },
  1827. searchFields: ['login', 'full_name'],
  1828. showNoResults: false
  1829. });
  1830. }
  1831. function searchTeams() {
  1832. const $searchTeamBox = $('#search-team-box');
  1833. $searchTeamBox.search({
  1834. minCharacters: 2,
  1835. apiSettings: {
  1836. url: `${AppSubUrl}/api/v1/orgs/${$searchTeamBox.data('org')}/teams/search?q={query}`,
  1837. headers: {'X-Csrf-Token': csrf},
  1838. onResponse(response) {
  1839. const items = [];
  1840. $.each(response.data, (_i, item) => {
  1841. const title = `${item.name} (${item.permission} access)`;
  1842. items.push({
  1843. title,
  1844. });
  1845. });
  1846. return {results: items};
  1847. }
  1848. },
  1849. searchFields: ['name', 'description'],
  1850. showNoResults: false
  1851. });
  1852. }
  1853. function searchRepositories() {
  1854. const $searchRepoBox = $('#search-repo-box');
  1855. $searchRepoBox.search({
  1856. minCharacters: 2,
  1857. apiSettings: {
  1858. url: `${AppSubUrl}/api/v1/repos/search?q={query}&uid=${$searchRepoBox.data('uid')}`,
  1859. onResponse(response) {
  1860. const items = [];
  1861. $.each(response.data, (_i, item) => {
  1862. items.push({
  1863. title: item.full_name.split('/')[1],
  1864. description: item.full_name
  1865. });
  1866. });
  1867. return {results: items};
  1868. }
  1869. },
  1870. searchFields: ['full_name'],
  1871. showNoResults: false
  1872. });
  1873. }
  1874. function initCodeView() {
  1875. if ($('.code-view .lines-num').length > 0) {
  1876. $(document).on('click', '.lines-num span', function (e) {
  1877. const $select = $(this);
  1878. let $list;
  1879. if ($('div.blame').length) {
  1880. $list = $('.code-view td.lines-code li');
  1881. } else {
  1882. $list = $('.code-view td.lines-code');
  1883. }
  1884. selectRange($list, $list.filter(`[rel=${$select.attr('id')}]`), (e.shiftKey ? $list.filter('.active').eq(0) : null));
  1885. deSelect();
  1886. });
  1887. $(window).on('hashchange', () => {
  1888. let m = window.location.hash.match(/^#(L\d+)-(L\d+)$/);
  1889. let $list;
  1890. if ($('div.blame').length) {
  1891. $list = $('.code-view td.lines-code li');
  1892. } else {
  1893. $list = $('.code-view td.lines-code');
  1894. }
  1895. let $first;
  1896. if (m) {
  1897. $first = $list.filter(`[rel=${m[1]}]`);
  1898. selectRange($list, $first, $list.filter(`[rel=${m[2]}]`));
  1899. $('html, body').scrollTop($first.offset().top - 200);
  1900. return;
  1901. }
  1902. m = window.location.hash.match(/^#(L|n)(\d+)$/);
  1903. if (m) {
  1904. $first = $list.filter(`[rel=L${m[2]}]`);
  1905. selectRange($list, $first);
  1906. $('html, body').scrollTop($first.offset().top - 200);
  1907. }
  1908. }).trigger('hashchange');
  1909. }
  1910. $(document).on('click', '.fold-file', ({currentTarget}) => {
  1911. const box = currentTarget.closest('.file-content');
  1912. const folded = box.dataset.folded !== 'true';
  1913. currentTarget.innerHTML = svg(`octicon-chevron-${folded ? 'right' : 'down'}`, 18);
  1914. box.dataset.folded = String(folded);
  1915. });
  1916. $(document).on('click', '.blob-excerpt', async ({currentTarget}) => {
  1917. const {url, query, anchor} = currentTarget.dataset;
  1918. const blob = await $.get(`${url}?${query}&anchor=${anchor}`);
  1919. currentTarget.closest('tr').outerHTML = blob;
  1920. });
  1921. }
  1922. function initU2FAuth() {
  1923. if ($('#wait-for-key').length === 0) {
  1924. return;
  1925. }
  1926. u2fApi.ensureSupport()
  1927. .then(() => {
  1928. $.getJSON(`${AppSubUrl}/user/u2f/challenge`).done((req) => {
  1929. u2fApi.sign(req.appId, req.challenge, req.registeredKeys, 30)
  1930. .then(u2fSigned)
  1931. .catch((err) => {
  1932. if (err === undefined) {
  1933. u2fError(1);
  1934. return;
  1935. }
  1936. u2fError(err.metaData.code);
  1937. });
  1938. });
  1939. }).catch(() => {
  1940. // Fallback in case browser do not support U2F
  1941. window.location.href = `${AppSubUrl}/user/two_factor`;
  1942. });
  1943. }
  1944. function u2fSigned(resp) {
  1945. $.ajax({
  1946. url: `${AppSubUrl}/user/u2f/sign`,
  1947. type: 'POST',
  1948. headers: {'X-Csrf-Token': csrf},
  1949. data: JSON.stringify(resp),
  1950. contentType: 'application/json; charset=utf-8',
  1951. }).done((res) => {
  1952. window.location.replace(res);
  1953. }).fail(() => {
  1954. u2fError(1);
  1955. });
  1956. }
  1957. function u2fRegistered(resp) {
  1958. if (checkError(resp)) {
  1959. return;
  1960. }
  1961. $.ajax({
  1962. url: `${AppSubUrl}/user/settings/security/u2f/register`,
  1963. type: 'POST',
  1964. headers: {'X-Csrf-Token': csrf},
  1965. data: JSON.stringify(resp),
  1966. contentType: 'application/json; charset=utf-8',
  1967. success() {
  1968. reload();
  1969. },
  1970. fail() {
  1971. u2fError(1);
  1972. }
  1973. });
  1974. }
  1975. function checkError(resp) {
  1976. if (!('errorCode' in resp)) {
  1977. return false;
  1978. }
  1979. if (resp.errorCode === 0) {
  1980. return false;
  1981. }
  1982. u2fError(resp.errorCode);
  1983. return true;
  1984. }
  1985. function u2fError(errorType) {
  1986. const u2fErrors = {
  1987. browser: $('#unsupported-browser'),
  1988. 1: $('#u2f-error-1'),
  1989. 2: $('#u2f-error-2'),
  1990. 3: $('#u2f-error-3'),
  1991. 4: $('#u2f-error-4'),
  1992. 5: $('.u2f-error-5')
  1993. };
  1994. u2fErrors[errorType].removeClass('hide');
  1995. Object.keys(u2fErrors).forEach((type) => {
  1996. if (type !== errorType) {
  1997. u2fErrors[type].addClass('hide');
  1998. }
  1999. });
  2000. $('#u2f-error').modal('show');
  2001. }
  2002. function initU2FRegister() {
  2003. $('#register-device').modal({allowMultiple: false});
  2004. $('#u2f-error').modal({allowMultiple: false});
  2005. $('#register-security-key').on('click', (e) => {
  2006. e.preventDefault();
  2007. u2fApi.ensureSupport()
  2008. .then(u2fRegisterRequest)
  2009. .catch(() => {
  2010. u2fError('browser');
  2011. });
  2012. });
  2013. }
  2014. function u2fRegisterRequest() {
  2015. $.post(`${AppSubUrl}/user/settings/security/u2f/request_register`, {
  2016. _csrf: csrf,
  2017. name: $('#nickname').val()
  2018. }).done((req) => {
  2019. $('#nickname').closest('div.field').removeClass('error');
  2020. $('#register-device').modal('show');
  2021. if (req.registeredKeys === null) {
  2022. req.registeredKeys = [];
  2023. }
  2024. u2fApi.register(req.appId, req.registerRequests, req.registeredKeys, 30)
  2025. .then(u2fRegistered)
  2026. .catch((reason) => {
  2027. if (reason === undefined) {
  2028. u2fError(1);
  2029. return;
  2030. }
  2031. u2fError(reason.metaData.code);
  2032. });
  2033. }).fail((xhr) => {
  2034. if (xhr.status === 409) {
  2035. $('#nickname').closest('div.field').addClass('error');
  2036. }
  2037. });
  2038. }
  2039. function initWipTitle() {
  2040. $('.title_wip_desc > a').on('click', (e) => {
  2041. e.preventDefault();
  2042. const $issueTitle = $('#issue_title');
  2043. $issueTitle.focus();
  2044. const value = $issueTitle.val().trim().toUpperCase();
  2045. const wipPrefixes = $('.title_wip_desc').data('wip-prefixes');
  2046. for (const prefix of wipPrefixes) {
  2047. if (value.startsWith(prefix.toUpperCase())) {
  2048. return;
  2049. }
  2050. }
  2051. $issueTitle.val(`${wipPrefixes[0]} ${$issueTitle.val()}`);
  2052. });
  2053. }
  2054. function initTemplateSearch() {
  2055. const $repoTemplate = $('#repo_template');
  2056. const checkTemplate = function () {
  2057. const $templateUnits = $('#template_units');
  2058. const $nonTemplate = $('#non_template');
  2059. if ($repoTemplate.val() !== '' && $repoTemplate.val() !== '0') {
  2060. $templateUnits.show();
  2061. $nonTemplate.hide();
  2062. } else {
  2063. $templateUnits.hide();
  2064. $nonTemplate.show();
  2065. }
  2066. };
  2067. $repoTemplate.on('change', checkTemplate);
  2068. checkTemplate();
  2069. const changeOwner = function () {
  2070. $('#repo_template_search')
  2071. .dropdown({
  2072. apiSettings: {
  2073. url: `${AppSubUrl}/api/v1/repos/search?q={query}&template=true&priority_owner_id=${$('#uid').val()}`,
  2074. onResponse(response) {
  2075. const filteredResponse = {success: true, results: []};
  2076. filteredResponse.results.push({
  2077. name: '',
  2078. value: ''
  2079. });
  2080. // Parse the response from the api to work with our dropdown
  2081. $.each(response.data, (_r, repo) => {
  2082. filteredResponse.results.push({
  2083. name: htmlEscape(repo.full_name),
  2084. value: repo.id
  2085. });
  2086. });
  2087. return filteredResponse;
  2088. },
  2089. cache: false,
  2090. },
  2091. fullTextSearch: true
  2092. });
  2093. };
  2094. $('#uid').on('change', changeOwner);
  2095. changeOwner();
  2096. }
  2097. $(document).ready(async () => {
  2098. // Show exact time
  2099. $('.time-since').each(function () {
  2100. $(this)
  2101. .addClass('poping up')
  2102. .attr('data-content', $(this).attr('title'))
  2103. .attr('data-variation', 'inverted tiny')
  2104. .attr('title', '');
  2105. });
  2106. // Semantic UI modules.
  2107. $('.dropdown:not(.custom)').dropdown();
  2108. $('.jump.dropdown').dropdown({
  2109. action: 'hide',
  2110. onShow() {
  2111. $('.poping.up').popup('hide');
  2112. }
  2113. });
  2114. $('.slide.up.dropdown').dropdown({
  2115. transition: 'slide up'
  2116. });
  2117. $('.upward.dropdown').dropdown({
  2118. direction: 'upward'
  2119. });
  2120. $('.ui.accordion').accordion();
  2121. $('.ui.checkbox').checkbox();
  2122. $('.ui.progress').progress({
  2123. showActivity: false
  2124. });
  2125. $('.poping.up').popup();
  2126. $('.top.menu .poping.up').popup({
  2127. onShow() {
  2128. if ($('.top.menu .menu.transition').hasClass('visible')) {
  2129. return false;
  2130. }
  2131. }
  2132. });
  2133. $('.tabular.menu .item').tab();
  2134. $('.tabable.menu .item').tab();
  2135. $('.toggle.button').on('click', function () {
  2136. $($(this).data('target')).slideToggle(100);
  2137. });
  2138. // make table <tr> element clickable like a link
  2139. $('tr[data-href]').on('click', function () {
  2140. window.location = $(this).data('href');
  2141. });
  2142. // make table <td> element clickable like a link
  2143. $('td[data-href]').click(function () {
  2144. window.location = $(this).data('href');
  2145. });
  2146. // Dropzone
  2147. const $dropzone = $('#dropzone');
  2148. if ($dropzone.length > 0) {
  2149. const filenameDict = {};
  2150. await createDropzone('#dropzone', {
  2151. url: $dropzone.data('upload-url'),
  2152. headers: {'X-Csrf-Token': csrf},
  2153. maxFiles: $dropzone.data('max-file'),
  2154. maxFilesize: $dropzone.data('max-size'),
  2155. acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'),
  2156. addRemoveLinks: true,
  2157. dictDefaultMessage: $dropzone.data('default-message'),
  2158. dictInvalidFileType: $dropzone.data('invalid-input-type'),
  2159. dictFileTooBig: $dropzone.data('file-too-big'),
  2160. dictRemoveFile: $dropzone.data('remove-file'),
  2161. timeout: 0,
  2162. init() {
  2163. this.on('success', (file, data) => {
  2164. filenameDict[file.name] = data.uuid;
  2165. const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
  2166. $('.files').append(input);
  2167. });
  2168. this.on('removedfile', (file) => {
  2169. if (file.name in filenameDict) {
  2170. $(`#${filenameDict[file.name]}`).remove();
  2171. }
  2172. if ($dropzone.data('remove-url')) {
  2173. $.post($dropzone.data('remove-url'), {
  2174. file: filenameDict[file.name],
  2175. _csrf: csrf
  2176. });
  2177. }
  2178. });
  2179. },
  2180. });
  2181. }
  2182. // Helpers.
  2183. $('.delete-button').on('click', showDeletePopup);
  2184. $('.add-all-button').on('click', showAddAllPopup);
  2185. $('.link-action').on('click', linkAction);
  2186. $('.language-menu a[lang]').on('click', linkLanguageAction);
  2187. $('.link-email-action').on('click', linkEmailAction);
  2188. $('.delete-branch-button').on('click', showDeletePopup);
  2189. $('.undo-button').on('click', function () {
  2190. const $this = $(this);
  2191. $.post($this.data('url'), {
  2192. _csrf: csrf,
  2193. id: $this.data('id')
  2194. }).done((data) => {
  2195. window.location.href = data.redirect;
  2196. });
  2197. });
  2198. $('.show-panel.button').on('click', function () {
  2199. $($(this).data('panel')).show();
  2200. });
  2201. $('.show-modal.button').on('click', function () {
  2202. $($(this).data('modal')).modal('show');
  2203. });
  2204. $('.delete-post.button').on('click', function () {
  2205. const $this = $(this);
  2206. $.post($this.data('request-url'), {
  2207. _csrf: csrf
  2208. }).done(() => {
  2209. window.location.href = $this.data('done-url');
  2210. });
  2211. });
  2212. $('.issue-checkbox').on('click', () => {
  2213. const numChecked = $('.issue-checkbox').children('input:checked').length;
  2214. if (numChecked > 0) {
  2215. $('#issue-filters').addClass('hide');
  2216. $('#issue-actions').removeClass('hide');
  2217. } else {
  2218. $('#issue-filters').removeClass('hide');
  2219. $('#issue-actions').addClass('hide');
  2220. }
  2221. });
  2222. $('.issue-action').on('click', function () {
  2223. let {action} = this.dataset;
  2224. let {elementId} = this.dataset;
  2225. const issueIDs = $('.issue-checkbox').children('input:checked').map(function () {
  2226. return this.dataset.issueId;
  2227. }).get().join();
  2228. const {url} = this.dataset;
  2229. if (elementId === '0' && url.substr(-9) === '/assignee') {
  2230. elementId = '';
  2231. action = 'clear';
  2232. }
  2233. updateIssuesMeta(url, action, issueIDs, elementId, '').then(() => {
  2234. // NOTICE: This reset of checkbox state targets Firefox caching behaviour, as the checkboxes stay checked after reload
  2235. if (action === 'close' || action === 'open') {
  2236. // uncheck all checkboxes
  2237. $('.issue-checkbox input[type="checkbox"]').each((_, e) => { e.checked = false });
  2238. }
  2239. reload();
  2240. });
  2241. });
  2242. // NOTICE: This event trigger targets Firefox caching behaviour, as the checkboxes stay checked after reload
  2243. // trigger ckecked event, if checkboxes are checked on load
  2244. $('.issue-checkbox input[type="checkbox"]:checked').first().each((_, e) => {
  2245. e.checked = false;
  2246. $(e).trigger('click');
  2247. });
  2248. $('.resolve-conversation').on('click', function (e) {
  2249. e.preventDefault();
  2250. const id = $(this).data('comment-id');
  2251. const action = $(this).data('action');
  2252. const url = $(this).data('update-url');
  2253. $.post(url, {
  2254. _csrf: csrf,
  2255. action,
  2256. comment_id: id,
  2257. }).then(reload);
  2258. });
  2259. buttonsClickOnEnter();
  2260. searchUsers();
  2261. searchTeams();
  2262. searchRepositories();
  2263. initMarkdownAnchors();
  2264. initCommentForm();
  2265. initInstall();
  2266. initRepository();
  2267. initMigration();
  2268. initWikiForm();
  2269. initEditForm();
  2270. initEditor();
  2271. initOrganization();
  2272. initGithook();
  2273. initWebhook();
  2274. initAdmin();
  2275. initCodeView();
  2276. initVueApp();
  2277. initTeamSettings();
  2278. initCtrlEnterSubmit();
  2279. initNavbarContentToggle();
  2280. initTopicbar();
  2281. initU2FAuth();
  2282. initU2FRegister();
  2283. initIssueList();
  2284. initWipTitle();
  2285. initPullRequestReview();
  2286. initRepoStatusChecker();
  2287. initTemplateSearch();
  2288. initContextPopups();
  2289. initTableSort();
  2290. initNotificationsTable();
  2291. // Repo clone url.
  2292. if ($('#repo-clone-url').length > 0) {
  2293. switch (localStorage.getItem('repo-clone-protocol')) {
  2294. case 'ssh':
  2295. if ($('#repo-clone-ssh').length > 0) {
  2296. $('#repo-clone-ssh').trigger('click');
  2297. } else {
  2298. $('#repo-clone-https').trigger('click');
  2299. }
  2300. break;
  2301. default:
  2302. $('#repo-clone-https').trigger('click');
  2303. break;
  2304. }
  2305. }
  2306. const routes = {
  2307. 'div.user.settings': initUserSettings,
  2308. 'div.repository.settings.collaboration': initRepositoryCollaboration
  2309. };
  2310. for (const [selector, fn] of Object.entries(routes)) {
  2311. if ($(selector).length > 0) {
  2312. fn();
  2313. break;
  2314. }
  2315. }
  2316. // parallel init of async loaded features
  2317. await Promise.all([
  2318. attachTribute(document.querySelectorAll('#content, .emoji-input')),
  2319. initGitGraph(),
  2320. initClipboard(),
  2321. initUserHeatmap(),
  2322. initProject(),
  2323. initServiceWorker(),
  2324. initNotificationCount(),
  2325. renderMarkdownContent(),
  2326. ]);
  2327. });
  2328. function changeHash(hash) {
  2329. if (window.history.pushState) {
  2330. window.history.pushState(null, null, hash);
  2331. } else {
  2332. window.location.hash = hash;
  2333. }
  2334. }
  2335. function deSelect() {
  2336. if (window.getSelection) {
  2337. window.getSelection().removeAllRanges();
  2338. } else {
  2339. document.selection.empty();
  2340. }
  2341. }
  2342. function selectRange($list, $select, $from) {
  2343. $list.removeClass('active');
  2344. if ($from) {
  2345. let a = parseInt($select.attr('rel').substr(1));
  2346. let b = parseInt($from.attr('rel').substr(1));
  2347. let c;
  2348. if (a !== b) {
  2349. if (a > b) {
  2350. c = a;
  2351. a = b;
  2352. b = c;
  2353. }
  2354. const classes = [];
  2355. for (let i = a; i <= b; i++) {
  2356. classes.push(`[rel=L${i}]`);
  2357. }
  2358. $list.filter(classes.join(',')).addClass('active');
  2359. changeHash(`#L${a}-L${b}`);
  2360. return;
  2361. }
  2362. }
  2363. $select.addClass('active');
  2364. changeHash(`#${$select.attr('rel')}`);
  2365. }
  2366. $(() => {
  2367. // Warn users that try to leave a page after entering data into a form.
  2368. // Except on sign-in pages, and for forms marked as 'ignore-dirty'.
  2369. if ($('.user.signin').length === 0) {
  2370. $('form:not(.ignore-dirty)').areYouSure();
  2371. }
  2372. // Parse SSH Key
  2373. $('#ssh-key-content').on('change paste keyup', function () {
  2374. const arrays = $(this).val().split(' ');
  2375. const $title = $('#ssh-key-title');
  2376. if ($title.val() === '' && arrays.length === 3 && arrays[2] !== '') {
  2377. $title.val(arrays[2]);
  2378. }
  2379. });
  2380. });
  2381. function showDeletePopup() {
  2382. const $this = $(this);
  2383. let filter = '';
  2384. if ($this.attr('id')) {
  2385. filter += `#${$this.attr('id')}`;
  2386. }
  2387. const dialog = $(`.delete.modal${filter}`);
  2388. dialog.find('.name').text($this.data('name'));
  2389. dialog.modal({
  2390. closable: false,
  2391. onApprove() {
  2392. if ($this.data('type') === 'form') {
  2393. $($this.data('form')).trigger('submit');
  2394. return;
  2395. }
  2396. $.post($this.data('url'), {
  2397. _csrf: csrf,
  2398. id: $this.data('id')
  2399. }).done((data) => {
  2400. window.location.href = data.redirect;
  2401. });
  2402. }
  2403. }).modal('show');
  2404. return false;
  2405. }
  2406. function showAddAllPopup() {
  2407. const $this = $(this);
  2408. let filter = '';
  2409. if ($this.attr('id')) {
  2410. filter += `#${$this.attr('id')}`;
  2411. }
  2412. const dialog = $(`.addall.modal${filter}`);
  2413. dialog.find('.name').text($this.data('name'));
  2414. dialog.modal({
  2415. closable: false,
  2416. onApprove() {
  2417. if ($this.data('type') === 'form') {
  2418. $($this.data('form')).trigger('submit');
  2419. return;
  2420. }
  2421. $.post($this.data('url'), {
  2422. _csrf: csrf,
  2423. id: $this.data('id')
  2424. }).done((data) => {
  2425. window.location.href = data.redirect;
  2426. });
  2427. }
  2428. }).modal('show');
  2429. return false;
  2430. }
  2431. function linkAction(e) {
  2432. e.preventDefault();
  2433. const $this = $(this);
  2434. const redirect = $this.data('redirect');
  2435. $.post($this.data('url'), {
  2436. _csrf: csrf
  2437. }).done((data) => {
  2438. if (data.redirect) {
  2439. window.location.href = data.redirect;
  2440. } else if (redirect) {
  2441. window.location.href = redirect;
  2442. } else {
  2443. window.location.reload();
  2444. }
  2445. });
  2446. }
  2447. function linkLanguageAction() {
  2448. const $this = $(this);
  2449. $.post($this.data('url')).always(() => {
  2450. window.location.reload();
  2451. });
  2452. }
  2453. function linkEmailAction(e) {
  2454. const $this = $(this);
  2455. $('#form-uid').val($this.data('uid'));
  2456. $('#form-email').val($this.data('email'));
  2457. $('#form-primary').val($this.data('primary'));
  2458. $('#form-activate').val($this.data('activate'));
  2459. $('#form-uid').val($this.data('uid'));
  2460. $('#change-email-modal').modal('show');
  2461. e.preventDefault();
  2462. }
  2463. function initVueComponents() {
  2464. // register svg icon vue components, e.g. <octicon-repo size="16"/>
  2465. for (const [name, htmlString] of Object.entries(svgs)) {
  2466. const template = htmlString
  2467. .replace(/height="[0-9]+"/, 'v-bind:height="size"')
  2468. .replace(/width="[0-9]+"/, 'v-bind:width="size"');
  2469. Vue.component(name, {
  2470. props: {
  2471. size: {
  2472. type: String,
  2473. default: '16',
  2474. },
  2475. },
  2476. template,
  2477. });
  2478. }
  2479. const vueDelimeters = ['${', '}'];
  2480. Vue.component('repo-search', {
  2481. delimiters: vueDelimeters,
  2482. props: {
  2483. searchLimit: {
  2484. type: Number,
  2485. default: 10
  2486. },
  2487. suburl: {
  2488. type: String,
  2489. required: true
  2490. },
  2491. uid: {
  2492. type: Number,
  2493. required: true
  2494. },
  2495. organizations: {
  2496. type: Array,
  2497. default: () => [],
  2498. },
  2499. isOrganization: {
  2500. type: Boolean,
  2501. default: true
  2502. },
  2503. canCreateOrganization: {
  2504. type: Boolean,
  2505. default: false
  2506. },
  2507. organizationsTotalCount: {
  2508. type: Number,
  2509. default: 0
  2510. },
  2511. moreReposLink: {
  2512. type: String,
  2513. default: ''
  2514. }
  2515. },
  2516. data() {
  2517. const params = new URLSearchParams(window.location.search);
  2518. let tab = params.get('repo-search-tab');
  2519. if (!tab) {
  2520. tab = 'repos';
  2521. }
  2522. let reposFilter = params.get('repo-search-filter');
  2523. if (!reposFilter) {
  2524. reposFilter = 'all';
  2525. }
  2526. let privateFilter = params.get('repo-search-private');
  2527. if (!privateFilter) {
  2528. privateFilter = 'both';
  2529. }
  2530. let archivedFilter = params.get('repo-search-archived');
  2531. if (!archivedFilter) {
  2532. archivedFilter = 'unarchived';
  2533. }
  2534. let searchQuery = params.get('repo-search-query');
  2535. if (!searchQuery) {
  2536. searchQuery = '';
  2537. }
  2538. let page = 1;
  2539. try {
  2540. page = parseInt(params.get('repo-search-page'));
  2541. } catch {
  2542. // noop
  2543. }
  2544. if (!page) {
  2545. page = 1;
  2546. }
  2547. return {
  2548. tab,
  2549. repos: [],
  2550. reposTotalCount: 0,
  2551. reposFilter,
  2552. archivedFilter,
  2553. privateFilter,
  2554. page,
  2555. finalPage: 1,
  2556. searchQuery,
  2557. isLoading: false,
  2558. staticPrefix: StaticUrlPrefix,
  2559. counts: {},
  2560. repoTypes: {
  2561. all: {
  2562. searchMode: '',
  2563. },
  2564. forks: {
  2565. searchMode: 'fork',
  2566. },
  2567. mirrors: {
  2568. searchMode: 'mirror',
  2569. },
  2570. sources: {
  2571. searchMode: 'source',
  2572. },
  2573. collaborative: {
  2574. searchMode: 'collaborative',
  2575. },
  2576. }
  2577. };
  2578. },
  2579. computed: {
  2580. showMoreReposLink() {
  2581. return this.repos.length > 0 && this.repos.length < this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`];
  2582. },
  2583. searchURL() {
  2584. return `${this.suburl}/api/v1/repos/search?sort=updated&order=desc&uid=${this.uid}&q=${this.searchQuery
  2585. }&page=${this.page}&limit=${this.searchLimit}&mode=${this.repoTypes[this.reposFilter].searchMode
  2586. }${this.reposFilter !== 'all' ? '&exclusive=1' : ''
  2587. }${this.archivedFilter === 'archived' ? '&archived=true' : ''}${this.archivedFilter === 'unarchived' ? '&archived=false' : ''
  2588. }${this.privateFilter === 'private' ? '&is_private=true' : ''}${this.privateFilter === 'public' ? '&is_private=false' : ''
  2589. }`;
  2590. },
  2591. repoTypeCount() {
  2592. return this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`];
  2593. }
  2594. },
  2595. mounted() {
  2596. this.searchRepos(this.reposFilter);
  2597. $(this.$el).find('.poping.up').popup();
  2598. $(this.$el).find('.dropdown').dropdown();
  2599. this.setCheckboxes();
  2600. const self = this;
  2601. Vue.nextTick(() => {
  2602. self.$refs.search.focus();
  2603. });
  2604. },
  2605. methods: {
  2606. changeTab(t) {
  2607. this.tab = t;
  2608. this.updateHistory();
  2609. },
  2610. setCheckboxes() {
  2611. switch (this.archivedFilter) {
  2612. case 'unarchived':
  2613. $('#archivedFilterCheckbox').checkbox('set unchecked');
  2614. break;
  2615. case 'archived':
  2616. $('#archivedFilterCheckbox').checkbox('set checked');
  2617. break;
  2618. case 'both':
  2619. $('#archivedFilterCheckbox').checkbox('set indeterminate');
  2620. break;
  2621. default:
  2622. this.archivedFilter = 'unarchived';
  2623. $('#archivedFilterCheckbox').checkbox('set unchecked');
  2624. break;
  2625. }
  2626. switch (this.privateFilter) {
  2627. case 'public':
  2628. $('#privateFilterCheckbox').checkbox('set unchecked');
  2629. break;
  2630. case 'private':
  2631. $('#privateFilterCheckbox').checkbox('set checked');
  2632. break;
  2633. case 'both':
  2634. $('#privateFilterCheckbox').checkbox('set indeterminate');
  2635. break;
  2636. default:
  2637. this.privateFilter = 'both';
  2638. $('#privateFilterCheckbox').checkbox('set indeterminate');
  2639. break;
  2640. }
  2641. },
  2642. changeReposFilter(filter) {
  2643. this.reposFilter = filter;
  2644. this.repos = [];
  2645. this.page = 1;
  2646. Vue.set(this.counts, `${filter}:${this.archivedFilter}:${this.privateFilter}`, 0);
  2647. this.searchRepos();
  2648. },
  2649. updateHistory() {
  2650. const params = new URLSearchParams(window.location.search);
  2651. if (this.tab === 'repos') {
  2652. params.delete('repo-search-tab');
  2653. } else {
  2654. params.set('repo-search-tab', this.tab);
  2655. }
  2656. if (this.reposFilter === 'all') {
  2657. params.delete('repo-search-filter');
  2658. } else {
  2659. params.set('repo-search-filter', this.reposFilter);
  2660. }
  2661. if (this.privateFilter === 'both') {
  2662. params.delete('repo-search-private');
  2663. } else {
  2664. params.set('repo-search-private', this.privateFilter);
  2665. }
  2666. if (this.archivedFilter === 'unarchived') {
  2667. params.delete('repo-search-archived');
  2668. } else {
  2669. params.set('repo-search-archived', this.archivedFilter);
  2670. }
  2671. if (this.searchQuery === '') {
  2672. params.delete('repo-search-query');
  2673. } else {
  2674. params.set('repo-search-query', this.searchQuery);
  2675. }
  2676. if (this.page === 1) {
  2677. params.delete('repo-search-page');
  2678. } else {
  2679. params.set('repo-search-page', `${this.page}`);
  2680. }
  2681. const queryString = params.toString();
  2682. if (queryString) {
  2683. window.history.replaceState({}, '', `?${queryString}`);
  2684. } else {
  2685. window.history.replaceState({}, '', window.location.pathname);
  2686. }
  2687. },
  2688. toggleArchivedFilter() {
  2689. switch (this.archivedFilter) {
  2690. case 'both':
  2691. this.archivedFilter = 'unarchived';
  2692. break;
  2693. case 'unarchived':
  2694. this.archivedFilter = 'archived';
  2695. break;
  2696. case 'archived':
  2697. this.archivedFilter = 'both';
  2698. break;
  2699. default:
  2700. this.archivedFilter = 'unarchived';
  2701. break;
  2702. }
  2703. this.page = 1;
  2704. this.repos = [];
  2705. this.setCheckboxes();
  2706. Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, 0);
  2707. this.searchRepos();
  2708. },
  2709. togglePrivateFilter() {
  2710. switch (this.privateFilter) {
  2711. case 'both':
  2712. this.privateFilter = 'public';
  2713. break;
  2714. case 'public':
  2715. this.privateFilter = 'private';
  2716. break;
  2717. case 'private':
  2718. this.privateFilter = 'both';
  2719. break;
  2720. default:
  2721. this.privateFilter = 'both';
  2722. break;
  2723. }
  2724. this.page = 1;
  2725. this.repos = [];
  2726. this.setCheckboxes();
  2727. Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, 0);
  2728. this.searchRepos();
  2729. },
  2730. changePage(page) {
  2731. this.page = page;
  2732. if (this.page > this.finalPage) {
  2733. this.page = this.finalPage;
  2734. }
  2735. if (this.page < 1) {
  2736. this.page = 1;
  2737. }
  2738. this.repos = [];
  2739. Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, 0);
  2740. this.searchRepos();
  2741. },
  2742. searchRepos() {
  2743. const self = this;
  2744. this.isLoading = true;
  2745. if (!this.reposTotalCount) {
  2746. const totalCountSearchURL = `${this.suburl}/api/v1/repos/search?sort=updated&order=desc&uid=${this.uid}&q=&page=1&mode=`;
  2747. $.getJSON(totalCountSearchURL, (_result, _textStatus, request) => {
  2748. self.reposTotalCount = request.getResponseHeader('X-Total-Count');
  2749. });
  2750. }
  2751. const searchedMode = this.repoTypes[this.reposFilter].searchMode;
  2752. const searchedURL = this.searchURL;
  2753. const searchedQuery = this.searchQuery;
  2754. $.getJSON(searchedURL, (result, _textStatus, request) => {
  2755. if (searchedURL === self.searchURL) {
  2756. self.repos = result.data;
  2757. const count = request.getResponseHeader('X-Total-Count');
  2758. if (searchedQuery === '' && searchedMode === '' && self.archivedFilter === 'both') {
  2759. self.reposTotalCount = count;
  2760. }
  2761. Vue.set(self.counts, `${self.reposFilter}:${self.archivedFilter}:${self.privateFilter}`, count);
  2762. self.finalPage = Math.floor(count / self.searchLimit) + 1;
  2763. self.updateHistory();
  2764. }
  2765. }).always(() => {
  2766. if (searchedURL === self.searchURL) {
  2767. self.isLoading = false;
  2768. }
  2769. });
  2770. },
  2771. repoIcon(repo) {
  2772. if (repo.fork) {
  2773. return 'octicon-repo-forked';
  2774. } else if (repo.mirror) {
  2775. return 'octicon-mirror';
  2776. } else if (repo.template) {
  2777. return `octicon-repo-template`;
  2778. } else if (repo.private) {
  2779. return 'octicon-lock';
  2780. } else if (repo.internal) {
  2781. return 'octicon-repo';
  2782. }
  2783. return 'octicon-repo';
  2784. }
  2785. }
  2786. });
  2787. }
  2788. function initCtrlEnterSubmit() {
  2789. $('.js-quick-submit').on('keydown', function (e) {
  2790. if (((e.ctrlKey && !e.altKey) || e.metaKey) && (e.keyCode === 13 || e.keyCode === 10)) {
  2791. $(this).closest('form').trigger('submit');
  2792. }
  2793. });
  2794. }
  2795. function initVueApp() {
  2796. const el = document.getElementById('app');
  2797. if (!el) {
  2798. return;
  2799. }
  2800. initVueComponents();
  2801. new Vue({
  2802. el,
  2803. delimiters: ['${', '}'],
  2804. components: {
  2805. ActivityTopAuthors,
  2806. },
  2807. data: () => {
  2808. return {
  2809. searchLimit: Number((document.querySelector('meta[name=_search_limit]') || {}).content),
  2810. suburl: AppSubUrl,
  2811. uid: Number((document.querySelector('meta[name=_context_uid]') || {}).content),
  2812. activityTopAuthors: window.ActivityTopAuthors || [],
  2813. };
  2814. },
  2815. });
  2816. }
  2817. window.timeAddManual = function () {
  2818. $('.mini.modal')
  2819. .modal({
  2820. duration: 200,
  2821. onApprove() {
  2822. $('#add_time_manual_form').trigger('submit');
  2823. }
  2824. }).modal('show');
  2825. };
  2826. window.toggleStopwatch = function () {
  2827. $('#toggle_stopwatch_form').trigger('submit');
  2828. };
  2829. window.cancelStopwatch = function () {
  2830. $('#cancel_stopwatch_form').trigger('submit');
  2831. };
  2832. function initFilterBranchTagDropdown(selector) {
  2833. $(selector).each(function () {
  2834. const $dropdown = $(this);
  2835. const $data = $dropdown.find('.data');
  2836. const data = {
  2837. items: [],
  2838. mode: $data.data('mode'),
  2839. searchTerm: '',
  2840. noResults: '',
  2841. canCreateBranch: false,
  2842. menuVisible: false,
  2843. active: 0
  2844. };
  2845. $data.find('.item').each(function () {
  2846. data.items.push({
  2847. name: $(this).text(),
  2848. url: $(this).data('url'),
  2849. branch: $(this).hasClass('branch'),
  2850. tag: $(this).hasClass('tag'),
  2851. selected: $(this).hasClass('selected')
  2852. });
  2853. });
  2854. $data.remove();
  2855. new Vue({
  2856. el: this,
  2857. delimiters: ['${', '}'],
  2858. data,
  2859. computed: {
  2860. filteredItems() {
  2861. const items = this.items.filter((item) => {
  2862. return ((this.mode === 'branches' && item.branch) || (this.mode === 'tags' && item.tag)) &&
  2863. (!this.searchTerm || item.name.toLowerCase().includes(this.searchTerm.toLowerCase()));
  2864. });
  2865. // no idea how to fix this so linting rule is disabled instead
  2866. this.active = (items.length === 0 && this.showCreateNewBranch ? 0 : -1); // eslint-disable-line vue/no-side-effects-in-computed-properties
  2867. return items;
  2868. },
  2869. showNoResults() {
  2870. return this.filteredItems.length === 0 && !this.showCreateNewBranch;
  2871. },
  2872. showCreateNewBranch() {
  2873. if (!this.canCreateBranch || !this.searchTerm || this.mode === 'tags') {
  2874. return false;
  2875. }
  2876. return this.items.filter((item) => item.name.toLowerCase() === this.searchTerm.toLowerCase()).length === 0;
  2877. }
  2878. },
  2879. watch: {
  2880. menuVisible(visible) {
  2881. if (visible) {
  2882. this.focusSearchField();
  2883. }
  2884. }
  2885. },
  2886. beforeMount() {
  2887. this.noResults = this.$el.getAttribute('data-no-results');
  2888. this.canCreateBranch = this.$el.getAttribute('data-can-create-branch') === 'true';
  2889. document.body.addEventListener('click', (event) => {
  2890. if (this.$el.contains(event.target)) return;
  2891. if (this.menuVisible) {
  2892. Vue.set(this, 'menuVisible', false);
  2893. }
  2894. });
  2895. },
  2896. methods: {
  2897. selectItem(item) {
  2898. const prev = this.getSelected();
  2899. if (prev !== null) {
  2900. prev.selected = false;
  2901. }
  2902. item.selected = true;
  2903. window.location.href = item.url;
  2904. },
  2905. createNewBranch() {
  2906. if (!this.showCreateNewBranch) return;
  2907. $(this.$refs.newBranchForm).trigger('submit');
  2908. },
  2909. focusSearchField() {
  2910. Vue.nextTick(() => {
  2911. this.$refs.searchField.focus();
  2912. });
  2913. },
  2914. getSelected() {
  2915. for (let i = 0, j = this.items.length; i < j; ++i) {
  2916. if (this.items[i].selected) return this.items[i];
  2917. }
  2918. return null;
  2919. },
  2920. getSelectedIndexInFiltered() {
  2921. for (let i = 0, j = this.filteredItems.length; i < j; ++i) {
  2922. if (this.filteredItems[i].selected) return i;
  2923. }
  2924. return -1;
  2925. },
  2926. scrollToActive() {
  2927. let el = this.$refs[`listItem${this.active}`];
  2928. if (!el || !el.length) return;
  2929. if (Array.isArray(el)) {
  2930. el = el[0];
  2931. }
  2932. const cont = this.$refs.scrollContainer;
  2933. if (el.offsetTop < cont.scrollTop) {
  2934. cont.scrollTop = el.offsetTop;
  2935. } else if (el.offsetTop + el.clientHeight > cont.scrollTop + cont.clientHeight) {
  2936. cont.scrollTop = el.offsetTop + el.clientHeight - cont.clientHeight;
  2937. }
  2938. },
  2939. keydown(event) {
  2940. if (event.keyCode === 40) { // arrow down
  2941. event.preventDefault();
  2942. if (this.active === -1) {
  2943. this.active = this.getSelectedIndexInFiltered();
  2944. }
  2945. if (this.active + (this.showCreateNewBranch ? 0 : 1) >= this.filteredItems.length) {
  2946. return;
  2947. }
  2948. this.active++;
  2949. this.scrollToActive();
  2950. } else if (event.keyCode === 38) { // arrow up
  2951. event.preventDefault();
  2952. if (this.active === -1) {
  2953. this.active = this.getSelectedIndexInFiltered();
  2954. }
  2955. if (this.active <= 0) {
  2956. return;
  2957. }
  2958. this.active--;
  2959. this.scrollToActive();
  2960. } else if (event.keyCode === 13) { // enter
  2961. event.preventDefault();
  2962. if (this.active >= this.filteredItems.length) {
  2963. this.createNewBranch();
  2964. } else if (this.active >= 0) {
  2965. this.selectItem(this.filteredItems[this.active]);
  2966. }
  2967. } else if (event.keyCode === 27) { // escape
  2968. event.preventDefault();
  2969. this.menuVisible = false;
  2970. }
  2971. }
  2972. }
  2973. });
  2974. });
  2975. }
  2976. $('.commit-button').on('click', function (e) {
  2977. e.preventDefault();
  2978. $(this).parent().find('.commit-body').toggle();
  2979. });
  2980. function initNavbarContentToggle() {
  2981. const content = $('#navbar');
  2982. const toggle = $('#navbar-expand-toggle');
  2983. let isExpanded = false;
  2984. toggle.on('click', () => {
  2985. isExpanded = !isExpanded;
  2986. if (isExpanded) {
  2987. content.addClass('shown');
  2988. toggle.addClass('active');
  2989. } else {
  2990. content.removeClass('shown');
  2991. toggle.removeClass('active');
  2992. }
  2993. });
  2994. }
  2995. function initTopicbar() {
  2996. const mgrBtn = $('#manage_topic');
  2997. const editDiv = $('#topic_edit');
  2998. const viewDiv = $('#repo-topics');
  2999. const saveBtn = $('#save_topic');
  3000. const topicDropdown = $('#topic_edit .dropdown');
  3001. const topicForm = $('#topic_edit.ui.form');
  3002. const topicPrompts = getPrompts();
  3003. mgrBtn.on('click', () => {
  3004. viewDiv.hide();
  3005. editDiv.css('display', ''); // show Semantic UI Grid
  3006. });
  3007. function getPrompts() {
  3008. const hidePrompt = $('div.hide#validate_prompt');
  3009. const prompts = {
  3010. countPrompt: hidePrompt.children('#count_prompt').text(),
  3011. formatPrompt: hidePrompt.children('#format_prompt').text()
  3012. };
  3013. hidePrompt.remove();
  3014. return prompts;
  3015. }
  3016. saveBtn.on('click', () => {
  3017. const topics = $('input[name=topics]').val();
  3018. $.post(saveBtn.data('link'), {
  3019. _csrf: csrf,
  3020. topics
  3021. }, (_data, _textStatus, xhr) => {
  3022. if (xhr.responseJSON.status === 'ok') {
  3023. viewDiv.children('.topic').remove();
  3024. if (topics.length) {
  3025. const topicArray = topics.split(',');
  3026. const last = viewDiv.children('a').last();
  3027. for (let i = 0; i < topicArray.length; i++) {
  3028. const link = $('<a class="ui repo-topic small label topic"></a>');
  3029. link.attr('href', `${AppSubUrl}/explore/repos?q=${encodeURIComponent(topicArray[i])}&topic=1`);
  3030. link.text(topicArray[i]);
  3031. link.insertBefore(last);
  3032. }
  3033. }
  3034. editDiv.css('display', 'none');
  3035. viewDiv.show();
  3036. }
  3037. }).fail((xhr) => {
  3038. if (xhr.status === 422) {
  3039. if (xhr.responseJSON.invalidTopics.length > 0) {
  3040. topicPrompts.formatPrompt = xhr.responseJSON.message;
  3041. const {invalidTopics} = xhr.responseJSON;
  3042. const topicLables = topicDropdown.children('a.ui.label');
  3043. topics.split(',').forEach((value, index) => {
  3044. for (let i = 0; i < invalidTopics.length; i++) {
  3045. if (invalidTopics[i] === value) {
  3046. topicLables.eq(index).removeClass('green').addClass('red');
  3047. }
  3048. }
  3049. });
  3050. } else {
  3051. topicPrompts.countPrompt = xhr.responseJSON.message;
  3052. }
  3053. }
  3054. }).always(() => {
  3055. topicForm.form('validate form');
  3056. });
  3057. });
  3058. topicDropdown.dropdown({
  3059. allowAdditions: true,
  3060. forceSelection: false,
  3061. fields: {name: 'description', value: 'data-value'},
  3062. saveRemoteData: false,
  3063. label: {
  3064. transition: 'horizontal flip',
  3065. duration: 200,
  3066. variation: false,
  3067. blue: true,
  3068. basic: true,
  3069. },
  3070. className: {
  3071. label: 'ui small label'
  3072. },
  3073. apiSettings: {
  3074. url: `${AppSubUrl}/api/v1/topics/search?q={query}`,
  3075. throttle: 500,
  3076. cache: false,
  3077. onResponse(res) {
  3078. const formattedResponse = {
  3079. success: false,
  3080. results: [],
  3081. };
  3082. const query = stripTags(this.urlData.query.trim());
  3083. let found_query = false;
  3084. const current_topics = [];
  3085. topicDropdown.find('div.label.visible.topic,a.label.visible').each((_, e) => { current_topics.push(e.dataset.value) });
  3086. if (res.topics) {
  3087. let found = false;
  3088. for (let i = 0; i < res.topics.length; i++) {
  3089. // skip currently added tags
  3090. if (current_topics.includes(res.topics[i].topic_name)) {
  3091. continue;
  3092. }
  3093. if (res.topics[i].topic_name.toLowerCase() === query.toLowerCase()) {
  3094. found_query = true;
  3095. }
  3096. formattedResponse.results.push({description: res.topics[i].topic_name, 'data-value': res.topics[i].topic_name});
  3097. found = true;
  3098. }
  3099. formattedResponse.success = found;
  3100. }
  3101. if (query.length > 0 && !found_query) {
  3102. formattedResponse.success = true;
  3103. formattedResponse.results.unshift({description: query, 'data-value': query});
  3104. } else if (query.length > 0 && found_query) {
  3105. formattedResponse.results.sort((a, b) => {
  3106. if (a.description.toLowerCase() === query.toLowerCase()) return -1;
  3107. if (b.description.toLowerCase() === query.toLowerCase()) return 1;
  3108. if (a.description > b.description) return -1;
  3109. if (a.description < b.description) return 1;
  3110. return 0;
  3111. });
  3112. }
  3113. return formattedResponse;
  3114. },
  3115. },
  3116. onLabelCreate(value) {
  3117. value = value.toLowerCase().trim();
  3118. this.attr('data-value', value).contents().first().replaceWith(value);
  3119. return $(this);
  3120. },
  3121. onAdd(addedValue, _addedText, $addedChoice) {
  3122. addedValue = addedValue.toLowerCase().trim();
  3123. $($addedChoice).attr('data-value', addedValue);
  3124. $($addedChoice).attr('data-text', addedValue);
  3125. }
  3126. });
  3127. $.fn.form.settings.rules.validateTopic = function (_values, regExp) {
  3128. const topics = topicDropdown.children('a.ui.label');
  3129. const status = topics.length === 0 || topics.last().attr('data-value').match(regExp);
  3130. if (!status) {
  3131. topics.last().removeClass('green').addClass('red');
  3132. }
  3133. return status && topicDropdown.children('a.ui.label.red').length === 0;
  3134. };
  3135. topicForm.form({
  3136. on: 'change',
  3137. inline: true,
  3138. fields: {
  3139. topics: {
  3140. identifier: 'topics',
  3141. rules: [
  3142. {
  3143. type: 'validateTopic',
  3144. value: /^[a-z0-9][a-z0-9-]{0,35}$/,
  3145. prompt: topicPrompts.formatPrompt
  3146. },
  3147. {
  3148. type: 'maxCount[25]',
  3149. prompt: topicPrompts.countPrompt
  3150. }
  3151. ]
  3152. },
  3153. }
  3154. });
  3155. }
  3156. window.toggleDeadlineForm = function () {
  3157. $('#deadlineForm').fadeToggle(150);
  3158. };
  3159. window.setDeadline = function () {
  3160. const deadline = $('#deadlineDate').val();
  3161. window.updateDeadline(deadline);
  3162. };
  3163. window.updateDeadline = function (deadlineString) {
  3164. $('#deadline-err-invalid-date').hide();
  3165. $('#deadline-loader').addClass('loading');
  3166. let realDeadline = null;
  3167. if (deadlineString !== '') {
  3168. const newDate = Date.parse(deadlineString);
  3169. if (Number.isNaN(newDate)) {
  3170. $('#deadline-loader').removeClass('loading');
  3171. $('#deadline-err-invalid-date').show();
  3172. return false;
  3173. }
  3174. realDeadline = new Date(newDate);
  3175. }
  3176. $.ajax(`${$('#update-issue-deadline-form').attr('action')}/deadline`, {
  3177. data: JSON.stringify({
  3178. due_date: realDeadline,
  3179. }),
  3180. headers: {
  3181. 'X-Csrf-Token': csrf,
  3182. 'X-Remote': true,
  3183. },
  3184. contentType: 'application/json',
  3185. type: 'POST',
  3186. success() {
  3187. reload();
  3188. },
  3189. error() {
  3190. $('#deadline-loader').removeClass('loading');
  3191. $('#deadline-err-invalid-date').show();
  3192. }
  3193. });
  3194. };
  3195. window.deleteDependencyModal = function (id, type) {
  3196. $('.remove-dependency')
  3197. .modal({
  3198. closable: false,
  3199. duration: 200,
  3200. onApprove() {
  3201. $('#removeDependencyID').val(id);
  3202. $('#dependencyType').val(type);
  3203. $('#removeDependencyForm').trigger('submit');
  3204. }
  3205. }).modal('show');
  3206. };
  3207. function initIssueList() {
  3208. const repolink = $('#repolink').val();
  3209. const repoId = $('#repoId').val();
  3210. const crossRepoSearch = $('#crossRepoSearch').val();
  3211. const tp = $('#type').val();
  3212. let issueSearchUrl = `${AppSubUrl}/api/v1/repos/${repolink}/issues?q={query}&type=${tp}`;
  3213. if (crossRepoSearch === 'true') {
  3214. issueSearchUrl = `${AppSubUrl}/api/v1/repos/issues/search?q={query}&priority_repo_id=${repoId}&type=${tp}`;
  3215. }
  3216. $('#new-dependency-drop-list')
  3217. .dropdown({
  3218. apiSettings: {
  3219. url: issueSearchUrl,
  3220. onResponse(response) {
  3221. const filteredResponse = {success: true, results: []};
  3222. const currIssueId = $('#new-dependency-drop-list').data('issue-id');
  3223. // Parse the response from the api to work with our dropdown
  3224. $.each(response, (_i, issue) => {
  3225. // Don't list current issue in the dependency list.
  3226. if (issue.id === currIssueId) {
  3227. return;
  3228. }
  3229. filteredResponse.results.push({
  3230. name: `#${issue.number} ${htmlEscape(issue.title)
  3231. }<div class="text small dont-break-out">${htmlEscape(issue.repository.full_name)}</div>`,
  3232. value: issue.id
  3233. });
  3234. });
  3235. return filteredResponse;
  3236. },
  3237. cache: false,
  3238. },
  3239. fullTextSearch: true
  3240. });
  3241. $('.menu a.label-filter-item').each(function () {
  3242. $(this).on('click', function (e) {
  3243. if (e.altKey) {
  3244. e.preventDefault();
  3245. const href = $(this).attr('href');
  3246. const id = $(this).data('label-id');
  3247. const regStr = `labels=(-?[0-9]+%2c)*(${id})(%2c-?[0-9]+)*&`;
  3248. const newStr = 'labels=$1-$2$3&';
  3249. window.location = href.replace(new RegExp(regStr), newStr);
  3250. }
  3251. });
  3252. });
  3253. $('.menu .ui.dropdown.label-filter').on('keydown', (e) => {
  3254. if (e.altKey && e.keyCode === 13) {
  3255. const selectedItems = $('.menu .ui.dropdown.label-filter .menu .item.selected');
  3256. if (selectedItems.length > 0) {
  3257. const item = $(selectedItems[0]);
  3258. const href = item.attr('href');
  3259. const id = item.data('label-id');
  3260. const regStr = `labels=(-?[0-9]+%2c)*(${id})(%2c-?[0-9]+)*&`;
  3261. const newStr = 'labels=$1-$2$3&';
  3262. window.location = href.replace(new RegExp(regStr), newStr);
  3263. }
  3264. }
  3265. });
  3266. }
  3267. window.cancelCodeComment = function (btn) {
  3268. const form = $(btn).closest('form');
  3269. if (form.length > 0 && form.hasClass('comment-form')) {
  3270. form.addClass('hide');
  3271. form.parent().find('button.comment-form-reply').show();
  3272. } else {
  3273. form.closest('.comment-code-cloud').remove();
  3274. }
  3275. };
  3276. window.submitReply = function (btn) {
  3277. const form = $(btn).closest('form');
  3278. if (form.length > 0 && form.hasClass('comment-form')) {
  3279. form.trigger('submit');
  3280. }
  3281. };
  3282. window.onOAuthLoginClick = function () {
  3283. const oauthLoader = $('#oauth2-login-loader');
  3284. const oauthNav = $('#oauth2-login-navigator');
  3285. oauthNav.hide();
  3286. oauthLoader.removeClass('disabled');
  3287. setTimeout(() => {
  3288. // recover previous content to let user try again
  3289. // usually redirection will be performed before this action
  3290. oauthLoader.addClass('disabled');
  3291. oauthNav.show();
  3292. }, 5000);
  3293. };