2020-04-28 21:05:39 +03:00
/* globals wipPrefixes */
2020-02-24 00:34:28 +03:00
/* exported timeAddManual, toggleStopwatch, cancelStopwatch */
2019-11-14 20:58:32 +03:00
/* exported toggleDeadlineForm, setDeadline, updateDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */
2019-11-13 17:52:13 +03:00
2020-03-11 22:34:54 +03:00
import './publicpath.js' ;
2020-01-26 11:17:25 +03:00
import './polyfills.js' ;
2020-02-24 00:34:28 +03:00
import Vue from 'vue' ;
import 'jquery.are-you-sure' ;
2020-03-11 22:34:54 +03:00
import './vendor/semanticdropdown.js' ;
import { svg } from './utils.js' ;
2020-02-07 20:09:30 +03:00
2020-03-11 22:34:54 +03:00
import initContextPopups from './features/contextpopup.js' ;
import initGitGraph from './features/gitgraph.js' ;
2020-02-08 02:03:42 +03:00
import initClipboard from './features/clipboard.js' ;
2020-03-11 22:34:54 +03:00
import initUserHeatmap from './features/userheatmap.js' ;
2020-03-17 22:08:15 +03:00
import initDateTimePicker from './features/datetimepicker.js' ;
2020-04-28 21:05:39 +03:00
import { initTribute , issuesTribute , emojiTribute } from './features/tribute.js' ;
2020-03-08 00:06:15 +03:00
import createDropzone from './features/dropzone.js' ;
2020-04-13 16:02:31 +03:00
import highlight from './features/highlight.js' ;
2020-01-20 13:07:30 +03:00
import ActivityTopAuthors from './components/ActivityTopAuthors.vue' ;
2020-04-24 06:57:38 +03:00
import { initNotificationsTable , initNotificationCount } from './features/notification.js' ;
2020-01-20 13:07:30 +03:00
2020-03-11 22:34:54 +03:00
const { AppSubUrl , StaticUrlPrefix , csrf } = window . config ;
2020-02-12 04:53:18 +03:00
2019-11-13 17:52:13 +03:00
function htmlEncode ( text ) {
2019-11-15 00:39:51 +03:00
return jQuery ( '<div />' ) . text ( text ) . html ( ) ;
2019-11-13 17:52:13 +03:00
}
let previewFileModes ;
let simpleMDEditor ;
2019-12-16 18:56:35 +03:00
const commentMDEditors = { } ;
2019-11-13 17:52:13 +03:00
let codeMirrorEditor ;
2020-01-12 05:57:32 +03:00
// Silence fomantic's error logging when tabs are used without a target content element
$ . fn . tab . settings . silent = true ;
2019-11-13 17:52:13 +03:00
function initCommentPreviewTab ( $form ) {
2019-11-15 00:39:51 +03:00
const $tabMenu = $form . find ( '.tabular.menu' ) ;
$tabMenu . find ( '.item' ) . tab ( ) ;
2020-04-19 01:46:29 +03:00
$tabMenu . find ( ` .item[data-tab=" ${ $tabMenu . data ( 'preview' ) } "] ` ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
const $this = $ ( this ) ;
$ . post ( $this . data ( 'url' ) , {
_csrf : csrf ,
mode : 'gfm' ,
context : $this . data ( 'context' ) ,
text : $form . find ( ` .tab.segment[data-tab=" ${ $tabMenu . data ( 'write' ) } "] textarea ` ) . val ( )
} , ( data ) => {
const $previewPanel = $form . find ( ` .tab.segment[data-tab=" ${ $tabMenu . data ( 'preview' ) } "] ` ) ;
$previewPanel . html ( data ) ;
$ ( 'pre code' , $previewPanel [ 0 ] ) . each ( function ( ) {
2020-04-13 16:02:31 +03:00
highlight ( this ) ;
2019-11-15 00:39:51 +03:00
} ) ;
2019-11-13 17:52:13 +03:00
} ) ;
2019-11-15 00:39:51 +03:00
} ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
buttonsClickOnEnter ( ) ;
2019-11-13 17:52:13 +03:00
}
function initEditPreviewTab ( $form ) {
2019-11-15 00:39:51 +03:00
const $tabMenu = $form . find ( '.tabular.menu' ) ;
$tabMenu . find ( '.item' ) . tab ( ) ;
const $previewTab = $tabMenu . find ( ` .item[data-tab=" ${ $tabMenu . data ( 'preview' ) } "] ` ) ;
if ( $previewTab . length ) {
previewFileModes = $previewTab . data ( 'preview-file-modes' ) . split ( ',' ) ;
2020-04-19 01:46:29 +03:00
$previewTab . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
const $this = $ ( this ) ;
2020-05-03 23:17:24 +03:00
let context = ` ${ $this . data ( 'context' ) } / ` ;
2020-04-20 13:32:44 +03:00
const treePathEl = $form . find ( 'input#tree_path' ) ;
if ( treePathEl . length > 0 ) {
context += treePathEl . val ( ) ;
}
context = context . substring ( 0 , context . lastIndexOf ( '/' ) ) ;
2019-11-15 00:39:51 +03:00
$ . post ( $this . data ( 'url' ) , {
_csrf : csrf ,
mode : 'gfm' ,
2020-04-20 13:32:44 +03:00
context ,
2019-11-15 00:39:51 +03:00
text : $form . find ( ` .tab.segment[data-tab=" ${ $tabMenu . data ( 'write' ) } "] textarea ` ) . val ( )
} , ( data ) => {
const $previewPanel = $form . find ( ` .tab.segment[data-tab=" ${ $tabMenu . data ( 'preview' ) } "] ` ) ;
$previewPanel . html ( data ) ;
$ ( 'pre code' , $previewPanel [ 0 ] ) . each ( function ( ) {
2020-04-13 16:02:31 +03:00
highlight ( this ) ;
2019-11-13 17:52:13 +03:00
} ) ;
2019-11-15 00:39:51 +03:00
} ) ;
} ) ;
}
2019-11-13 17:52:13 +03:00
}
function initEditDiffTab ( $form ) {
2019-11-15 00:39:51 +03:00
const $tabMenu = $form . find ( '.tabular.menu' ) ;
$tabMenu . find ( '.item' ) . tab ( ) ;
2020-04-19 01:46:29 +03:00
$tabMenu . find ( ` .item[data-tab=" ${ $tabMenu . data ( 'diff' ) } "] ` ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
const $this = $ ( this ) ;
$ . post ( $this . data ( 'url' ) , {
_csrf : csrf ,
context : $this . data ( 'context' ) ,
content : $form . find ( ` .tab.segment[data-tab=" ${ $tabMenu . data ( 'write' ) } "] textarea ` ) . val ( )
} , ( data ) => {
const $diffPreviewPanel = $form . find ( ` .tab.segment[data-tab=" ${ $tabMenu . data ( 'diff' ) } "] ` ) ;
$diffPreviewPanel . html ( data ) ;
2019-11-13 17:52:13 +03:00
} ) ;
2019-11-15 00:39:51 +03:00
} ) ;
2019-11-13 17:52:13 +03:00
}
function initEditForm ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( '.edit.form' ) . length === 0 ) {
return ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
initEditPreviewTab ( $ ( '.edit.form' ) ) ;
initEditDiffTab ( $ ( '.edit.form' ) ) ;
2019-11-13 17:52:13 +03:00
}
function initBranchSelector ( ) {
2019-11-15 00:39:51 +03:00
const $selectBranch = $ ( '.ui.select-branch' ) ;
const $branchMenu = $selectBranch . find ( '.reference-list-menu' ) ;
2020-04-19 01:46:29 +03:00
$branchMenu . find ( '.item:not(.no-select)' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
const selectedValue = $ ( this ) . data ( 'id' ) ;
$ ( $ ( this ) . data ( 'id-selector' ) ) . val ( selectedValue ) ;
$selectBranch . find ( '.ui .branch-name' ) . text ( selectedValue ) ;
} ) ;
2020-04-19 01:46:29 +03:00
$selectBranch . find ( '.reference.column' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
$selectBranch . find ( '.scrolling.reference-list-menu' ) . css ( 'display' , 'none' ) ;
$selectBranch . find ( '.reference .text' ) . removeClass ( 'black' ) ;
$ ( $ ( this ) . data ( 'target' ) ) . css ( 'display' , 'block' ) ;
$ ( this ) . find ( '.text' ) . addClass ( 'black' ) ;
return false ;
} ) ;
2019-11-13 17:52:13 +03:00
}
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>
2020-04-01 07:14:46 +03:00
function initLabelEdit ( ) {
// Create label
const $newLabelPanel = $ ( '.new-label.segment' ) ;
2020-04-19 01:46:29 +03:00
$ ( '.new-label.button' ) . on ( 'click' , ( ) => {
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>
2020-04-01 07:14:46 +03:00
$newLabelPanel . show ( ) ;
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '.new-label.segment .cancel' ) . on ( 'click' , ( ) => {
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>
2020-04-01 07:14:46 +03:00
$newLabelPanel . hide ( ) ;
} ) ;
$ ( '.color-picker' ) . each ( function ( ) {
$ ( this ) . minicolors ( ) ;
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '.precolors .color' ) . on ( 'click' , function ( ) {
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>
2020-04-01 07:14:46 +03:00
const color _hex = $ ( this ) . data ( 'color-hex' ) ;
$ ( '.color-picker' ) . val ( color _hex ) ;
$ ( '.minicolors-swatch-color' ) . css ( 'background-color' , color _hex ) ;
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '.edit-label-button' ) . on ( 'click' , function ( ) {
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>
2020-04-01 07:14:46 +03:00
$ ( '#label-modal-id' ) . val ( $ ( this ) . data ( 'id' ) ) ;
$ ( '.edit-label .new-label-input' ) . val ( $ ( this ) . data ( 'title' ) ) ;
$ ( '.edit-label .new-label-desc-input' ) . val ( $ ( this ) . data ( 'description' ) ) ;
$ ( '.edit-label .color-picker' ) . val ( $ ( this ) . data ( 'color' ) ) ;
$ ( '.minicolors-swatch-color' ) . css ( 'background-color' , $ ( this ) . data ( 'color' ) ) ;
$ ( '.edit-label.modal' ) . modal ( {
onApprove ( ) {
2020-04-19 01:46:29 +03:00
$ ( '.edit-label.form' ) . trigger ( 'submit' ) ;
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>
2020-04-01 07:14:46 +03:00
}
} ) . modal ( 'show' ) ;
return false ;
} ) ;
}
2020-04-06 19:33:34 +03:00
function updateIssuesMeta ( url , action , issueIds , elementId , isAdd ) {
2019-11-15 00:39:51 +03:00
return new Promise ( ( ( resolve ) => {
$ . ajax ( {
type : 'POST' ,
url ,
data : {
_csrf : csrf ,
action ,
issue _ids : issueIds ,
2020-04-06 19:33:34 +03:00
id : elementId ,
is _add : isAdd
2019-11-15 00:39:51 +03:00
} ,
success : resolve
} ) ;
} ) ) ;
2019-11-13 17:52:13 +03:00
}
function initRepoStatusChecker ( ) {
2019-11-15 00:39:51 +03:00
const migrating = $ ( '#repo_migrating' ) ;
$ ( '#repo_migrating_failed' ) . hide ( ) ;
if ( migrating ) {
const repo _name = migrating . attr ( 'repo' ) ;
if ( typeof repo _name === 'undefined' ) {
return ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
$ . ajax ( {
type : 'GET' ,
2020-02-12 04:53:18 +03:00
url : ` ${ AppSubUrl } / ${ repo _name } /status ` ,
2019-11-15 00:39:51 +03:00
data : {
_csrf : csrf ,
} ,
complete ( xhr ) {
if ( xhr . status === 200 ) {
if ( xhr . responseJSON ) {
if ( xhr . responseJSON . status === 0 ) {
window . location . reload ( ) ;
return ;
}
setTimeout ( ( ) => {
initRepoStatusChecker ( ) ;
} , 2000 ) ;
return ;
}
}
$ ( '#repo_migrating_progress' ) . hide ( ) ;
$ ( '#repo_migrating_failed' ) . show ( ) ;
}
} ) ;
}
2019-11-13 17:52:13 +03:00
}
function initReactionSelector ( parent ) {
2019-11-15 00:39:51 +03:00
let reactions = '' ;
if ( ! parent ) {
parent = $ ( document ) ;
reactions = '.reactions > ' ;
}
2019-11-13 17:52:13 +03:00
2020-03-11 22:34:54 +03:00
parent . find ( ` ${ reactions } a.label ` ) . popup ( { position : 'bottom left' , metadata : { content : 'title' , title : 'none' } } ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
parent . find ( ` .select-reaction > .menu > .item, ${ reactions } a.label ` ) . on ( 'click' , function ( e ) {
const vm = this ;
e . preventDefault ( ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
if ( $ ( this ) . hasClass ( 'disabled' ) ) return ;
2020-03-11 22:34:54 +03:00
const actionURL = $ ( this ) . hasClass ( 'item' ) ? $ ( this ) . closest ( '.select-reaction' ) . data ( 'action-url' ) : $ ( this ) . data ( 'action-url' ) ;
2019-11-15 00:39:51 +03:00
const url = ` ${ actionURL } / ${ $ ( this ) . hasClass ( 'blue' ) ? 'unreact' : 'react' } ` ;
$ . ajax ( {
type : 'POST' ,
url ,
data : {
_csrf : csrf ,
content : $ ( this ) . data ( 'content' )
}
} ) . done ( ( resp ) => {
if ( resp && ( resp . html || resp . empty ) ) {
const content = $ ( vm ) . closest ( '.content' ) ;
let react = content . find ( '.segment.reactions' ) ;
if ( ! resp . empty && react . length > 0 ) {
react . remove ( ) ;
}
if ( ! resp . empty ) {
react = $ ( '<div class="ui attached segment reactions"></div>' ) ;
const attachments = content . find ( '.segment.bottom:first' ) ;
if ( attachments . length > 0 ) {
react . insertBefore ( attachments ) ;
} else {
react . appendTo ( content ) ;
}
react . html ( resp . html ) ;
react . find ( '.dropdown' ) . dropdown ( ) ;
initReactionSelector ( react ) ;
}
}
} ) ;
} ) ;
2019-11-13 17:52:13 +03:00
}
function insertAtCursor ( field , value ) {
2019-11-15 00:39:51 +03:00
if ( field . selectionStart || field . selectionStart === 0 ) {
const startPos = field . selectionStart ;
const endPos = field . selectionEnd ;
2020-03-11 22:34:54 +03:00
field . value = field . value . substring ( 0 , startPos ) + value + field . value . substring ( endPos , field . value . length ) ;
2019-11-15 00:39:51 +03:00
field . selectionStart = startPos + value . length ;
field . selectionEnd = startPos + value . length ;
} else {
field . value += value ;
}
2019-11-13 17:52:13 +03:00
}
function replaceAndKeepCursor ( field , oldval , newval ) {
2019-11-15 00:39:51 +03:00
if ( field . selectionStart || field . selectionStart === 0 ) {
const startPos = field . selectionStart ;
const endPos = field . selectionEnd ;
field . value = field . value . replace ( oldval , newval ) ;
field . selectionStart = startPos + newval . length - oldval . length ;
field . selectionEnd = endPos + newval . length - oldval . length ;
} else {
field . value = field . value . replace ( oldval , newval ) ;
}
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
function retrieveImageFromClipboardAsBlob ( pasteEvent , callback ) {
if ( ! pasteEvent . clipboardData ) {
return ;
}
2019-11-13 17:52:13 +03:00
2020-03-11 22:34:54 +03:00
const { items } = pasteEvent . clipboardData ;
2019-11-15 00:39:51 +03:00
if ( typeof items === 'undefined' ) {
return ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
for ( let i = 0 ; i < items . length ; i ++ ) {
2020-03-11 22:34:54 +03:00
if ( ! items [ i ] . type . includes ( 'image' ) ) continue ;
2019-11-15 00:39:51 +03:00
const blob = items [ i ] . getAsFile ( ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
if ( typeof ( callback ) === 'function' ) {
pasteEvent . preventDefault ( ) ;
pasteEvent . stopPropagation ( ) ;
callback ( blob ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
}
2019-11-13 17:52:13 +03:00
}
function uploadFile ( file , callback ) {
2019-11-15 00:39:51 +03:00
const xhr = new XMLHttpRequest ( ) ;
2019-11-13 17:52:13 +03:00
2020-03-11 22:34:54 +03:00
xhr . addEventListener ( 'load' , ( ) => {
2019-11-15 00:39:51 +03:00
if ( xhr . status === 200 ) {
callback ( xhr . responseText ) ;
}
2020-03-11 22:34:54 +03:00
} ) ;
2019-11-13 17:52:13 +03:00
2020-02-12 04:53:18 +03:00
xhr . open ( 'post' , ` ${ AppSubUrl } /attachments ` , true ) ;
2019-11-15 00:39:51 +03:00
xhr . setRequestHeader ( 'X-Csrf-Token' , csrf ) ;
const formData = new FormData ( ) ;
formData . append ( 'file' , file , file . name ) ;
xhr . send ( formData ) ;
2019-11-13 17:52:13 +03:00
}
function reload ( ) {
2019-11-15 00:39:51 +03:00
window . location . reload ( ) ;
2019-11-13 17:52:13 +03:00
}
function initImagePaste ( target ) {
2019-11-15 00:39:51 +03:00
target . each ( function ( ) {
const field = this ;
field . addEventListener ( 'paste' , ( event ) => {
retrieveImageFromClipboardAsBlob ( event , ( img ) => {
const name = img . name . substr ( 0 , img . name . lastIndexOf ( '.' ) ) ;
insertAtCursor ( field , ` ![ ${ name } ]() ` ) ;
uploadFile ( img , ( res ) => {
const data = JSON . parse ( res ) ;
2020-02-12 04:53:18 +03:00
replaceAndKeepCursor ( field , ` ![ ${ name } ]() ` , ` ![ ${ name } ]( ${ AppSubUrl } /attachments/ ${ data . uuid } ) ` ) ;
2019-11-15 00:39:51 +03:00
const input = $ ( ` <input id=" ${ data . uuid } " name="files" type="hidden"> ` ) . val ( data . uuid ) ;
$ ( '.files' ) . append ( input ) ;
} ) ;
} ) ;
} , false ) ;
} ) ;
2019-11-13 17:52:13 +03:00
}
2019-12-16 18:56:35 +03:00
function initSimpleMDEImagePaste ( simplemde , files ) {
simplemde . codemirror . on ( 'paste' , ( _ , event ) => {
retrieveImageFromClipboardAsBlob ( event , ( img ) => {
const name = img . name . substr ( 0 , img . name . lastIndexOf ( '.' ) ) ;
uploadFile ( img , ( res ) => {
const data = JSON . parse ( res ) ;
const pos = simplemde . codemirror . getCursor ( ) ;
2020-02-12 04:53:18 +03:00
simplemde . codemirror . replaceRange ( ` ![ ${ name } ]( ${ AppSubUrl } /attachments/ ${ data . uuid } ) ` , pos ) ;
2019-12-16 18:56:35 +03:00
const input = $ ( ` <input id=" ${ data . uuid } " name="files" type="hidden"> ` ) . val ( data . uuid ) ;
files . append ( input ) ;
} ) ;
} ) ;
} ) ;
}
2020-01-14 23:47:38 +03:00
let autoSimpleMDE ;
2019-11-13 17:52:13 +03:00
function initCommentForm ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( '.comment.form' ) . length === 0 ) {
return ;
}
2020-01-14 23:47:38 +03:00
autoSimpleMDE = setCommentSimpleMDE ( $ ( '.comment.form textarea:not(.review-textarea)' ) ) ;
2019-11-15 00:39:51 +03:00
initBranchSelector ( ) ;
initCommentPreviewTab ( $ ( '.comment.form' ) ) ;
initImagePaste ( $ ( '.comment.form textarea' ) ) ;
// Listsubmit
function initListSubmits ( selector , outerSelector ) {
const $list = $ ( ` .ui. ${ outerSelector } .list ` ) ;
const $noSelect = $list . find ( '.no-select' ) ;
const $listMenu = $ ( ` . ${ selector } .menu ` ) ;
let hasLabelUpdateAction = $listMenu . data ( 'action' ) === 'update' ;
const labels = { } ;
$ ( ` . ${ selector } ` ) . dropdown ( 'setting' , 'onHide' , ( ) => {
hasLabelUpdateAction = $listMenu . data ( 'action' ) === 'update' ; // Update the var
if ( hasLabelUpdateAction ) {
const promises = [ ] ;
Object . keys ( labels ) . forEach ( ( elementId ) => {
const label = labels [ elementId ] ;
const promise = updateIssuesMeta (
label [ 'update-url' ] ,
label . action ,
label [ 'issue-id' ] ,
2020-04-06 19:33:34 +03:00
elementId ,
label [ 'is-checked' ]
2019-11-15 00:39:51 +03:00
) ;
promises . push ( promise ) ;
2019-11-13 17:52:13 +03:00
} ) ;
2019-11-15 00:39:51 +03:00
Promise . all ( promises ) . then ( reload ) ;
}
} ) ;
2020-04-19 01:46:29 +03:00
$listMenu . find ( '.item:not(.no-select)' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
// we don't need the action attribute when updating assignees
2020-04-06 19:33:34 +03:00
if ( selector === 'select-assignees-modify' || selector === 'select-reviewers-modify' ) {
2019-11-15 00:39:51 +03:00
// UI magic. We need to do this here, otherwise it would destroy the functionality of
// adding/removing labels
2020-04-06 19:33:34 +03:00
if ( $ ( this ) . data ( 'can-change' ) === 'block' ) {
return false ;
}
2019-11-15 00:39:51 +03:00
if ( $ ( this ) . hasClass ( 'checked' ) ) {
$ ( this ) . removeClass ( 'checked' ) ;
2020-02-11 20:02:41 +03:00
$ ( this ) . find ( '.octicon-check' ) . addClass ( 'invisible' ) ;
2020-04-06 19:33:34 +03:00
$ ( this ) . data ( 'is-checked' , 'remove' ) ;
2019-11-15 00:39:51 +03:00
} else {
$ ( this ) . addClass ( 'checked' ) ;
2020-02-11 20:02:41 +03:00
$ ( this ) . find ( '.octicon-check' ) . removeClass ( 'invisible' ) ;
2020-04-06 19:33:34 +03:00
$ ( this ) . data ( 'is-checked' , 'add' ) ;
2019-11-15 00:39:51 +03:00
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
updateIssuesMeta (
$listMenu . data ( 'update-url' ) ,
'' ,
$listMenu . data ( 'issue-id' ) ,
2020-04-06 19:33:34 +03:00
$ ( this ) . data ( 'id' ) ,
$ ( this ) . data ( 'is-checked' )
2019-11-15 00:39:51 +03:00
) ;
$listMenu . data ( 'action' , 'update' ) ; // Update to reload the page when we updated items
return false ;
}
if ( $ ( this ) . hasClass ( 'checked' ) ) {
$ ( this ) . removeClass ( 'checked' ) ;
2020-02-11 20:02:41 +03:00
$ ( this ) . find ( '.octicon-check' ) . addClass ( 'invisible' ) ;
2019-11-15 00:39:51 +03:00
if ( hasLabelUpdateAction ) {
if ( ! ( $ ( this ) . data ( 'id' ) in labels ) ) {
labels [ $ ( this ) . data ( 'id' ) ] = {
'update-url' : $listMenu . data ( 'update-url' ) ,
action : 'detach' ,
'issue-id' : $listMenu . data ( 'issue-id' ) ,
} ;
} else {
delete labels [ $ ( this ) . data ( 'id' ) ] ;
}
}
} else {
$ ( this ) . addClass ( 'checked' ) ;
2020-02-11 20:02:41 +03:00
$ ( this ) . find ( '.octicon-check' ) . removeClass ( 'invisible' ) ;
2019-11-15 00:39:51 +03:00
if ( hasLabelUpdateAction ) {
if ( ! ( $ ( this ) . data ( 'id' ) in labels ) ) {
labels [ $ ( this ) . data ( 'id' ) ] = {
'update-url' : $listMenu . data ( 'update-url' ) ,
action : 'attach' ,
'issue-id' : $listMenu . data ( 'issue-id' ) ,
} ;
} else {
delete labels [ $ ( this ) . data ( 'id' ) ] ;
}
}
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
const listIds = [ ] ;
$ ( this ) . parent ( ) . find ( '.item' ) . each ( function ( ) {
if ( $ ( this ) . hasClass ( 'checked' ) ) {
listIds . push ( $ ( this ) . data ( 'id' ) ) ;
$ ( $ ( this ) . data ( 'id-selector' ) ) . removeClass ( 'hide' ) ;
} else {
$ ( $ ( this ) . data ( 'id-selector' ) ) . addClass ( 'hide' ) ;
}
} ) ;
if ( listIds . length === 0 ) {
$noSelect . removeClass ( 'hide' ) ;
} else {
$noSelect . addClass ( 'hide' ) ;
}
$ ( $ ( this ) . parent ( ) . data ( 'id' ) ) . val ( listIds . join ( ',' ) ) ;
return false ;
} ) ;
2020-04-19 01:46:29 +03:00
$listMenu . find ( '.no-select.item' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( hasLabelUpdateAction || selector === 'select-assignees-modify' ) {
updateIssuesMeta (
$listMenu . data ( 'update-url' ) ,
'clear' ,
$listMenu . data ( 'issue-id' ) ,
2020-04-06 19:33:34 +03:00
'' ,
2019-11-15 00:39:51 +03:00
''
) . then ( reload ) ;
}
$ ( this ) . parent ( ) . find ( '.item' ) . each ( function ( ) {
$ ( this ) . removeClass ( 'checked' ) ;
2020-02-11 20:02:41 +03:00
$ ( this ) . find ( '.octicon' ) . addClass ( 'invisible' ) ;
2020-04-06 19:33:34 +03:00
$ ( this ) . data ( 'is-checked' , 'remove' ) ;
2019-11-15 00:39:51 +03:00
} ) ;
$list . find ( '.item' ) . each ( function ( ) {
$ ( this ) . addClass ( 'hide' ) ;
} ) ;
$noSelect . removeClass ( 'hide' ) ;
$ ( $ ( this ) . parent ( ) . data ( 'id' ) ) . val ( '' ) ;
} ) ;
}
// Init labels and assignees
initListSubmits ( 'select-label' , 'labels' ) ;
initListSubmits ( 'select-assignees' , 'assignees' ) ;
initListSubmits ( 'select-assignees-modify' , 'assignees' ) ;
2020-04-06 19:33:34 +03:00
initListSubmits ( 'select-reviewers-modify' , 'assignees' ) ;
2019-11-15 00:39:51 +03:00
function selectItem ( select _id , input _id ) {
const $menu = $ ( ` ${ select _id } .menu ` ) ;
const $list = $ ( ` .ui ${ select _id } .list ` ) ;
const hasUpdateAction = $menu . data ( 'action' ) === 'update' ;
2020-04-19 01:46:29 +03:00
$menu . find ( '.item:not(.no-select)' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
$ ( this ) . parent ( ) . find ( '.item' ) . each ( function ( ) {
$ ( this ) . removeClass ( 'selected active' ) ;
} ) ;
$ ( this ) . addClass ( 'selected active' ) ;
if ( hasUpdateAction ) {
updateIssuesMeta (
$menu . data ( 'update-url' ) ,
'' ,
$menu . data ( 'issue-id' ) ,
2020-04-06 19:33:34 +03:00
$ ( this ) . data ( 'id' ) ,
$ ( this ) . data ( 'is-checked' )
2019-11-15 00:39:51 +03:00
) . then ( reload ) ;
}
switch ( input _id ) {
case '#milestone_id' :
$list . find ( '.selected' ) . html ( ` <a class="item" href= ${ $ ( this ) . data ( 'href' ) } > ${
htmlEncode ( $ ( this ) . text ( ) ) } < / a > ` ) ;
break ;
case '#assignee_id' :
2020-03-11 22:34:54 +03:00
$list . find ( '.selected' ) . html ( ` <a class="item" href= ${ $ ( this ) . data ( 'href' ) } > ` +
` <img class="ui avatar image" src= ${ $ ( this ) . data ( 'avatar' ) } > ${
2019-11-15 00:39:51 +03:00
htmlEncode ( $ ( this ) . text ( ) ) } < / a > ` ) ;
}
$ ( ` .ui ${ select _id } .list .no-select ` ) . addClass ( 'hide' ) ;
$ ( input _id ) . val ( $ ( this ) . data ( 'id' ) ) ;
} ) ;
2020-04-19 01:46:29 +03:00
$menu . find ( '.no-select.item' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
$ ( this ) . parent ( ) . find ( '.item:not(.no-select)' ) . each ( function ( ) {
$ ( this ) . removeClass ( 'selected active' ) ;
} ) ;
if ( hasUpdateAction ) {
updateIssuesMeta (
$menu . data ( 'update-url' ) ,
'' ,
$menu . data ( 'issue-id' ) ,
2020-04-06 19:33:34 +03:00
$ ( this ) . data ( 'id' ) ,
$ ( this ) . data ( 'is-checked' )
2019-11-15 00:39:51 +03:00
) . then ( reload ) ;
}
$list . find ( '.selected' ) . html ( '' ) ;
$list . find ( '.no-select' ) . removeClass ( 'hide' ) ;
$ ( input _id ) . val ( '' ) ;
} ) ;
}
// Milestone and assignee
selectItem ( '.select-milestone' , '#milestone_id' ) ;
selectItem ( '.select-assignee' , '#assignee_id' ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function initInstall ( ) {
if ( $ ( '.install' ) . length === 0 ) {
return ;
}
if ( $ ( '#db_host' ) . val ( ) === '' ) {
$ ( '#db_host' ) . val ( '127.0.0.1:3306' ) ;
$ ( '#db_user' ) . val ( 'gitea' ) ;
$ ( '#db_name' ) . val ( 'gitea' ) ;
}
// Database type change detection.
2020-04-19 01:46:29 +03:00
$ ( '#db_type' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
const sqliteDefault = 'data/gitea.db' ;
const tidbDefault = 'data/gitea_tidb' ;
const dbType = $ ( this ) . val ( ) ;
if ( dbType === 'SQLite3' ) {
$ ( '#sql_settings' ) . hide ( ) ;
$ ( '#pgsql_settings' ) . hide ( ) ;
$ ( '#mysql_settings' ) . hide ( ) ;
$ ( '#sqlite_settings' ) . show ( ) ;
if ( dbType === 'SQLite3' && $ ( '#db_path' ) . val ( ) === tidbDefault ) {
$ ( '#db_path' ) . val ( sqliteDefault ) ;
}
return ;
}
const dbDefaults = {
MySQL : '127.0.0.1:3306' ,
PostgreSQL : '127.0.0.1:5432' ,
MSSQL : '127.0.0.1:1433'
} ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
$ ( '#sqlite_settings' ) . hide ( ) ;
$ ( '#sql_settings' ) . show ( ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
$ ( '#pgsql_settings' ) . toggle ( dbType === 'PostgreSQL' ) ;
$ ( '#mysql_settings' ) . toggle ( dbType === 'MySQL' ) ;
$ . each ( dbDefaults , ( _type , defaultHost ) => {
if ( $ ( '#db_host' ) . val ( ) === defaultHost ) {
$ ( '#db_host' ) . val ( dbDefaults [ dbType ] ) ;
return false ;
}
} ) ;
} ) ;
// TODO: better handling of exclusive relations.
2020-04-19 01:46:29 +03:00
$ ( '#offline-mode input' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( this ) . is ( ':checked' ) ) {
$ ( '#disable-gravatar' ) . checkbox ( 'check' ) ;
$ ( '#federated-avatar-lookup' ) . checkbox ( 'uncheck' ) ;
}
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '#disable-gravatar input' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( this ) . is ( ':checked' ) ) {
$ ( '#federated-avatar-lookup' ) . checkbox ( 'uncheck' ) ;
} else {
$ ( '#offline-mode' ) . checkbox ( 'uncheck' ) ;
}
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '#federated-avatar-lookup input' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( this ) . is ( ':checked' ) ) {
$ ( '#disable-gravatar' ) . checkbox ( 'uncheck' ) ;
$ ( '#offline-mode' ) . checkbox ( 'uncheck' ) ;
}
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '#enable-openid-signin input' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( this ) . is ( ':checked' ) ) {
if ( ! $ ( '#disable-registration input' ) . is ( ':checked' ) ) {
$ ( '#enable-openid-signup' ) . checkbox ( 'check' ) ;
}
} else {
$ ( '#enable-openid-signup' ) . checkbox ( 'uncheck' ) ;
}
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '#disable-registration input' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( this ) . is ( ':checked' ) ) {
$ ( '#enable-captcha' ) . checkbox ( 'uncheck' ) ;
$ ( '#enable-openid-signup' ) . checkbox ( 'uncheck' ) ;
} else {
$ ( '#enable-openid-signup' ) . checkbox ( 'check' ) ;
}
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '#enable-captcha input' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( this ) . is ( ':checked' ) ) {
$ ( '#disable-registration' ) . checkbox ( 'uncheck' ) ;
}
} ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-25 14:50:46 +03:00
function initIssueComments ( ) {
2020-04-11 07:44:50 +03:00
if ( $ ( '.repository.view.issue .timeline' ) . length === 0 ) return ;
$ ( '.re-request-review' ) . on ( 'click' , function ( event ) {
const url = $ ( this ) . data ( 'update-url' ) ;
const issueId = $ ( this ) . data ( 'issue-id' ) ;
const id = $ ( this ) . data ( 'id' ) ;
const isChecked = $ ( this ) . data ( 'is-checked' ) ;
2019-11-25 14:50:46 +03:00
2020-04-06 19:33:34 +03:00
event . preventDefault ( ) ;
updateIssuesMeta (
2020-04-11 07:44:50 +03:00
url ,
2020-04-06 19:33:34 +03:00
'' ,
2020-04-11 07:44:50 +03:00
issueId ,
id ,
isChecked
2020-04-06 19:33:34 +03:00
) . then ( reload ) ;
} ) ;
2020-04-19 01:46:29 +03:00
$ ( document ) . on ( 'click' , ( event ) => {
2019-11-25 14:50:46 +03:00
const urlTarget = $ ( ':target' ) ;
if ( urlTarget . length === 0 ) return ;
const urlTargetId = urlTarget . attr ( 'id' ) ;
if ( ! urlTargetId ) return ;
if ( ! /^(issue|pull)(comment)?-\d+$/ . test ( urlTargetId ) ) return ;
const $target = $ ( event . target ) ;
if ( $target . closest ( ` # ${ urlTargetId } ` ) . length === 0 ) {
const scrollPosition = $ ( window ) . scrollTop ( ) ;
window . location . hash = '' ;
$ ( window ) . scrollTop ( scrollPosition ) ;
window . history . pushState ( null , null , ' ' ) ;
}
} ) ;
}
2020-03-17 22:08:15 +03:00
async function initRepository ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( '.repository' ) . length === 0 ) {
return ;
}
function initFilterSearchDropdown ( selector ) {
const $dropdown = $ ( selector ) ;
$dropdown . dropdown ( {
fullTextSearch : true ,
selectOnKeydown : false ,
onChange ( _text , _value , $choice ) {
if ( $choice . data ( 'url' ) ) {
window . location . href = $choice . data ( 'url' ) ;
}
} ,
2020-03-11 22:34:54 +03:00
message : { noResults : $dropdown . data ( 'no-results' ) }
2019-11-15 00:39:51 +03:00
} ) ;
}
// File list and commits
if ( $ ( '.repository.file.list' ) . length > 0 || ( '.repository.commits' ) . length > 0 ) {
initFilterBranchTagDropdown ( '.choose.reference .dropdown' ) ;
}
// Wiki
if ( $ ( '.repository.wiki.view' ) . length > 0 ) {
initFilterSearchDropdown ( '.choose.page .dropdown' ) ;
}
// Options
if ( $ ( '.repository.settings.options' ) . length > 0 ) {
// Enable or select internal/external wiki system and issue tracker.
2020-04-19 01:46:29 +03:00
$ ( '.enable-system' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( this . checked ) {
$ ( $ ( this ) . data ( 'target' ) ) . removeClass ( 'disabled' ) ;
if ( ! $ ( this ) . data ( 'context' ) ) $ ( $ ( this ) . data ( 'context' ) ) . addClass ( 'disabled' ) ;
} else {
$ ( $ ( this ) . data ( 'target' ) ) . addClass ( 'disabled' ) ;
if ( ! $ ( this ) . data ( 'context' ) ) $ ( $ ( this ) . data ( 'context' ) ) . removeClass ( 'disabled' ) ;
}
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '.enable-system-radio' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( this . value === 'false' ) {
$ ( $ ( this ) . data ( 'target' ) ) . addClass ( 'disabled' ) ;
if ( typeof $ ( this ) . data ( 'context' ) !== 'undefined' ) $ ( $ ( this ) . data ( 'context' ) ) . removeClass ( 'disabled' ) ;
} else if ( this . value === 'true' ) {
$ ( $ ( this ) . data ( 'target' ) ) . removeClass ( 'disabled' ) ;
if ( typeof $ ( this ) . data ( 'context' ) !== 'undefined' ) $ ( $ ( this ) . data ( 'context' ) ) . addClass ( 'disabled' ) ;
}
} ) ;
}
// Labels
if ( $ ( '.repository.labels' ) . length > 0 ) {
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>
2020-04-01 07:14:46 +03:00
initLabelEdit ( ) ;
2019-11-15 00:39:51 +03:00
}
// Milestones
if ( $ ( '.repository.new.milestone' ) . length > 0 ) {
const $datepicker = $ ( '.milestone.datepicker' ) ;
2020-03-17 22:08:15 +03:00
await initDateTimePicker ( $datepicker . data ( 'lang' ) ) ;
2019-11-15 00:39:51 +03:00
$datepicker . datetimepicker ( {
inline : true ,
timepicker : false ,
startDate : $datepicker . data ( 'start-date' ) ,
2020-03-17 22:08:15 +03:00
onSelectDate ( date ) {
$ ( '#deadline' ) . val ( date . toISOString ( ) . substring ( 0 , 10 ) ) ;
} ,
2019-11-15 00:39:51 +03:00
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '#clear-date' ) . on ( 'click' , ( ) => {
2019-11-15 00:39:51 +03:00
$ ( '#deadline' ) . val ( '' ) ;
return false ;
} ) ;
}
// Issues
if ( $ ( '.repository.view.issue' ) . length > 0 ) {
// Edit issue title
const $issueTitle = $ ( '#issue-title' ) ;
const $editInput = $ ( '#edit-title-input input' ) ;
const editTitleToggle = function ( ) {
$issueTitle . toggle ( ) ;
$ ( '.not-in-edit' ) . toggle ( ) ;
$ ( '#edit-title-input' ) . toggle ( ) ;
2019-12-16 09:20:25 +03:00
$ ( '#pull-desc' ) . toggle ( ) ;
$ ( '#pull-desc-edit' ) . toggle ( ) ;
2019-11-15 00:39:51 +03:00
$ ( '.in-edit' ) . toggle ( ) ;
$editInput . focus ( ) ;
return false ;
} ;
2019-12-16 09:20:25 +03:00
const changeBranchSelect = function ( ) {
const selectionTextField = $ ( '#pull-target-branch' ) ;
const baseName = selectionTextField . data ( 'basename' ) ;
const branchNameNew = $ ( this ) . data ( 'branch' ) ;
const branchNameOld = selectionTextField . data ( 'branch' ) ;
// Replace branch name to keep translation from HTML template
selectionTextField . html ( selectionTextField . html ( ) . replace (
` ${ baseName } : ${ branchNameOld } ` ,
` ${ baseName } : ${ branchNameNew } `
) ) ;
selectionTextField . data ( 'branch' , branchNameNew ) ; // update branch name in setting
} ;
2020-04-19 01:46:29 +03:00
$ ( '#branch-select > .item' ) . on ( 'click' , changeBranchSelect ) ;
2019-12-16 09:20:25 +03:00
2020-04-19 01:46:29 +03:00
$ ( '#edit-title' ) . on ( 'click' , editTitleToggle ) ;
$ ( '#cancel-edit-title' ) . on ( 'click' , editTitleToggle ) ;
$ ( '#save-edit-title' ) . on ( 'click' , editTitleToggle ) . on ( 'click' , function ( ) {
2019-12-16 09:20:25 +03:00
const pullrequest _targetbranch _change = function ( update _url ) {
const targetBranch = $ ( '#pull-target-branch' ) . data ( 'branch' ) ;
const $branchTarget = $ ( '#branch_target' ) ;
if ( targetBranch === $branchTarget . text ( ) ) {
return false ;
}
$ . post ( update _url , {
_csrf : csrf ,
target _branch : targetBranch
2020-05-12 06:34:48 +03:00
} ) . done ( ( data ) => {
2020-03-11 22:34:54 +03:00
$branchTarget . text ( data . base _branch ) ;
} ) . always ( ( ) => {
reload ( ) ;
} ) ;
2019-12-16 09:20:25 +03:00
} ;
const pullrequest _target _update _url = $ ( this ) . data ( 'target-update-url' ) ;
2019-11-15 00:39:51 +03:00
if ( $editInput . val ( ) . length === 0 || $editInput . val ( ) === $issueTitle . text ( ) ) {
$editInput . val ( $issueTitle . text ( ) ) ;
2019-12-16 09:20:25 +03:00
pullrequest _targetbranch _change ( pullrequest _target _update _url ) ;
} else {
$ . post ( $ ( this ) . data ( 'update-url' ) , {
_csrf : csrf ,
title : $editInput . val ( )
2020-03-11 22:34:54 +03:00
} , ( data ) => {
2019-12-16 09:20:25 +03:00
$editInput . val ( data . title ) ;
$issueTitle . text ( data . title ) ;
pullrequest _targetbranch _change ( pullrequest _target _update _url ) ;
reload ( ) ;
} ) ;
2019-11-15 00:39:51 +03:00
}
return false ;
} ) ;
2019-11-25 14:50:46 +03:00
// Issue Comments
initIssueComments ( ) ;
2019-11-19 21:33:42 +03:00
// Issue/PR Context Menus
$ ( '.context-dropdown' ) . dropdown ( {
action : 'hide'
} ) ;
// Quote reply
2020-04-19 01:46:29 +03:00
$ ( '.quote-reply' ) . on ( 'click' , function ( event ) {
2019-11-19 21:33:42 +03:00
$ ( this ) . closest ( '.dropdown' ) . find ( '.menu' ) . toggle ( 'visible' ) ;
const target = $ ( this ) . data ( 'target' ) ;
2020-01-14 23:47:38 +03:00
const quote = $ ( ` #comment- ${ target } ` ) . text ( ) . replace ( /\n/g , '\n> ' ) ;
const content = ` > ${ quote } \n \n ` ;
2019-11-19 21:33:42 +03:00
let $content ;
if ( $ ( this ) . hasClass ( 'quote-reply-diff' ) ) {
const $parent = $ ( this ) . closest ( '.comment-code-cloud' ) ;
2020-04-19 01:46:29 +03:00
$parent . find ( 'button.comment-form-reply' ) . trigger ( 'click' ) ;
2019-11-19 21:33:42 +03:00
$content = $parent . find ( '[name="content"]' ) ;
2020-01-14 23:47:38 +03:00
if ( $content . val ( ) !== '' ) {
$content . val ( ` ${ $content . val ( ) } \n \n ${ content } ` ) ;
} else {
$content . val ( ` ${ content } ` ) ;
}
$content . focus ( ) ;
} else if ( autoSimpleMDE !== null ) {
if ( autoSimpleMDE . value ( ) !== '' ) {
autoSimpleMDE . value ( ` ${ autoSimpleMDE . value ( ) } \n \n ${ content } ` ) ;
} else {
autoSimpleMDE . value ( ` ${ content } ` ) ;
}
2019-11-19 21:33:42 +03:00
}
event . preventDefault ( ) ;
} ) ;
2019-11-15 00:39:51 +03:00
// Edit issue or comment content
2020-04-19 01:46:29 +03:00
$ ( '.edit-content' ) . on ( 'click' , async function ( event ) {
2019-11-19 21:33:42 +03:00
$ ( this ) . closest ( '.dropdown' ) . find ( '.menu' ) . toggle ( 'visible' ) ;
const $segment = $ ( this ) . closest ( '.header' ) . next ( ) ;
2019-11-15 00:39:51 +03:00
const $editContentZone = $segment . find ( '.edit-content-zone' ) ;
const $renderContent = $segment . find ( '.render-content' ) ;
const $rawContent = $segment . find ( '.raw-content' ) ;
let $textarea ;
2019-12-16 18:56:35 +03:00
let $simplemde ;
2019-11-15 00:39:51 +03:00
// Setup new form
if ( $editContentZone . html ( ) . length === 0 ) {
$editContentZone . html ( $ ( '#edit-content-form' ) . html ( ) ) ;
$textarea = $editContentZone . find ( 'textarea' ) ;
issuesTribute . attach ( $textarea . get ( ) ) ;
emojiTribute . attach ( $textarea . get ( ) ) ;
2020-03-08 00:06:15 +03:00
let dz ;
2019-11-15 00:39:51 +03:00
const $dropzone = $editContentZone . find ( '.dropzone' ) ;
const $files = $editContentZone . find ( '.comment-files' ) ;
if ( $dropzone . length > 0 ) {
2020-03-08 00:06:15 +03:00
$dropzone . data ( 'saved' , false ) ;
2019-11-15 00:39:51 +03:00
const filenameDict = { } ;
2020-03-08 00:06:15 +03:00
dz = await createDropzone ( $dropzone [ 0 ] , {
2019-11-15 00:39:51 +03:00
url : $dropzone . data ( 'upload-url' ) ,
2020-03-11 22:34:54 +03:00
headers : { 'X-Csrf-Token' : csrf } ,
2019-11-15 00:39:51 +03:00
maxFiles : $dropzone . data ( 'max-file' ) ,
maxFilesize : $dropzone . data ( 'max-size' ) ,
acceptedFiles : ( $dropzone . data ( 'accepts' ) === '*/*' ) ? null : $dropzone . data ( 'accepts' ) ,
addRemoveLinks : true ,
dictDefaultMessage : $dropzone . data ( 'default-message' ) ,
dictInvalidFileType : $dropzone . data ( 'invalid-input-type' ) ,
dictFileTooBig : $dropzone . data ( 'file-too-big' ) ,
dictRemoveFile : $dropzone . data ( 'remove-file' ) ,
init ( ) {
this . on ( 'success' , ( file , data ) => {
filenameDict [ file . name ] = {
uuid : data . uuid ,
submitted : false
} ;
const input = $ ( ` <input id=" ${ data . uuid } " name="files" type="hidden"> ` ) . val ( data . uuid ) ;
$files . append ( input ) ;
} ) ;
this . on ( 'removedfile' , ( file ) => {
if ( ! ( file . name in filenameDict ) ) {
return ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
$ ( ` # ${ filenameDict [ file . name ] . uuid } ` ) . remove ( ) ;
if ( $dropzone . data ( 'remove-url' ) && $dropzone . data ( 'csrf' ) && ! filenameDict [ file . name ] . submitted ) {
$ . post ( $dropzone . data ( 'remove-url' ) , {
file : filenameDict [ file . name ] . uuid ,
_csrf : $dropzone . data ( 'csrf' )
} ) ;
}
} ) ;
this . on ( 'submit' , ( ) => {
$ . each ( filenameDict , ( name ) => {
filenameDict [ name ] . submitted = true ;
} ) ;
} ) ;
this . on ( 'reload' , ( ) => {
$ . getJSON ( $editContentZone . data ( 'attachment-url' ) , ( data ) => {
2020-03-08 00:06:15 +03:00
dz . removeAllFiles ( true ) ;
2019-11-15 00:39:51 +03:00
$files . empty ( ) ;
$ . each ( data , function ( ) {
const imgSrc = ` ${ $dropzone . data ( 'upload-url' ) } / ${ this . uuid } ` ;
2020-03-08 00:06:15 +03:00
dz . emit ( 'addedfile' , this ) ;
dz . emit ( 'thumbnail' , this , imgSrc ) ;
dz . emit ( 'complete' , this ) ;
dz . files . push ( this ) ;
2019-11-15 00:39:51 +03:00
filenameDict [ this . name ] = {
submitted : true ,
uuid : this . uuid
} ;
$dropzone . find ( ` img[src=' ${ imgSrc } '] ` ) . css ( 'max-width' , '100%' ) ;
const input = $ ( ` <input id=" ${ this . uuid } " name="files" type="hidden"> ` ) . val ( this . uuid ) ;
$files . append ( input ) ;
} ) ;
} ) ;
} ) ;
}
} ) ;
2020-03-08 00:06:15 +03:00
dz . emit ( 'reload' ) ;
2019-11-15 00:39:51 +03:00
}
// Give new write/preview data-tab name to distinguish from others
const $editContentForm = $editContentZone . find ( '.ui.comment.form' ) ;
const $tabMenu = $editContentForm . find ( '.tabular.menu' ) ;
$tabMenu . attr ( 'data-write' , $editContentZone . data ( 'write' ) ) ;
$tabMenu . attr ( 'data-preview' , $editContentZone . data ( 'preview' ) ) ;
$tabMenu . find ( '.write.item' ) . attr ( 'data-tab' , $editContentZone . data ( 'write' ) ) ;
$tabMenu . find ( '.preview.item' ) . attr ( 'data-tab' , $editContentZone . data ( 'preview' ) ) ;
$editContentForm . find ( '.write.segment' ) . attr ( 'data-tab' , $editContentZone . data ( 'write' ) ) ;
$editContentForm . find ( '.preview.segment' ) . attr ( 'data-tab' , $editContentZone . data ( 'preview' ) ) ;
2019-12-16 18:56:35 +03:00
$simplemde = setCommentSimpleMDE ( $textarea ) ;
commentMDEditors [ $editContentZone . data ( 'write' ) ] = $simplemde ;
2019-11-15 00:39:51 +03:00
initCommentPreviewTab ( $editContentForm ) ;
2019-12-16 18:56:35 +03:00
initSimpleMDEImagePaste ( $simplemde , $files ) ;
2019-11-15 00:39:51 +03:00
2020-04-19 01:46:29 +03:00
$editContentZone . find ( '.cancel.button' ) . on ( 'click' , ( ) => {
2019-11-15 00:39:51 +03:00
$renderContent . show ( ) ;
$editContentZone . hide ( ) ;
2020-03-08 00:06:15 +03:00
dz . emit ( 'reload' ) ;
2019-11-15 00:39:51 +03:00
} ) ;
2020-04-19 01:46:29 +03:00
$editContentZone . find ( '.save.button' ) . on ( 'click' , ( ) => {
2019-11-15 00:39:51 +03:00
$renderContent . show ( ) ;
$editContentZone . hide ( ) ;
const $attachments = $files . find ( '[name=files]' ) . map ( function ( ) {
return $ ( this ) . val ( ) ;
} ) . get ( ) ;
$ . post ( $editContentZone . data ( 'update-url' ) , {
_csrf : csrf ,
content : $textarea . val ( ) ,
context : $editContentZone . data ( 'context' ) ,
files : $attachments
} , ( data ) => {
if ( data . length === 0 ) {
$renderContent . html ( $ ( '#no-content' ) . html ( ) ) ;
} else {
$renderContent . html ( data . content ) ;
$ ( 'pre code' , $renderContent [ 0 ] ) . each ( function ( ) {
2020-04-13 16:02:31 +03:00
highlight ( this ) ;
2019-11-15 00:39:51 +03:00
} ) ;
}
const $content = $segment . parent ( ) ;
if ( ! $content . find ( '.ui.small.images' ) . length ) {
if ( data . attachments !== '' ) {
$content . append (
'<div class="ui bottom attached segment"><div class="ui small images"></div></div>'
) ;
$content . find ( '.ui.small.images' ) . html ( data . attachments ) ;
}
} else if ( data . attachments === '' ) {
$content . find ( '.ui.small.images' ) . parent ( ) . remove ( ) ;
2019-11-13 17:52:13 +03:00
} else {
2019-11-15 00:39:51 +03:00
$content . find ( '.ui.small.images' ) . html ( data . attachments ) ;
2019-11-13 17:52:13 +03:00
}
2020-03-08 00:06:15 +03:00
dz . emit ( 'submit' ) ;
dz . emit ( 'reload' ) ;
2019-11-15 00:39:51 +03:00
} ) ;
2019-11-13 17:52:13 +03:00
} ) ;
2019-11-15 00:39:51 +03:00
} else {
$textarea = $segment . find ( 'textarea' ) ;
2019-12-16 18:56:35 +03:00
$simplemde = commentMDEditors [ $editContentZone . data ( 'write' ) ] ;
2019-11-15 00:39:51 +03:00
}
// Show write/preview tab and copy raw content as needed
$editContentZone . show ( ) ;
$renderContent . hide ( ) ;
if ( $textarea . val ( ) . length === 0 ) {
$textarea . val ( $rawContent . text ( ) ) ;
2019-12-16 18:56:35 +03:00
$simplemde . value ( $rawContent . text ( ) ) ;
2019-11-15 00:39:51 +03:00
}
$textarea . focus ( ) ;
2019-12-16 18:56:35 +03:00
$simplemde . codemirror . focus ( ) ;
2019-11-19 21:33:42 +03:00
event . preventDefault ( ) ;
2019-11-15 00:39:51 +03:00
} ) ;
// Delete comment
2020-04-19 01:46:29 +03:00
$ ( '.delete-comment' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
const $this = $ ( this ) ;
if ( window . confirm ( $this . data ( 'locale' ) ) ) {
$ . post ( $this . data ( 'url' ) , {
_csrf : csrf
2020-05-12 06:34:48 +03:00
} ) . done ( ( ) => {
2019-11-15 00:39:51 +03:00
$ ( ` # ${ $this . data ( 'comment-id' ) } ` ) . remove ( ) ;
2019-11-13 17:52:13 +03:00
} ) ;
2019-11-15 00:39:51 +03:00
}
return false ;
} ) ;
// Change status
const $statusButton = $ ( '#status-button' ) ;
2020-04-19 01:46:29 +03:00
$ ( '#comment-form .edit_area' ) . on ( 'keyup' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( this ) . val ( ) . length === 0 ) {
$statusButton . text ( $statusButton . data ( 'status' ) ) ;
} else {
$statusButton . text ( $statusButton . data ( 'status-and-comment' ) ) ;
}
} ) ;
2020-04-19 01:46:29 +03:00
$statusButton . on ( 'click' , ( ) => {
2019-11-15 00:39:51 +03:00
$ ( '#status' ) . val ( $statusButton . data ( 'status-val' ) ) ;
2020-04-19 01:46:29 +03:00
$ ( '#comment-form' ) . trigger ( 'submit' ) ;
2019-11-15 00:39:51 +03:00
} ) ;
// Pull Request merge button
const $mergeButton = $ ( '.merge-button > button' ) ;
$mergeButton . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
$ ( ` . ${ $ ( this ) . data ( 'do' ) } -fields ` ) . show ( ) ;
$ ( this ) . parent ( ) . hide ( ) ;
} ) ;
$ ( '.merge-button > .dropdown' ) . dropdown ( {
onChange ( _text , _value , $choice ) {
if ( $choice . data ( 'do' ) ) {
$mergeButton . find ( '.button-text' ) . text ( $choice . text ( ) ) ;
$mergeButton . data ( 'do' , $choice . data ( 'do' ) ) ;
}
}
} ) ;
$ ( '.merge-cancel' ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
$ ( this ) . closest ( '.form' ) . hide ( ) ;
$mergeButton . parent ( ) . show ( ) ;
} ) ;
initReactionSelector ( ) ;
}
// Diff
if ( $ ( '.repository.diff' ) . length > 0 ) {
$ ( '.diff-counter' ) . each ( function ( ) {
const $item = $ ( this ) ;
const addLine = $item . find ( 'span[data-line].add' ) . data ( 'line' ) ;
const delLine = $item . find ( 'span[data-line].del' ) . data ( 'line' ) ;
const addPercent = parseFloat ( addLine ) / ( parseFloat ( addLine ) + parseFloat ( delLine ) ) * 100 ;
$item . find ( '.bar .add' ) . css ( 'width' , ` ${ addPercent } % ` ) ;
} ) ;
}
// Quick start and repository home
2020-04-19 01:46:29 +03:00
$ ( '#repo-clone-ssh' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
$ ( '.clone-url' ) . text ( $ ( this ) . data ( 'link' ) ) ;
$ ( '#repo-clone-url' ) . val ( $ ( this ) . data ( 'link' ) ) ;
$ ( this ) . addClass ( 'blue' ) ;
$ ( '#repo-clone-https' ) . removeClass ( 'blue' ) ;
localStorage . setItem ( 'repo-clone-protocol' , 'ssh' ) ;
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '#repo-clone-https' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
$ ( '.clone-url' ) . text ( $ ( this ) . data ( 'link' ) ) ;
$ ( '#repo-clone-url' ) . val ( $ ( this ) . data ( 'link' ) ) ;
$ ( this ) . addClass ( 'blue' ) ;
$ ( '#repo-clone-ssh' ) . removeClass ( 'blue' ) ;
localStorage . setItem ( 'repo-clone-protocol' , 'https' ) ;
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '#repo-clone-url' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
$ ( this ) . select ( ) ;
} ) ;
// Pull request
const $repoComparePull = $ ( '.repository.compare.pull' ) ;
if ( $repoComparePull . length > 0 ) {
initFilterSearchDropdown ( '.choose.branch .dropdown' ) ;
// show pull request form
$repoComparePull . find ( 'button.show-form' ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
$repoComparePull . find ( '.pullrequest-form' ) . show ( ) ;
2020-04-18 07:19:19 +03:00
autoSimpleMDE . codemirror . refresh ( ) ;
2019-11-15 00:39:51 +03:00
$ ( this ) . parent ( ) . hide ( ) ;
} ) ;
}
// Branches
if ( $ ( '.repository.settings.branches' ) . length > 0 ) {
initFilterSearchDropdown ( '.protected-branches .dropdown' ) ;
2020-04-19 01:46:29 +03:00
$ ( '.enable-protection, .enable-whitelist, .enable-statuscheck' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( this . checked ) {
$ ( $ ( this ) . data ( 'target' ) ) . removeClass ( 'disabled' ) ;
} else {
$ ( $ ( this ) . data ( 'target' ) ) . addClass ( 'disabled' ) ;
}
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '.disable-whitelist' ) . on ( 'change' , function ( ) {
2019-12-04 04:08:56 +03:00
if ( this . checked ) {
$ ( $ ( this ) . data ( 'target' ) ) . addClass ( 'disabled' ) ;
}
} ) ;
2019-11-15 00:39:51 +03:00
}
2020-02-11 12:34:17 +03:00
// Language stats
if ( $ ( '.language-stats' ) . length > 0 ) {
$ ( '.language-stats' ) . on ( 'click' , ( e ) => {
e . preventDefault ( ) ;
$ ( '.language-stats-details, .repository-menu' ) . slideToggle ( ) ;
} ) ;
}
2019-11-15 00:39:51 +03:00
}
function initMigration ( ) {
const toggleMigrations = function ( ) {
const authUserName = $ ( '#auth_username' ) . val ( ) ;
const cloneAddr = $ ( '#clone_addr' ) . val ( ) ;
2020-03-11 22:34:54 +03:00
if ( ! $ ( '#mirror' ) . is ( ':checked' ) && ( authUserName && authUserName . length > 0 ) &&
2020-04-19 18:44:11 +03:00
( cloneAddr !== undefined && ( cloneAddr . startsWith ( 'https://github.com' ) || cloneAddr . startsWith ( 'http://github.com' ) || cloneAddr . startsWith ( 'http://gitlab.com' ) || cloneAddr . startsWith ( 'https://gitlab.com' ) ) ) ) {
2019-11-15 00:39:51 +03:00
$ ( '#migrate_items' ) . show ( ) ;
} else {
$ ( '#migrate_items' ) . hide ( ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
} ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
toggleMigrations ( ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
$ ( '#clone_addr' ) . on ( 'input' , toggleMigrations ) ;
$ ( '#auth_username' ) . on ( 'input' , toggleMigrations ) ;
$ ( '#mirror' ) . on ( 'change' , toggleMigrations ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function initPullRequestReview ( ) {
$ ( '.show-outdated' ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
const id = $ ( this ) . data ( 'comment' ) ;
$ ( this ) . addClass ( 'hide' ) ;
$ ( ` #code-comments- ${ id } ` ) . removeClass ( 'hide' ) ;
$ ( ` #code-preview- ${ id } ` ) . removeClass ( 'hide' ) ;
$ ( ` #hide-outdated- ${ id } ` ) . removeClass ( 'hide' ) ;
} ) ;
$ ( '.hide-outdated' ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
const id = $ ( this ) . data ( 'comment' ) ;
$ ( this ) . addClass ( 'hide' ) ;
$ ( ` #code-comments- ${ id } ` ) . addClass ( 'hide' ) ;
$ ( ` #code-preview- ${ id } ` ) . addClass ( 'hide' ) ;
$ ( ` #show-outdated- ${ id } ` ) . removeClass ( 'hide' ) ;
} ) ;
$ ( 'button.comment-form-reply' ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
$ ( this ) . hide ( ) ;
const form = $ ( this ) . parent ( ) . find ( '.comment-form' ) ;
form . removeClass ( 'hide' ) ;
assingMenuAttributes ( form . find ( '.menu' ) ) ;
} ) ;
// The following part is only for diff views
if ( $ ( '.repository.pull.diff' ) . length === 0 ) {
return ;
}
$ ( '.diff-detail-box.ui.sticky' ) . sticky ( ) ;
$ ( '.btn-review' ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
$ ( this ) . closest ( '.dropdown' ) . find ( '.menu' ) . toggle ( 'visible' ) ;
} ) . closest ( '.dropdown' ) . find ( '.link.close' )
. on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
$ ( this ) . closest ( '.menu' ) . toggle ( 'visible' ) ;
} ) ;
$ ( '.code-view .lines-code,.code-view .lines-num' )
. on ( 'mouseenter' , function ( ) {
const parent = $ ( this ) . closest ( 'td' ) ;
$ ( this ) . closest ( 'tr' ) . addClass (
2020-03-11 22:34:54 +03:00
parent . hasClass ( 'lines-num-old' ) || parent . hasClass ( 'lines-code-old' ) ? 'focus-lines-old' : 'focus-lines-new'
2019-11-15 00:39:51 +03:00
) ;
} )
. on ( 'mouseleave' , function ( ) {
$ ( this ) . closest ( 'tr' ) . removeClass ( 'focus-lines-new focus-lines-old' ) ;
} ) ;
$ ( '.add-code-comment' ) . on ( 'click' , function ( e ) {
// https://github.com/go-gitea/gitea/issues/4745
if ( $ ( e . target ) . hasClass ( 'btn-add-single' ) ) {
return ;
}
e . preventDefault ( ) ;
const isSplit = $ ( this ) . closest ( '.code-diff' ) . hasClass ( 'code-diff-split' ) ;
const side = $ ( this ) . data ( 'side' ) ;
const idx = $ ( this ) . data ( 'idx' ) ;
const path = $ ( this ) . data ( 'path' ) ;
const form = $ ( '#pull_review_add_comment' ) . html ( ) ;
const tr = $ ( this ) . closest ( 'tr' ) ;
let ntr = tr . next ( ) ;
if ( ! ntr . hasClass ( 'add-comment' ) ) {
ntr = $ ( ` <tr class="add-comment"> ${
2020-03-11 22:34:54 +03:00
isSplit ? '<td class="lines-num"></td><td class="lines-type-marker"></td><td class="add-comment-left"></td><td class="lines-num"></td><td class="lines-type-marker"></td><td class="add-comment-right"></td>' :
'<td class="lines-num"></td><td class="lines-num"></td><td class="lines-type-marker"></td><td class="add-comment-left add-comment-right"></td>'
2019-11-15 00:39:51 +03:00
} < / t r > ` ) ;
tr . after ( ntr ) ;
}
const td = ntr . find ( ` .add-comment- ${ side } ` ) ;
let commentCloud = td . find ( '.comment-code-cloud' ) ;
if ( commentCloud . length === 0 ) {
td . html ( form ) ;
commentCloud = td . find ( '.comment-code-cloud' ) ;
assingMenuAttributes ( commentCloud . find ( '.menu' ) ) ;
td . find ( "input[name='line']" ) . val ( idx ) ;
td . find ( "input[name='side']" ) . val ( side === 'left' ? 'previous' : 'proposed' ) ;
td . find ( "input[name='path']" ) . val ( path ) ;
}
commentCloud . find ( 'textarea' ) . focus ( ) ;
} ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function assingMenuAttributes ( menu ) {
const id = Math . floor ( Math . random ( ) * Math . floor ( 1000000 ) ) ;
menu . attr ( 'data-write' , menu . attr ( 'data-write' ) + id ) ;
menu . attr ( 'data-preview' , menu . attr ( 'data-preview' ) + id ) ;
menu . find ( '.item' ) . each ( function ( ) {
const tab = $ ( this ) . attr ( 'data-tab' ) + id ;
$ ( this ) . attr ( 'data-tab' , tab ) ;
} ) ;
menu . parent ( ) . find ( "*[data-tab='write']" ) . attr ( 'data-tab' , ` write ${ id } ` ) ;
menu . parent ( ) . find ( "*[data-tab='preview']" ) . attr ( 'data-tab' , ` preview ${ id } ` ) ;
initCommentPreviewTab ( menu . parent ( '.form' ) ) ;
return id ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function initRepositoryCollaboration ( ) {
// Change collaborator access mode
2020-04-19 01:46:29 +03:00
$ ( '.access-mode.menu .item' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
const $menu = $ ( this ) . parent ( ) ;
$ . post ( $menu . data ( 'url' ) , {
_csrf : csrf ,
uid : $menu . data ( 'uid' ) ,
mode : $ ( this ) . data ( 'value' )
} ) ;
} ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function initTeamSettings ( ) {
// Change team access mode
2020-04-19 01:46:29 +03:00
$ ( '.organization.new.team input[name=permission]' ) . on ( 'change' , ( ) => {
2019-11-15 00:39:51 +03:00
const val = $ ( 'input[name=permission]:checked' , '.organization.new.team' ) . val ( ) ;
if ( val === 'admin' ) {
$ ( '.organization.new.team .team-units' ) . hide ( ) ;
} else {
$ ( '.organization.new.team .team-units' ) . show ( ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
} ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function initWikiForm ( ) {
const $editArea = $ ( '.repository.wiki textarea#edit_area' ) ;
2019-11-16 05:20:07 +03:00
let sideBySideChanges = 0 ;
let sideBySideTimeout = null ;
2019-11-15 00:39:51 +03:00
if ( $editArea . length > 0 ) {
const simplemde = new SimpleMDE ( {
autoDownloadFontAwesome : false ,
element : $editArea [ 0 ] ,
forceSync : true ,
previewRender ( plainText , preview ) { // Async method
setTimeout ( ( ) => {
// FIXME: still send render request when return back to edit mode
2019-11-16 05:20:07 +03:00
const render = function ( ) {
sideBySideChanges = 0 ;
2020-03-11 22:34:54 +03:00
if ( sideBySideTimeout !== null ) {
2019-11-16 05:20:07 +03:00
clearTimeout ( sideBySideTimeout ) ;
sideBySideTimeout = null ;
}
$ . post ( $editArea . data ( 'url' ) , {
_csrf : csrf ,
mode : 'gfm' ,
context : $editArea . data ( 'context' ) ,
text : plainText
2020-03-11 22:34:54 +03:00
} , ( data ) => {
2019-11-16 05:20:07 +03:00
preview . innerHTML = ` <div class="markdown ui segment"> ${ data } </div> ` ;
$ ( preview ) . find ( 'pre code' ) . each ( ( _ , e ) => {
2020-04-13 16:02:31 +03:00
highlight ( e ) ;
2019-11-16 05:20:07 +03:00
} ) ;
} ) ;
} ;
if ( ! simplemde . isSideBySideActive ( ) ) {
render ( ) ;
} else {
// delay preview by keystroke counting
sideBySideChanges ++ ;
if ( sideBySideChanges > 10 ) {
render ( ) ;
}
// or delay preview by timeout
2020-03-11 22:34:54 +03:00
if ( sideBySideTimeout !== null ) {
2019-11-16 05:20:07 +03:00
clearTimeout ( sideBySideTimeout ) ;
sideBySideTimeout = null ;
}
sideBySideTimeout = setTimeout ( render , 600 ) ;
}
2019-11-15 00:39:51 +03:00
} , 0 ) ;
2019-11-16 05:20:07 +03:00
if ( ! simplemde . isSideBySideActive ( ) ) {
return 'Loading...' ;
}
return preview . innerHTML ;
2019-11-15 00:39:51 +03:00
} ,
renderingConfig : {
singleLineBreaks : false
} ,
indentWithTabs : false ,
tabSize : 4 ,
spellChecker : false ,
toolbar : [ 'bold' , 'italic' , 'strikethrough' , '|' ,
'heading-1' , 'heading-2' , 'heading-3' , 'heading-bigger' , 'heading-smaller' , '|' ,
{
name : 'code-inline' ,
action ( e ) {
const cm = e . codemirror ;
const selection = cm . getSelection ( ) ;
cm . replaceSelection ( ` \` ${ selection } \` ` ) ;
if ( ! selection ) {
const cursorPos = cm . getCursor ( ) ;
cm . setCursor ( cursorPos . line , cursorPos . ch - 1 ) ;
}
cm . focus ( ) ;
} ,
className : 'fa fa-angle-right' ,
title : 'Add Inline Code' ,
} , 'code' , 'quote' , '|' , {
name : 'checkbox-empty' ,
action ( e ) {
const cm = e . codemirror ;
cm . replaceSelection ( ` \n - [ ] ${ cm . getSelection ( ) } ` ) ;
cm . focus ( ) ;
} ,
className : 'fa fa-square-o' ,
title : 'Add Checkbox (empty)' ,
} ,
{
name : 'checkbox-checked' ,
action ( e ) {
const cm = e . codemirror ;
cm . replaceSelection ( ` \n - [x] ${ cm . getSelection ( ) } ` ) ;
cm . focus ( ) ;
} ,
className : 'fa fa-check-square-o' ,
title : 'Add Checkbox (checked)' ,
} , '|' ,
'unordered-list' , 'ordered-list' , '|' ,
'link' , 'image' , 'table' , 'horizontal-rule' , '|' ,
2020-02-01 20:06:57 +03:00
'clean-block' , 'preview' , 'fullscreen' , 'side-by-side' , '|' ,
{
name : 'revert-to-textarea' ,
action ( e ) {
e . toTextArea ( ) ;
} ,
className : 'fa fa-file' ,
title : 'Revert to simple textarea' ,
} ,
]
2019-11-15 00:39:51 +03:00
} ) ;
$ ( simplemde . codemirror . getInputField ( ) ) . addClass ( 'js-quick-submit' ) ;
2019-11-17 22:42:44 +03:00
setTimeout ( ( ) => {
const $bEdit = $ ( '.repository.wiki.new .previewtabs a[data-tab="write"]' ) ;
const $bPrev = $ ( '.repository.wiki.new .previewtabs a[data-tab="preview"]' ) ;
const $toolbar = $ ( '.editor-toolbar' ) ;
const $bPreview = $ ( '.editor-toolbar a.fa-eye' ) ;
const $bSideBySide = $ ( '.editor-toolbar a.fa-columns' ) ;
$bEdit . on ( 'click' , ( ) => {
if ( $toolbar . hasClass ( 'disabled-for-preview' ) ) {
2020-04-19 01:46:29 +03:00
$bPreview . trigger ( 'click' ) ;
2019-11-17 22:42:44 +03:00
}
} ) ;
$bPrev . on ( 'click' , ( ) => {
if ( ! $toolbar . hasClass ( 'disabled-for-preview' ) ) {
2020-04-19 01:46:29 +03:00
$bPreview . trigger ( 'click' ) ;
2019-11-17 22:42:44 +03:00
}
} ) ;
$bPreview . on ( 'click' , ( ) => {
setTimeout ( ( ) => {
if ( $toolbar . hasClass ( 'disabled-for-preview' ) ) {
if ( $bEdit . hasClass ( 'active' ) ) {
$bEdit . removeClass ( 'active' ) ;
}
if ( ! $bPrev . hasClass ( 'active' ) ) {
$bPrev . addClass ( 'active' ) ;
}
} else {
if ( ! $bEdit . hasClass ( 'active' ) ) {
$bEdit . addClass ( 'active' ) ;
}
if ( $bPrev . hasClass ( 'active' ) ) {
$bPrev . removeClass ( 'active' ) ;
}
}
} , 0 ) ;
} ) ;
$bSideBySide . on ( 'click' , ( ) => {
sideBySideChanges = 10 ;
} ) ;
} , 0 ) ;
2019-11-15 00:39:51 +03:00
}
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
// Adding function to get the cursor position in a text field to jQuery object.
$ . fn . getCursorPosition = function ( ) {
const el = $ ( this ) . get ( 0 ) ;
let pos = 0 ;
if ( 'selectionStart' in el ) {
pos = el . selectionStart ;
} else if ( 'selection' in document ) {
el . focus ( ) ;
const Sel = document . selection . createRange ( ) ;
const SelLength = document . selection . createRange ( ) . text . length ;
Sel . moveStart ( 'character' , - el . value . length ) ;
pos = Sel . text . length - SelLength ;
}
return pos ;
} ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function setSimpleMDE ( $editArea ) {
if ( codeMirrorEditor ) {
codeMirrorEditor . toTextArea ( ) ;
codeMirrorEditor = null ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
if ( simpleMDEditor ) {
return true ;
}
simpleMDEditor = new SimpleMDE ( {
autoDownloadFontAwesome : false ,
element : $editArea [ 0 ] ,
forceSync : true ,
renderingConfig : {
singleLineBreaks : false
} ,
indentWithTabs : false ,
tabSize : 4 ,
spellChecker : false ,
previewRender ( plainText , preview ) { // Async method
setTimeout ( ( ) => {
// FIXME: still send render request when return back to edit mode
$ . post ( $editArea . data ( 'url' ) , {
_csrf : csrf ,
mode : 'gfm' ,
context : $editArea . data ( 'context' ) ,
text : plainText
2020-03-11 22:34:54 +03:00
} , ( data ) => {
2019-11-15 00:39:51 +03:00
preview . innerHTML = ` <div class="markdown ui segment"> ${ data } </div> ` ;
} ) ;
} , 0 ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
return 'Loading...' ;
} ,
toolbar : [ 'bold' , 'italic' , 'strikethrough' , '|' ,
'heading-1' , 'heading-2' , 'heading-3' , 'heading-bigger' , 'heading-smaller' , '|' ,
'code' , 'quote' , '|' ,
'unordered-list' , 'ordered-list' , '|' ,
'link' , 'image' , 'table' , 'horizontal-rule' , '|' ,
2020-02-01 20:06:57 +03:00
'clean-block' , 'preview' , 'fullscreen' , 'side-by-side' , '|' ,
{
name : 'revert-to-textarea' ,
action ( e ) {
e . toTextArea ( ) ;
} ,
className : 'fa fa-file' ,
title : 'Revert to simple textarea' ,
} ,
]
2019-11-15 00:39:51 +03:00
} ) ;
2020-04-07 06:51:21 +03:00
$ ( simpleMDEditor . codemirror . getInputField ( ) ) . addClass ( 'js-quick-submit' ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
return true ;
}
2019-11-13 17:52:13 +03:00
2019-12-16 18:56:35 +03:00
function setCommentSimpleMDE ( $editArea ) {
const simplemde = new SimpleMDE ( {
autoDownloadFontAwesome : false ,
element : $editArea [ 0 ] ,
forceSync : true ,
renderingConfig : {
singleLineBreaks : false
} ,
indentWithTabs : false ,
tabSize : 4 ,
spellChecker : false ,
toolbar : [ 'bold' , 'italic' , 'strikethrough' , '|' ,
'heading-1' , 'heading-2' , 'heading-3' , 'heading-bigger' , 'heading-smaller' , '|' ,
'code' , 'quote' , '|' ,
'unordered-list' , 'ordered-list' , '|' ,
'link' , 'image' , 'table' , 'horizontal-rule' , '|' ,
2020-02-01 20:06:57 +03:00
'clean-block' , '|' ,
{
name : 'revert-to-textarea' ,
action ( e ) {
e . toTextArea ( ) ;
} ,
className : 'fa fa-file' ,
title : 'Revert to simple textarea' ,
} ,
]
2019-12-16 18:56:35 +03:00
} ) ;
2020-04-07 06:51:21 +03:00
$ ( simplemde . codemirror . getInputField ( ) ) . addClass ( 'js-quick-submit' ) ;
2019-12-16 18:56:35 +03:00
simplemde . codemirror . setOption ( 'extraKeys' , {
Enter : ( ) => {
if ( ! ( issuesTribute . isActive || emojiTribute . isActive ) ) {
return CodeMirror . Pass ;
}
} ,
Backspace : ( cm ) => {
2019-12-30 20:11:50 +03:00
if ( cm . getInputField ( ) . trigger ) {
cm . getInputField ( ) . trigger ( 'input' ) ;
}
2019-12-16 18:56:35 +03:00
cm . execCommand ( 'delCharBefore' ) ;
}
} ) ;
issuesTribute . attach ( simplemde . codemirror . getInputField ( ) ) ;
emojiTribute . attach ( simplemde . codemirror . getInputField ( ) ) ;
return simplemde ;
}
2019-11-15 00:39:51 +03:00
function setCodeMirror ( $editArea ) {
if ( simpleMDEditor ) {
simpleMDEditor . toTextArea ( ) ;
simpleMDEditor = null ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
if ( codeMirrorEditor ) {
return true ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
codeMirrorEditor = CodeMirror . fromTextArea ( $editArea [ 0 ] , {
lineNumbers : true
} ) ;
codeMirrorEditor . on ( 'change' , ( cm , _change ) => {
$editArea . val ( cm . getValue ( ) ) ;
} ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
return true ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
function initEditor ( ) {
2020-04-19 01:46:29 +03:00
$ ( '.js-quick-pull-choice-option' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( this ) . val ( ) === 'commit-to-new-branch' ) {
$ ( '.quick-pull-branch-name' ) . show ( ) ;
$ ( '.quick-pull-branch-name input' ) . prop ( 'required' , true ) ;
} else {
$ ( '.quick-pull-branch-name' ) . hide ( ) ;
$ ( '.quick-pull-branch-name input' ) . prop ( 'required' , false ) ;
}
$ ( '#commit-button' ) . text ( $ ( this ) . attr ( 'button_text' ) ) ;
} ) ;
const $editFilename = $ ( '#file-name' ) ;
2020-04-19 01:46:29 +03:00
$editFilename . on ( 'keyup' , function ( e ) {
2019-11-15 00:39:51 +03:00
const $section = $ ( '.breadcrumb span.section' ) ;
const $divider = $ ( '.breadcrumb div.divider' ) ;
let value ;
let parts ;
if ( e . keyCode === 8 ) {
if ( $ ( this ) . getCursorPosition ( ) === 0 ) {
if ( $section . length > 0 ) {
value = $section . last ( ) . find ( 'a' ) . text ( ) ;
$ ( this ) . val ( value + $ ( this ) . val ( ) ) ;
$ ( this ) [ 0 ] . setSelectionRange ( value . length , value . length ) ;
$section . last ( ) . remove ( ) ;
$divider . last ( ) . remove ( ) ;
}
}
}
if ( e . keyCode === 191 ) {
parts = $ ( this ) . val ( ) . split ( '/' ) ;
for ( let i = 0 ; i < parts . length ; ++ i ) {
value = parts [ i ] ;
if ( i < parts . length - 1 ) {
if ( value . length ) {
$ ( ` <span class="section"><a href="#"> ${ value } </a></span> ` ) . insertBefore ( $ ( this ) ) ;
$ ( '<div class="divider"> / </div>' ) . insertBefore ( $ ( this ) ) ;
}
} else {
$ ( this ) . val ( value ) ;
}
$ ( this ) [ 0 ] . setSelectionRange ( 0 , 0 ) ;
}
}
parts = [ ] ;
$ ( '.breadcrumb span.section' ) . each ( function ( ) {
const element = $ ( this ) ;
if ( element . find ( 'a' ) . length ) {
parts . push ( element . find ( 'a' ) . text ( ) ) ;
} else {
parts . push ( element . text ( ) ) ;
}
} ) ;
if ( $ ( this ) . val ( ) ) parts . push ( $ ( this ) . val ( ) ) ;
$ ( '#tree_path' ) . val ( parts . join ( '/' ) ) ;
} ) . trigger ( 'keyup' ) ;
const $editArea = $ ( '.repository.editor textarea#edit_area' ) ;
if ( ! $editArea . length ) return ;
const markdownFileExts = $editArea . data ( 'markdown-file-exts' ) . split ( ',' ) ;
const lineWrapExtensions = $editArea . data ( 'line-wrap-extensions' ) . split ( ',' ) ;
$editFilename . on ( 'keyup' , ( ) => {
const val = $editFilename . val ( ) ;
let mode , spec , extension , extWithDot , dataUrl , apiCall ;
extension = extWithDot = '' ;
const m = /.+\.([^.]+)$/ . exec ( val ) ;
if ( m ) {
extension = m [ 1 ] ;
extWithDot = ` . ${ extension } ` ;
}
const info = CodeMirror . findModeByExtension ( extension ) ;
const previewLink = $ ( 'a[data-tab=preview]' ) ;
if ( info ) {
mode = info . mode ;
spec = info . mime ;
apiCall = mode ;
} else {
apiCall = extension ;
2019-11-13 17:52:13 +03:00
}
2020-03-11 22:34:54 +03:00
if ( previewLink . length && apiCall && previewFileModes && previewFileModes . length && previewFileModes . includes ( apiCall ) ) {
2019-11-15 00:39:51 +03:00
dataUrl = previewLink . data ( 'url' ) ;
previewLink . data ( 'url' , dataUrl . replace ( /(.*)\/.*/i , ` $ 1/ ${ mode } ` ) ) ;
previewLink . show ( ) ;
} else {
previewLink . hide ( ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
// If this file is a Markdown extensions, we will load that editor and return
2020-03-11 22:34:54 +03:00
if ( markdownFileExts . includes ( extWithDot ) ) {
2019-11-15 00:39:51 +03:00
if ( setSimpleMDE ( $editArea ) ) {
return ;
}
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
// Else we are going to use CodeMirror
if ( ! codeMirrorEditor && ! setCodeMirror ( $editArea ) ) {
return ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
if ( mode ) {
codeMirrorEditor . setOption ( 'mode' , spec ) ;
CodeMirror . autoLoadMode ( codeMirrorEditor , mode ) ;
2019-11-13 17:52:13 +03:00
}
2020-03-11 22:34:54 +03:00
if ( lineWrapExtensions . includes ( extWithDot ) ) {
2019-11-15 00:39:51 +03:00
codeMirrorEditor . setOption ( 'lineWrapping' , true ) ;
} else {
codeMirrorEditor . setOption ( 'lineWrapping' , false ) ;
}
// get the filename without any folder
let value = $editFilename . val ( ) ;
if ( value . length === 0 ) {
return ;
}
value = value . split ( '/' ) ;
value = value [ value . length - 1 ] ;
$ . getJSON ( $editFilename . data ( 'ec-url-prefix' ) + value , ( editorconfig ) => {
if ( editorconfig . indent _style === 'tab' ) {
codeMirrorEditor . setOption ( 'indentWithTabs' , true ) ;
codeMirrorEditor . setOption ( 'extraKeys' , { } ) ;
} else {
codeMirrorEditor . setOption ( 'indentWithTabs' , false ) ;
// required because CodeMirror doesn't seems to use spaces correctly for {"indentWithTabs": false}:
// - https://github.com/codemirror/CodeMirror/issues/988
// - https://codemirror.net/doc/manual.html#keymaps
codeMirrorEditor . setOption ( 'extraKeys' , {
Tab ( cm ) {
2020-03-11 22:34:54 +03:00
const spaces = new Array ( parseInt ( cm . getOption ( 'indentUnit' ) ) + 1 ) . join ( ' ' ) ;
2019-11-15 00:39:51 +03:00
cm . replaceSelection ( spaces ) ;
}
2019-11-13 17:52:13 +03:00
} ) ;
2019-11-15 00:39:51 +03:00
}
codeMirrorEditor . setOption ( 'indentUnit' , editorconfig . indent _size || 4 ) ;
codeMirrorEditor . setOption ( 'tabSize' , editorconfig . tab _width || 4 ) ;
} ) ;
} ) . trigger ( 'keyup' ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
// to enable or disable the commit button
const $commitButton = $ ( '#commit-button' ) ;
const $editForm = $ ( '.ui.edit.form' ) ;
const dirtyFileClass = 'dirty-file' ;
// Disabling the button at the start
$commitButton . prop ( 'disabled' , true ) ;
// Registering a custom listener for the file path and the file content
$editForm . areYouSure ( {
silent : true ,
dirtyClass : dirtyFileClass ,
fieldSelector : ':input:not(.commit-form-wrapper :input)' ,
change ( ) {
const dirty = $ ( this ) . hasClass ( dirtyFileClass ) ;
$commitButton . prop ( 'disabled' , ! dirty ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
} ) ;
2019-11-13 17:52:13 +03:00
2020-04-19 01:46:29 +03:00
$commitButton . on ( 'click' , ( event ) => {
2019-11-15 00:39:51 +03:00
// A modal which asks if an empty file should be committed
if ( $editArea . val ( ) . length === 0 ) {
$ ( '#edit-empty-content-modal' ) . modal ( {
onApprove ( ) {
2020-04-19 01:46:29 +03:00
$ ( '.edit.form' ) . trigger ( 'submit' ) ;
2019-11-15 00:39:51 +03:00
}
} ) . modal ( 'show' ) ;
event . preventDefault ( ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
} ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function initOrganization ( ) {
if ( $ ( '.organization' ) . length === 0 ) {
return ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
// Options
if ( $ ( '.organization.settings.options' ) . length > 0 ) {
2020-04-19 01:46:29 +03:00
$ ( '#org_name' ) . on ( 'keyup' , function ( ) {
2019-11-15 00:39:51 +03:00
const $prompt = $ ( '#org-name-change-prompt' ) ;
if ( $ ( this ) . val ( ) . toString ( ) . toLowerCase ( ) !== $ ( this ) . data ( 'org-name' ) . toString ( ) . toLowerCase ( ) ) {
$prompt . show ( ) ;
} else {
$prompt . hide ( ) ;
}
} ) ;
}
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>
2020-04-01 07:14:46 +03:00
// Labels
if ( $ ( '.organization.settings.labels' ) . length > 0 ) {
initLabelEdit ( ) ;
}
2019-11-15 00:39:51 +03:00
}
2019-11-13 17:52:13 +03:00
function initUserSettings ( ) {
2019-11-15 00:39:51 +03:00
// Options
if ( $ ( '.user.settings.profile' ) . length > 0 ) {
2020-04-19 01:46:29 +03:00
$ ( '#username' ) . on ( 'keyup' , function ( ) {
2019-11-15 00:39:51 +03:00
const $prompt = $ ( '#name-change-prompt' ) ;
if ( $ ( this ) . val ( ) . toString ( ) . toLowerCase ( ) !== $ ( this ) . data ( 'name' ) . toString ( ) . toLowerCase ( ) ) {
$prompt . show ( ) ;
} else {
$prompt . hide ( ) ;
}
2019-11-13 17:52:13 +03:00
} ) ;
2019-11-15 00:39:51 +03:00
}
}
2019-11-13 17:52:13 +03:00
2019-11-15 09:56:06 +03:00
function initGithook ( ) {
if ( $ ( '.edit.githook' ) . length === 0 ) {
return ;
}
CodeMirror . autoLoadMode ( CodeMirror . fromTextArea ( $ ( '#content' ) [ 0 ] , {
lineNumbers : true ,
mode : 'shell'
} ) , 'shell' ) ;
}
2019-11-15 00:39:51 +03:00
function initWebhook ( ) {
if ( $ ( '.new.webhook' ) . length === 0 ) {
return ;
}
2020-04-19 01:46:29 +03:00
$ ( '.events.checkbox input' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( this ) . is ( ':checked' ) ) {
$ ( '.events.fields' ) . show ( ) ;
}
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '.non-events.checkbox input' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( this ) . is ( ':checked' ) ) {
$ ( '.events.fields' ) . hide ( ) ;
}
} ) ;
const updateContentType = function ( ) {
const visible = $ ( '#http_method' ) . val ( ) === 'POST' ;
$ ( '#content_type' ) . parent ( ) . parent ( ) [ visible ? 'show' : 'hide' ] ( ) ;
} ;
updateContentType ( ) ;
2020-04-19 01:46:29 +03:00
$ ( '#http_method' ) . on ( 'change' , ( ) => {
2019-11-15 00:39:51 +03:00
updateContentType ( ) ;
} ) ;
// Test delivery
2020-04-19 01:46:29 +03:00
$ ( '#test-delivery' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
const $this = $ ( this ) ;
$this . addClass ( 'loading disabled' ) ;
$ . post ( $this . data ( 'link' ) , {
_csrf : csrf
} ) . done (
setTimeout ( ( ) => {
window . location . href = $this . data ( 'redirect' ) ;
} , 5000 )
) ;
} ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
function initAdmin ( ) {
if ( $ ( '.admin' ) . length === 0 ) {
return ;
}
// New user
if ( $ ( '.admin.new.user' ) . length > 0 || $ ( '.admin.edit.user' ) . length > 0 ) {
2020-04-19 01:46:29 +03:00
$ ( '#login_type' ) . on ( 'change' , function ( ) {
2019-11-15 00:39:51 +03:00
if ( $ ( this ) . val ( ) . substring ( 0 , 1 ) === '0' ) {
$ ( '#login_name' ) . removeAttr ( 'required' ) ;
$ ( '.non-local' ) . hide ( ) ;
$ ( '.local' ) . show ( ) ;
$ ( '#user_name' ) . focus ( ) ;
if ( $ ( this ) . data ( 'password' ) === 'required' ) {
$ ( '#password' ) . attr ( 'required' , 'required' ) ;
}
} else {
$ ( '#login_name' ) . attr ( 'required' , 'required' ) ;
$ ( '.non-local' ) . show ( ) ;
$ ( '.local' ) . hide ( ) ;
$ ( '#login_name' ) . focus ( ) ;
$ ( '#password' ) . removeAttr ( 'required' ) ;
}
} ) ;
}
function onSecurityProtocolChange ( ) {
if ( $ ( '#security_protocol' ) . val ( ) > 0 ) {
$ ( '.has-tls' ) . show ( ) ;
2019-11-13 17:52:13 +03:00
} else {
2019-11-15 00:39:51 +03:00
$ ( '.has-tls' ) . hide ( ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
}
function onUsePagedSearchChange ( ) {
if ( $ ( '#use_paged_search' ) . prop ( 'checked' ) ) {
$ ( '.search-page-size' ) . show ( )
. find ( 'input' ) . attr ( 'required' , 'required' ) ;
} else {
$ ( '.search-page-size' ) . hide ( )
. find ( 'input' ) . removeAttr ( 'required' ) ;
}
}
function onOAuth2Change ( ) {
$ ( '.open_id_connect_auto_discovery_url, .oauth2_use_custom_url' ) . hide ( ) ;
$ ( '.open_id_connect_auto_discovery_url input[required]' ) . removeAttr ( 'required' ) ;
const provider = $ ( '#oauth2_provider' ) . val ( ) ;
switch ( provider ) {
case 'github' :
case 'gitlab' :
case 'gitea' :
2020-03-03 06:11:45 +03:00
case 'nextcloud' :
2019-11-15 00:39:51 +03:00
$ ( '.oauth2_use_custom_url' ) . show ( ) ;
break ;
case 'openidConnect' :
$ ( '.open_id_connect_auto_discovery_url input' ) . attr ( 'required' , 'required' ) ;
$ ( '.open_id_connect_auto_discovery_url' ) . show ( ) ;
break ;
}
onOAuth2UseCustomURLChange ( ) ;
}
function onOAuth2UseCustomURLChange ( ) {
const provider = $ ( '#oauth2_provider' ) . val ( ) ;
$ ( '.oauth2_use_custom_url_field' ) . hide ( ) ;
$ ( '.oauth2_use_custom_url_field input[required]' ) . removeAttr ( 'required' ) ;
if ( $ ( '#oauth2_use_custom_url' ) . is ( ':checked' ) ) {
2020-03-17 11:12:44 +03:00
$ ( '#oauth2_token_url' ) . val ( $ ( ` # ${ provider } _token_url ` ) . val ( ) ) ;
$ ( '#oauth2_auth_url' ) . val ( $ ( ` # ${ provider } _auth_url ` ) . val ( ) ) ;
$ ( '#oauth2_profile_url' ) . val ( $ ( ` # ${ provider } _profile_url ` ) . val ( ) ) ;
$ ( '#oauth2_email_url' ) . val ( $ ( ` # ${ provider } _email_url ` ) . val ( ) ) ;
2019-11-15 00:39:51 +03:00
switch ( provider ) {
case 'github' :
$ ( '.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input, .oauth2_email_url input' ) . attr ( 'required' , 'required' ) ;
$ ( '.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url, .oauth2_email_url' ) . show ( ) ;
break ;
2020-03-03 06:11:45 +03:00
case 'nextcloud' :
2019-11-15 00:39:51 +03:00
case 'gitea' :
case 'gitlab' :
$ ( '.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input' ) . attr ( 'required' , 'required' ) ;
$ ( '.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url' ) . show ( ) ;
$ ( '#oauth2_email_url' ) . val ( '' ) ;
break ;
}
}
}
// New authentication
if ( $ ( '.admin.new.authentication' ) . length > 0 ) {
2020-04-19 01:46:29 +03:00
$ ( '#auth_type' ) . on ( 'change' , function ( ) {
2020-03-17 11:12:44 +03:00
$ ( '.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi' ) . hide ( ) ;
2019-11-15 00:39:51 +03:00
2019-11-23 02:33:31 +03:00
$ ( '.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' ) ;
2019-11-15 00:39:51 +03:00
$ ( '.binddnrequired' ) . removeClass ( 'required' ) ;
const authType = $ ( this ) . val ( ) ;
switch ( authType ) {
case '2' : // LDAP
$ ( '.ldap' ) . show ( ) ;
$ ( '.binddnrequired input, .ldap div.required:not(.dldap) input' ) . attr ( 'required' , 'required' ) ;
$ ( '.binddnrequired' ) . addClass ( 'required' ) ;
break ;
case '3' : // SMTP
$ ( '.smtp' ) . show ( ) ;
$ ( '.has-tls' ) . show ( ) ;
$ ( '.smtp div.required input, .has-tls' ) . attr ( 'required' , 'required' ) ;
break ;
case '4' : // PAM
$ ( '.pam' ) . show ( ) ;
$ ( '.pam input' ) . attr ( 'required' , 'required' ) ;
break ;
case '5' : // LDAP
$ ( '.dldap' ) . show ( ) ;
$ ( '.dldap div.required:not(.ldap) input' ) . attr ( 'required' , 'required' ) ;
break ;
case '6' : // OAuth2
$ ( '.oauth2' ) . show ( ) ;
$ ( '.oauth2 div.required:not(.oauth2_use_custom_url,.oauth2_use_custom_url_field,.open_id_connect_auto_discovery_url) input' ) . attr ( 'required' , 'required' ) ;
onOAuth2Change ( ) ;
break ;
2019-11-23 02:33:31 +03:00
case '7' : // SSPI
$ ( '.sspi' ) . show ( ) ;
$ ( '.sspi div.required input' ) . attr ( 'required' , 'required' ) ;
break ;
2019-11-15 00:39:51 +03:00
}
if ( authType === '2' || authType === '5' ) {
onSecurityProtocolChange ( ) ;
}
if ( authType === '2' ) {
onUsePagedSearchChange ( ) ;
}
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '#auth_type' ) . trigger ( 'change' ) ;
$ ( '#security_protocol' ) . on ( 'change' , onSecurityProtocolChange ) ;
$ ( '#use_paged_search' ) . on ( 'change' , onUsePagedSearchChange ) ;
$ ( '#oauth2_provider' ) . on ( 'change' , onOAuth2Change ) ;
$ ( '#oauth2_use_custom_url' ) . on ( 'change' , onOAuth2UseCustomURLChange ) ;
2019-11-15 00:39:51 +03:00
}
// Edit authentication
if ( $ ( '.admin.edit.authentication' ) . length > 0 ) {
const authType = $ ( '#auth_type' ) . val ( ) ;
if ( authType === '2' || authType === '5' ) {
2020-04-19 01:46:29 +03:00
$ ( '#security_protocol' ) . on ( 'change' , onSecurityProtocolChange ) ;
2019-11-15 00:39:51 +03:00
if ( authType === '2' ) {
2020-04-19 01:46:29 +03:00
$ ( '#use_paged_search' ) . on ( 'change' , onUsePagedSearchChange ) ;
2019-11-15 00:39:51 +03:00
}
} else if ( authType === '6' ) {
2020-04-19 01:46:29 +03:00
$ ( '#oauth2_provider' ) . on ( 'change' , onOAuth2Change ) ;
$ ( '#oauth2_use_custom_url' ) . on ( 'change' , onOAuth2UseCustomURLChange ) ;
2019-11-15 00:39:51 +03:00
onOAuth2Change ( ) ;
}
}
// Notice
if ( $ ( '.admin.notice' ) ) {
const $detailModal = $ ( '#detail-modal' ) ;
// Attach view detail modals
2020-04-19 01:46:29 +03:00
$ ( '.view-detail' ) . on ( 'click' , function ( ) {
2020-05-05 18:16:55 +03:00
$detailModal . find ( '.content pre' ) . text ( $ ( this ) . data ( 'content' ) ) ;
2019-11-15 00:39:51 +03:00
$detailModal . modal ( 'show' ) ;
return false ;
} ) ;
// Select actions
const $checkboxes = $ ( '.select.table .ui.checkbox' ) ;
2020-04-19 01:46:29 +03:00
$ ( '.select.action' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
switch ( $ ( this ) . data ( 'action' ) ) {
case 'select-all' :
$checkboxes . checkbox ( 'check' ) ;
break ;
case 'deselect-all' :
$checkboxes . checkbox ( 'uncheck' ) ;
break ;
case 'inverse' :
$checkboxes . checkbox ( 'toggle' ) ;
break ;
}
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '#delete-selection' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
const $this = $ ( this ) ;
$this . addClass ( 'loading disabled' ) ;
const ids = [ ] ;
$checkboxes . each ( function ( ) {
if ( $ ( this ) . checkbox ( 'is checked' ) ) {
ids . push ( $ ( this ) . data ( 'id' ) ) ;
}
} ) ;
$ . post ( $this . data ( 'link' ) , {
_csrf : csrf ,
ids
} ) . done ( ( ) => {
window . location . href = $this . data ( 'redirect' ) ;
} ) ;
} ) ;
}
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
function buttonsClickOnEnter ( ) {
2020-04-19 01:46:29 +03:00
$ ( '.ui.button' ) . on ( 'keypress' , function ( e ) {
2019-11-15 00:39:51 +03:00
if ( e . keyCode === 13 || e . keyCode === 32 ) { // enter key or space bar
2020-04-19 01:46:29 +03:00
$ ( this ) . trigger ( 'click' ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
} ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
function searchUsers ( ) {
const $searchUserBox = $ ( '#search-user-box' ) ;
$searchUserBox . search ( {
minCharacters : 2 ,
apiSettings : {
2020-02-12 04:53:18 +03:00
url : ` ${ AppSubUrl } /api/v1/users/search?q={query} ` ,
2019-11-15 00:39:51 +03:00
onResponse ( response ) {
const items = [ ] ;
$ . each ( response . data , ( _i , item ) => {
let title = item . login ;
if ( item . full _name && item . full _name . length > 0 ) {
title += ` ( ${ htmlEncode ( item . full _name ) } ) ` ;
}
items . push ( {
title ,
image : item . avatar _url
} ) ;
} ) ;
2019-11-13 17:52:13 +03:00
2020-03-11 22:34:54 +03:00
return { results : items } ;
2019-11-15 00:39:51 +03:00
}
} ,
searchFields : [ 'login' , 'full_name' ] ,
showNoResults : false
} ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function searchTeams ( ) {
const $searchTeamBox = $ ( '#search-team-box' ) ;
$searchTeamBox . search ( {
minCharacters : 2 ,
apiSettings : {
2020-02-12 04:53:18 +03:00
url : ` ${ AppSubUrl } /api/v1/orgs/ ${ $searchTeamBox . data ( 'org' ) } /teams/search?q={query} ` ,
2020-03-11 22:34:54 +03:00
headers : { 'X-Csrf-Token' : csrf } ,
2019-11-15 00:39:51 +03:00
onResponse ( response ) {
const items = [ ] ;
$ . each ( response . data , ( _i , item ) => {
const title = ` ${ item . name } ( ${ item . permission } access) ` ;
items . push ( {
title ,
} ) ;
} ) ;
2019-11-13 17:52:13 +03:00
2020-03-11 22:34:54 +03:00
return { results : items } ;
2019-11-15 00:39:51 +03:00
}
} ,
searchFields : [ 'name' , 'description' ] ,
showNoResults : false
} ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function searchRepositories ( ) {
const $searchRepoBox = $ ( '#search-repo-box' ) ;
$searchRepoBox . search ( {
minCharacters : 2 ,
apiSettings : {
2020-02-12 04:53:18 +03:00
url : ` ${ AppSubUrl } /api/v1/repos/search?q={query}&uid= ${ $searchRepoBox . data ( 'uid' ) } ` ,
2019-11-15 00:39:51 +03:00
onResponse ( response ) {
const items = [ ] ;
$ . each ( response . data , ( _i , item ) => {
items . push ( {
title : item . full _name . split ( '/' ) [ 1 ] ,
description : item . full _name
} ) ;
} ) ;
2019-11-13 17:52:13 +03:00
2020-03-11 22:34:54 +03:00
return { results : items } ;
2019-11-15 00:39:51 +03:00
}
} ,
searchFields : [ 'full_name' ] ,
showNoResults : false
} ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
function initCodeView ( ) {
if ( $ ( '.code-view .linenums' ) . length > 0 ) {
$ ( document ) . on ( 'click' , '.lines-num span' , function ( e ) {
const $select = $ ( this ) ;
const $list = $select . parent ( ) . siblings ( '.lines-code' ) . find ( 'ol.linenums > li' ) ;
selectRange ( $list , $list . filter ( ` [rel= ${ $select . attr ( 'id' ) } ] ` ) , ( e . shiftKey ? $list . filter ( '.active' ) . eq ( 0 ) : null ) ) ;
deSelect ( ) ;
} ) ;
$ ( window ) . on ( 'hashchange' , ( ) => {
let m = window . location . hash . match ( /^#(L\d+)-(L\d+)$/ ) ;
const $list = $ ( '.code-view ol.linenums > li' ) ;
let $first ;
if ( m ) {
$first = $list . filter ( ` . ${ m [ 1 ] } ` ) ;
selectRange ( $list , $first , $list . filter ( ` . ${ m [ 2 ] } ` ) ) ;
$ ( 'html, body' ) . scrollTop ( $first . offset ( ) . top - 200 ) ;
return ;
}
m = window . location . hash . match ( /^#(L|n)(\d+)$/ ) ;
if ( m ) {
$first = $list . filter ( ` .L ${ m [ 2 ] } ` ) ;
selectRange ( $list , $first ) ;
$ ( 'html, body' ) . scrollTop ( $first . offset ( ) . top - 200 ) ;
}
} ) . trigger ( 'hashchange' ) ;
}
2020-03-11 22:34:54 +03:00
$ ( '.fold-code' ) . on ( 'click' , ( { target } ) => {
2020-01-26 11:17:25 +03:00
const box = target . closest ( '.file-content' ) ;
const folded = box . dataset . folded !== 'true' ;
target . classList . add ( ` fa-chevron- ${ folded ? 'right' : 'down' } ` ) ;
target . classList . remove ( ` fa-chevron- ${ folded ? 'down' : 'right' } ` ) ;
box . dataset . folded = String ( folded ) ;
2019-11-15 05:52:59 +03:00
} ) ;
function insertBlobExcerpt ( e ) {
const $blob = $ ( e . target ) ;
const $row = $blob . parent ( ) . parent ( ) ;
$ . get ( ` ${ $blob . data ( 'url' ) } ? ${ $blob . data ( 'query' ) } &anchor= ${ $blob . data ( 'anchor' ) } ` , ( blob ) => {
$row . replaceWith ( blob ) ;
2020-03-11 22:34:54 +03:00
$ ( ` [data-anchor=" ${ $blob . data ( 'anchor' ) } "] ` ) . on ( 'click' , ( e ) => { insertBlobExcerpt ( e ) } ) ;
2019-12-07 21:01:57 +03:00
$ ( '.diff-detail-box.ui.sticky' ) . sticky ( ) ;
2019-11-15 05:52:59 +03:00
} ) ;
}
2020-03-11 22:34:54 +03:00
$ ( '.ui.blob-excerpt' ) . on ( 'click' , ( e ) => { insertBlobExcerpt ( e ) } ) ;
2019-11-15 00:39:51 +03:00
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function initU2FAuth ( ) {
if ( $ ( '#wait-for-key' ) . length === 0 ) {
return ;
}
u2fApi . ensureSupport ( )
. then ( ( ) => {
2020-05-12 06:34:48 +03:00
$ . getJSON ( ` ${ AppSubUrl } /user/u2f/challenge ` ) . done ( ( req ) => {
2019-11-15 00:39:51 +03:00
u2fApi . sign ( req . appId , req . challenge , req . registeredKeys , 30 )
. then ( u2fSigned )
. catch ( ( err ) => {
if ( err === undefined ) {
u2fError ( 1 ) ;
return ;
}
u2fError ( err . metaData . code ) ;
} ) ;
} ) ;
} ) . catch ( ( ) => {
// Fallback in case browser do not support U2F
2020-02-12 04:53:18 +03:00
window . location . href = ` ${ AppSubUrl } /user/two_factor ` ;
2019-11-15 00:39:51 +03:00
} ) ;
}
function u2fSigned ( resp ) {
$ . ajax ( {
2020-02-12 04:53:18 +03:00
url : ` ${ AppSubUrl } /user/u2f/sign ` ,
2019-11-15 00:39:51 +03:00
type : 'POST' ,
2020-03-11 22:34:54 +03:00
headers : { 'X-Csrf-Token' : csrf } ,
2019-11-15 00:39:51 +03:00
data : JSON . stringify ( resp ) ,
contentType : 'application/json; charset=utf-8' ,
} ) . done ( ( res ) => {
window . location . replace ( res ) ;
} ) . fail ( ( ) => {
u2fError ( 1 ) ;
} ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function u2fRegistered ( resp ) {
if ( checkError ( resp ) ) {
return ;
}
$ . ajax ( {
2020-02-12 04:53:18 +03:00
url : ` ${ AppSubUrl } /user/settings/security/u2f/register ` ,
2019-11-15 00:39:51 +03:00
type : 'POST' ,
2020-03-11 22:34:54 +03:00
headers : { 'X-Csrf-Token' : csrf } ,
2019-11-15 00:39:51 +03:00
data : JSON . stringify ( resp ) ,
contentType : 'application/json; charset=utf-8' ,
success ( ) {
reload ( ) ;
} ,
fail ( ) {
u2fError ( 1 ) ;
}
} ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function checkError ( resp ) {
if ( ! ( 'errorCode' in resp ) ) {
return false ;
}
if ( resp . errorCode === 0 ) {
2019-11-13 17:52:13 +03:00
return false ;
2019-11-15 00:39:51 +03:00
}
u2fError ( resp . errorCode ) ;
return true ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
function u2fError ( errorType ) {
const u2fErrors = {
browser : $ ( '#unsupported-browser' ) ,
1 : $ ( '#u2f-error-1' ) ,
2 : $ ( '#u2f-error-2' ) ,
3 : $ ( '#u2f-error-3' ) ,
4 : $ ( '#u2f-error-4' ) ,
5 : $ ( '.u2f-error-5' )
} ;
u2fErrors [ errorType ] . removeClass ( 'hide' ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
Object . keys ( u2fErrors ) . forEach ( ( type ) => {
if ( type !== errorType ) {
u2fErrors [ type ] . addClass ( 'hide' ) ;
}
} ) ;
$ ( '#u2f-error' ) . modal ( 'show' ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function initU2FRegister ( ) {
2020-03-11 22:34:54 +03:00
$ ( '#register-device' ) . modal ( { allowMultiple : false } ) ;
$ ( '#u2f-error' ) . modal ( { allowMultiple : false } ) ;
2019-11-15 00:39:51 +03:00
$ ( '#register-security-key' ) . on ( 'click' , ( e ) => {
e . preventDefault ( ) ;
u2fApi . ensureSupport ( )
. then ( u2fRegisterRequest )
. catch ( ( ) => {
u2fError ( 'browser' ) ;
} ) ;
} ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
function u2fRegisterRequest ( ) {
2020-02-12 04:53:18 +03:00
$ . post ( ` ${ AppSubUrl } /user/settings/security/u2f/request_register ` , {
2019-11-15 00:39:51 +03:00
_csrf : csrf ,
name : $ ( '#nickname' ) . val ( )
2020-05-12 06:34:48 +03:00
} ) . done ( ( req ) => {
2019-11-15 00:39:51 +03:00
$ ( '#nickname' ) . closest ( 'div.field' ) . removeClass ( 'error' ) ;
$ ( '#register-device' ) . modal ( 'show' ) ;
if ( req . registeredKeys === null ) {
req . registeredKeys = [ ] ;
}
u2fApi . register ( req . appId , req . registerRequests , req . registeredKeys , 30 )
. then ( u2fRegistered )
. catch ( ( reason ) => {
if ( reason === undefined ) {
u2fError ( 1 ) ;
return ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
u2fError ( reason . metaData . code ) ;
} ) ;
} ) . fail ( ( xhr ) => {
if ( xhr . status === 409 ) {
$ ( '#nickname' ) . closest ( 'div.field' ) . addClass ( 'error' ) ;
}
} ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
function initWipTitle ( ) {
2020-04-19 01:46:29 +03:00
$ ( '.title_wip_desc > a' ) . on ( 'click' , ( e ) => {
2019-11-15 00:39:51 +03:00
e . preventDefault ( ) ;
const $issueTitle = $ ( '#issue_title' ) ;
$issueTitle . focus ( ) ;
const value = $issueTitle . val ( ) . trim ( ) . toUpperCase ( ) ;
for ( const i in wipPrefixes ) {
if ( value . startsWith ( wipPrefixes [ i ] . toUpperCase ( ) ) ) {
2019-11-13 17:52:13 +03:00
return ;
2019-11-15 00:39:51 +03:00
}
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
$issueTitle . val ( ` ${ wipPrefixes [ 0 ] } ${ $issueTitle . val ( ) } ` ) ;
} ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function initTemplateSearch ( ) {
const $repoTemplate = $ ( '#repo_template' ) ;
const checkTemplate = function ( ) {
const $templateUnits = $ ( '#template_units' ) ;
const $nonTemplate = $ ( '#non_template' ) ;
2020-01-05 05:39:14 +03:00
if ( $repoTemplate . val ( ) !== '' && $repoTemplate . val ( ) !== '0' ) {
2019-11-15 00:39:51 +03:00
$templateUnits . show ( ) ;
$nonTemplate . hide ( ) ;
} else {
$templateUnits . hide ( ) ;
$nonTemplate . show ( ) ;
}
} ;
2020-04-19 01:46:29 +03:00
$repoTemplate . on ( 'change' , checkTemplate ) ;
2019-11-15 00:39:51 +03:00
checkTemplate ( ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
const changeOwner = function ( ) {
$ ( '#repo_template_search' )
. dropdown ( {
apiSettings : {
2020-02-12 04:53:18 +03:00
url : ` ${ AppSubUrl } /api/v1/repos/search?q={query}&template=true&priority_owner_id= ${ $ ( '#uid' ) . val ( ) } ` ,
2019-11-15 00:39:51 +03:00
onResponse ( response ) {
2020-03-11 22:34:54 +03:00
const filteredResponse = { success : true , results : [ ] } ;
2019-11-15 00:39:51 +03:00
filteredResponse . results . push ( {
name : '' ,
value : ''
} ) ;
// Parse the response from the api to work with our dropdown
$ . each ( response . data , ( _r , repo ) => {
filteredResponse . results . push ( {
name : htmlEncode ( repo . full _name ) ,
value : repo . id
} ) ;
} ) ;
return filteredResponse ;
} ,
cache : false ,
2019-11-13 17:52:13 +03:00
} ,
2019-11-15 00:39:51 +03:00
fullTextSearch : true
} ) ;
} ;
2020-04-19 01:46:29 +03:00
$ ( '#uid' ) . on ( 'change' , changeOwner ) ;
2019-11-15 00:39:51 +03:00
changeOwner ( ) ;
}
2020-01-29 00:57:20 +03:00
$ ( document ) . ready ( async ( ) => {
2019-11-15 00:39:51 +03:00
// Show exact time
$ ( '.time-since' ) . each ( function ( ) {
$ ( this )
. addClass ( 'poping up' )
. attr ( 'data-content' , $ ( this ) . attr ( 'title' ) )
. attr ( 'data-variation' , 'inverted tiny' )
. attr ( 'title' , '' ) ;
} ) ;
// Semantic UI modules.
$ ( '.dropdown:not(.custom)' ) . dropdown ( ) ;
$ ( '.jump.dropdown' ) . dropdown ( {
action : 'hide' ,
onShow ( ) {
$ ( '.poping.up' ) . popup ( 'hide' ) ;
}
} ) ;
$ ( '.slide.up.dropdown' ) . dropdown ( {
transition : 'slide up'
} ) ;
$ ( '.upward.dropdown' ) . dropdown ( {
direction : 'upward'
} ) ;
$ ( '.ui.accordion' ) . accordion ( ) ;
$ ( '.ui.checkbox' ) . checkbox ( ) ;
$ ( '.ui.progress' ) . progress ( {
showActivity : false
} ) ;
$ ( '.poping.up' ) . popup ( ) ;
$ ( '.top.menu .poping.up' ) . popup ( {
onShow ( ) {
if ( $ ( '.top.menu .menu.transition' ) . hasClass ( 'visible' ) ) {
return false ;
}
}
} ) ;
$ ( '.tabular.menu .item' ) . tab ( ) ;
$ ( '.tabable.menu .item' ) . tab ( ) ;
2020-04-19 01:46:29 +03:00
$ ( '.toggle.button' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
$ ( $ ( this ) . data ( 'target' ) ) . slideToggle ( 100 ) ;
} ) ;
// make table <tr> element clickable like a link
2020-04-19 01:46:29 +03:00
$ ( 'tr[data-href]' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
window . location = $ ( this ) . data ( 'href' ) ;
} ) ;
2020-04-24 06:57:38 +03:00
// make table <td> element clickable like a link
$ ( 'td[data-href]' ) . click ( function ( ) {
window . location = $ ( this ) . data ( 'href' ) ;
} ) ;
2019-11-15 00:39:51 +03:00
// Dropzone
const $dropzone = $ ( '#dropzone' ) ;
if ( $dropzone . length > 0 ) {
const filenameDict = { } ;
2020-03-08 00:06:15 +03:00
await createDropzone ( '#dropzone' , {
2019-11-15 00:39:51 +03:00
url : $dropzone . data ( 'upload-url' ) ,
2020-03-11 22:34:54 +03:00
headers : { 'X-Csrf-Token' : csrf } ,
2019-11-15 00:39:51 +03:00
maxFiles : $dropzone . data ( 'max-file' ) ,
maxFilesize : $dropzone . data ( 'max-size' ) ,
acceptedFiles : ( $dropzone . data ( 'accepts' ) === '*/*' ) ? null : $dropzone . data ( 'accepts' ) ,
addRemoveLinks : true ,
dictDefaultMessage : $dropzone . data ( 'default-message' ) ,
dictInvalidFileType : $dropzone . data ( 'invalid-input-type' ) ,
dictFileTooBig : $dropzone . data ( 'file-too-big' ) ,
dictRemoveFile : $dropzone . data ( 'remove-file' ) ,
init ( ) {
this . on ( 'success' , ( file , data ) => {
filenameDict [ file . name ] = data . uuid ;
const input = $ ( ` <input id=" ${ data . uuid } " name="files" type="hidden"> ` ) . val ( data . uuid ) ;
$ ( '.files' ) . append ( input ) ;
} ) ;
this . on ( 'removedfile' , ( file ) => {
if ( file . name in filenameDict ) {
$ ( ` # ${ filenameDict [ file . name ] } ` ) . remove ( ) ;
}
if ( $dropzone . data ( 'remove-url' ) && $dropzone . data ( 'csrf' ) ) {
$ . post ( $dropzone . data ( 'remove-url' ) , {
file : filenameDict [ file . name ] ,
_csrf : $dropzone . data ( 'csrf' )
} ) ;
}
} ) ;
} ,
} ) ;
}
// Helpers.
2020-04-19 01:46:29 +03:00
$ ( '.delete-button' ) . on ( 'click' , showDeletePopup ) ;
$ ( '.add-all-button' ) . on ( 'click' , showAddAllPopup ) ;
$ ( '.link-action' ) . on ( 'click' , linkAction ) ;
$ ( '.link-email-action' ) . on ( 'click' , linkEmailAction ) ;
2019-11-15 00:39:51 +03:00
2020-04-19 01:46:29 +03:00
$ ( '.delete-branch-button' ) . on ( 'click' , showDeletePopup ) ;
2019-11-15 00:39:51 +03:00
2020-04-19 01:46:29 +03:00
$ ( '.undo-button' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
const $this = $ ( this ) ;
$ . post ( $this . data ( 'url' ) , {
_csrf : csrf ,
id : $this . data ( 'id' )
} ) . done ( ( data ) => {
window . location . href = data . redirect ;
} ) ;
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '.show-panel.button' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
$ ( $ ( this ) . data ( 'panel' ) ) . show ( ) ;
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '.show-modal.button' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
$ ( $ ( this ) . data ( 'modal' ) ) . modal ( 'show' ) ;
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '.delete-post.button' ) . on ( 'click' , function ( ) {
2019-11-15 00:39:51 +03:00
const $this = $ ( this ) ;
$ . post ( $this . data ( 'request-url' ) , {
_csrf : csrf
} ) . done ( ( ) => {
window . location . href = $this . data ( 'done-url' ) ;
} ) ;
} ) ;
// Set anchor.
$ ( '.markdown' ) . each ( function ( ) {
$ ( this ) . find ( 'h1, h2, h3, h4, h5, h6' ) . each ( function ( ) {
let node = $ ( this ) ;
2019-12-24 01:38:50 +03:00
node = node . wrap ( '<div class="anchor-wrap"></div>' ) ;
2020-02-11 20:02:41 +03:00
node . append ( ` <a class="anchor" href="# ${ encodeURIComponent ( node . attr ( 'id' ) ) } "> ${ svg ( 'octicon-link' , 16 ) } </a> ` ) ;
2019-11-15 00:39:51 +03:00
} ) ;
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '.issue-checkbox' ) . on ( 'click' , ( ) => {
2019-11-15 00:39:51 +03:00
const numChecked = $ ( '.issue-checkbox' ) . children ( 'input:checked' ) . length ;
if ( numChecked > 0 ) {
$ ( '#issue-filters' ) . addClass ( 'hide' ) ;
$ ( '#issue-actions' ) . removeClass ( 'hide' ) ;
} else {
$ ( '#issue-filters' ) . removeClass ( 'hide' ) ;
$ ( '#issue-actions' ) . addClass ( 'hide' ) ;
}
} ) ;
2020-04-19 01:46:29 +03:00
$ ( '.issue-action' ) . on ( 'click' , function ( ) {
2020-03-11 22:34:54 +03:00
let { action } = this . dataset ;
let { elementId } = this . dataset ;
2019-11-15 00:39:51 +03:00
const issueIDs = $ ( '.issue-checkbox' ) . children ( 'input:checked' ) . map ( function ( ) {
return this . dataset . issueId ;
} ) . get ( ) . join ( ) ;
2020-03-11 22:34:54 +03:00
const { url } = this . dataset ;
2019-11-15 00:39:51 +03:00
if ( elementId === '0' && url . substr ( - 9 ) === '/assignee' ) {
elementId = '' ;
action = 'clear' ;
}
2020-04-06 19:33:34 +03:00
updateIssuesMeta ( url , action , issueIDs , elementId , '' ) . then ( ( ) => {
2019-11-15 00:39:51 +03:00
// NOTICE: This reset of checkbox state targets Firefox caching behaviour, as the checkboxes stay checked after reload
if ( action === 'close' || action === 'open' ) {
// uncheck all checkboxes
2020-03-11 22:34:54 +03:00
$ ( '.issue-checkbox input[type="checkbox"]' ) . each ( ( _ , e ) => { e . checked = false } ) ;
2019-11-15 00:39:51 +03:00
}
reload ( ) ;
} ) ;
} ) ;
// NOTICE: This event trigger targets Firefox caching behaviour, as the checkboxes stay checked after reload
// trigger ckecked event, if checkboxes are checked on load
$ ( '.issue-checkbox input[type="checkbox"]:checked' ) . first ( ) . each ( ( _ , e ) => {
e . checked = false ;
2020-04-19 01:46:29 +03:00
$ ( e ) . trigger ( 'click' ) ;
2019-11-15 00:39:51 +03:00
} ) ;
2020-04-18 16:50:25 +03:00
$ ( '.resolve-conversation' ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
const id = $ ( this ) . data ( 'comment-id' ) ;
const action = $ ( this ) . data ( 'action' ) ;
const url = $ ( this ) . data ( 'update-url' ) ;
$ . post ( url , {
_csrf : csrf ,
action ,
comment _id : id ,
} ) . then ( reload ) ;
} ) ;
2019-11-15 00:39:51 +03:00
buttonsClickOnEnter ( ) ;
searchUsers ( ) ;
searchTeams ( ) ;
searchRepositories ( ) ;
initCommentForm ( ) ;
initInstall ( ) ;
initRepository ( ) ;
initMigration ( ) ;
initWikiForm ( ) ;
initEditForm ( ) ;
initEditor ( ) ;
initOrganization ( ) ;
2019-11-15 09:56:06 +03:00
initGithook ( ) ;
2019-11-15 00:39:51 +03:00
initWebhook ( ) ;
initAdmin ( ) ;
initCodeView ( ) ;
initVueApp ( ) ;
initTeamSettings ( ) ;
initCtrlEnterSubmit ( ) ;
initNavbarContentToggle ( ) ;
initTopicbar ( ) ;
initU2FAuth ( ) ;
initU2FRegister ( ) ;
initIssueList ( ) ;
initWipTitle ( ) ;
initPullRequestReview ( ) ;
initRepoStatusChecker ( ) ;
initTemplateSearch ( ) ;
2020-02-12 04:53:18 +03:00
initContextPopups ( ) ;
2020-04-24 06:57:38 +03:00
initNotificationsTable ( ) ;
initNotificationCount ( ) ;
2020-04-28 21:05:39 +03:00
initTribute ( ) ;
2019-11-15 00:39:51 +03:00
// Repo clone url.
if ( $ ( '#repo-clone-url' ) . length > 0 ) {
switch ( localStorage . getItem ( 'repo-clone-protocol' ) ) {
case 'ssh' :
2020-04-19 01:46:29 +03:00
if ( $ ( '#repo-clone-ssh' ) . length === 0 ) {
$ ( '#repo-clone-https' ) . trigger ( 'click' ) ;
2019-11-15 00:39:51 +03:00
}
break ;
default :
2020-04-19 01:46:29 +03:00
$ ( '#repo-clone-https' ) . trigger ( 'click' ) ;
2019-11-15 00:39:51 +03:00
break ;
}
}
const routes = {
'div.user.settings' : initUserSettings ,
'div.repository.settings.collaboration' : initRepositoryCollaboration
} ;
let selector ;
for ( selector in routes ) {
if ( $ ( selector ) . length > 0 ) {
routes [ selector ] ( ) ;
break ;
}
}
const $cloneAddr = $ ( '#clone_addr' ) ;
2020-04-19 01:46:29 +03:00
$cloneAddr . on ( 'change' , ( ) => {
2019-11-15 00:39:51 +03:00
const $repoName = $ ( '#repo_name' ) ;
if ( $cloneAddr . val ( ) . length > 0 && $repoName . val ( ) . length === 0 ) { // Only modify if repo_name input is blank
$repoName . val ( $cloneAddr . val ( ) . match ( /^(.*\/)?((.+?)(\.git)?)$/ ) [ 3 ] ) ;
}
} ) ;
2020-01-29 00:57:20 +03:00
2020-02-08 02:03:42 +03:00
// parallel init of lazy-loaded features
2020-04-13 16:02:31 +03:00
await Promise . all ( [
highlight ( document . querySelectorAll ( 'pre code' ) ) ,
2020-02-08 02:03:42 +03:00
initGitGraph ( ) ,
initClipboard ( ) ,
2020-02-24 00:34:28 +03:00
initUserHeatmap ( ) ,
2020-01-29 00:57:20 +03:00
] ) ;
2019-11-15 00:39:51 +03:00
} ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function changeHash ( hash ) {
if ( window . history . pushState ) {
window . history . pushState ( null , null , hash ) ;
} else {
window . location . hash = hash ;
}
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
function deSelect ( ) {
if ( window . getSelection ) {
window . getSelection ( ) . removeAllRanges ( ) ;
} else {
document . selection . empty ( ) ;
}
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
function selectRange ( $list , $select , $from ) {
$list . removeClass ( 'active' ) ;
if ( $from ) {
let a = parseInt ( $select . attr ( 'rel' ) . substr ( 1 ) ) ;
let b = parseInt ( $from . attr ( 'rel' ) . substr ( 1 ) ) ;
let c ;
if ( a !== b ) {
if ( a > b ) {
c = a ;
a = b ;
b = c ;
}
const classes = [ ] ;
for ( let i = a ; i <= b ; i ++ ) {
classes . push ( ` .L ${ i } ` ) ;
}
$list . filter ( classes . join ( ',' ) ) . addClass ( 'active' ) ;
changeHash ( ` #L ${ a } -L ${ b } ` ) ;
return ;
}
}
$select . addClass ( 'active' ) ;
changeHash ( ` # ${ $select . attr ( 'rel' ) } ` ) ;
}
$ ( ( ) => {
// Warn users that try to leave a page after entering data into a form.
// Except on sign-in pages, and for forms marked as 'ignore-dirty'.
if ( $ ( '.user.signin' ) . length === 0 ) {
$ ( 'form:not(.ignore-dirty)' ) . areYouSure ( ) ;
}
// Parse SSH Key
$ ( '#ssh-key-content' ) . on ( 'change paste keyup' , function ( ) {
const arrays = $ ( this ) . val ( ) . split ( ' ' ) ;
const $title = $ ( '#ssh-key-title' ) ;
if ( $title . val ( ) === '' && arrays . length === 3 && arrays [ 2 ] !== '' ) {
$title . val ( arrays [ 2 ] ) ;
}
} ) ;
} ) ;
function showDeletePopup ( ) {
const $this = $ ( this ) ;
let filter = '' ;
if ( $this . attr ( 'id' ) ) {
filter += ` # ${ $this . attr ( 'id' ) } ` ;
}
const dialog = $ ( ` .delete.modal ${ filter } ` ) ;
dialog . find ( '.name' ) . text ( $this . data ( 'name' ) ) ;
dialog . modal ( {
closable : false ,
onApprove ( ) {
if ( $this . data ( 'type' ) === 'form' ) {
2020-04-19 01:46:29 +03:00
$ ( $this . data ( 'form' ) ) . trigger ( 'submit' ) ;
2019-11-13 17:52:13 +03:00
return ;
2019-11-15 00:39:51 +03:00
}
$ . post ( $this . data ( 'url' ) , {
_csrf : csrf ,
id : $this . data ( 'id' )
} ) . done ( ( data ) => {
window . location . href = data . redirect ;
} ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
} ) . modal ( 'show' ) ;
return false ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function showAddAllPopup ( ) {
const $this = $ ( this ) ;
let filter = '' ;
if ( $this . attr ( 'id' ) ) {
filter += ` # ${ $this . attr ( 'id' ) } ` ;
}
const dialog = $ ( ` .addall.modal ${ filter } ` ) ;
dialog . find ( '.name' ) . text ( $this . data ( 'name' ) ) ;
dialog . modal ( {
closable : false ,
onApprove ( ) {
if ( $this . data ( 'type' ) === 'form' ) {
2020-04-19 01:46:29 +03:00
$ ( $this . data ( 'form' ) ) . trigger ( 'submit' ) ;
2019-11-15 00:39:51 +03:00
return ;
}
$ . post ( $this . data ( 'url' ) , {
_csrf : csrf ,
id : $this . data ( 'id' )
} ) . done ( ( data ) => {
window . location . href = data . redirect ;
} ) ;
}
} ) . modal ( 'show' ) ;
return false ;
}
2020-03-17 00:13:41 +03:00
function linkAction ( e ) {
e . preventDefault ( ) ;
2020-02-25 23:28:47 +03:00
const $this = $ ( this ) ;
2020-03-03 07:50:31 +03:00
const redirect = $this . data ( 'redirect' ) ;
2020-02-25 23:28:47 +03:00
$ . post ( $this . data ( 'url' ) , {
_csrf : csrf
} ) . done ( ( data ) => {
if ( data . redirect ) {
window . location . href = data . redirect ;
2020-03-03 07:50:31 +03:00
} else if ( redirect ) {
window . location . href = redirect ;
2020-02-25 23:28:47 +03:00
} else {
window . location . reload ( ) ;
}
} ) ;
}
2020-03-02 21:25:36 +03:00
function linkEmailAction ( e ) {
const $this = $ ( this ) ;
$ ( '#form-uid' ) . val ( $this . data ( 'uid' ) ) ;
$ ( '#form-email' ) . val ( $this . data ( 'email' ) ) ;
$ ( '#form-primary' ) . val ( $this . data ( 'primary' ) ) ;
$ ( '#form-activate' ) . val ( $this . data ( 'activate' ) ) ;
$ ( '#form-uid' ) . val ( $this . data ( 'uid' ) ) ;
$ ( '#change-email-modal' ) . modal ( 'show' ) ;
e . preventDefault ( ) ;
}
2019-11-15 00:39:51 +03:00
function initVueComponents ( ) {
const vueDelimeters = [ '${' , '}' ] ;
Vue . component ( 'repo-search' , {
delimiters : vueDelimeters ,
props : {
searchLimit : {
type : Number ,
default : 10
} ,
suburl : {
type : String ,
required : true
} ,
uid : {
type : Number ,
required : true
} ,
organizations : {
type : Array ,
default : [ ]
} ,
isOrganization : {
type : Boolean ,
default : true
} ,
canCreateOrganization : {
type : Boolean ,
default : false
} ,
organizationsTotalCount : {
type : Number ,
default : 0
} ,
moreReposLink : {
type : String ,
default : ''
}
} ,
data ( ) {
return {
tab : 'repos' ,
repos : [ ] ,
reposTotalCount : 0 ,
reposFilter : 'all' ,
searchQuery : '' ,
isLoading : false ,
2020-02-12 04:53:18 +03:00
staticPrefix : StaticUrlPrefix ,
2019-11-15 00:39:51 +03:00
repoTypes : {
all : {
count : 0 ,
searchMode : '' ,
} ,
forks : {
count : 0 ,
searchMode : 'fork' ,
} ,
mirrors : {
count : 0 ,
searchMode : 'mirror' ,
} ,
sources : {
count : 0 ,
searchMode : 'source' ,
} ,
collaborative : {
count : 0 ,
searchMode : 'collaborative' ,
} ,
}
} ;
} ,
computed : {
showMoreReposLink ( ) {
return this . repos . length > 0 && this . repos . length < this . repoTypes [ this . reposFilter ] . count ;
} ,
searchURL ( ) {
return ` ${ this . suburl } /api/v1/repos/search?sort=updated&order=desc&uid= ${ this . uid } &q= ${ this . searchQuery
} & limit = $ { this . searchLimit } & mode = $ { this . repoTypes [ this . reposFilter ] . searchMode
} $ { this . reposFilter !== 'all' ? '&exclusive=1' : '' } ` ;
} ,
repoTypeCount ( ) {
return this . repoTypes [ this . reposFilter ] . count ;
}
} ,
mounted ( ) {
this . searchRepos ( this . reposFilter ) ;
const self = this ;
Vue . nextTick ( ( ) => {
self . $refs . search . focus ( ) ;
} ) ;
} ,
methods : {
changeTab ( t ) {
this . tab = t ;
} ,
changeReposFilter ( filter ) {
this . reposFilter = filter ;
this . repos = [ ] ;
this . repoTypes [ filter ] . count = 0 ;
this . searchRepos ( filter ) ;
} ,
showRepo ( repo , filter ) {
switch ( filter ) {
case 'sources' :
return repo . owner . id === this . uid && ! repo . mirror && ! repo . fork ;
case 'forks' :
return repo . owner . id === this . uid && ! repo . mirror && repo . fork ;
case 'mirrors' :
return repo . mirror ;
case 'collaborative' :
return repo . owner . id !== this . uid && ! repo . mirror ;
default :
return true ;
}
} ,
searchRepos ( reposFilter ) {
const self = this ;
this . isLoading = true ;
const searchedMode = this . repoTypes [ reposFilter ] . searchMode ;
const searchedURL = this . searchURL ;
const searchedQuery = this . searchQuery ;
$ . getJSON ( searchedURL , ( result , _textStatus , request ) => {
if ( searchedURL === self . searchURL ) {
self . repos = result . data ;
const count = request . getResponseHeader ( 'X-Total-Count' ) ;
if ( searchedQuery === '' && searchedMode === '' ) {
self . reposTotalCount = count ;
}
self . repoTypes [ reposFilter ] . count = count ;
}
} ) . always ( ( ) => {
if ( searchedURL === self . searchURL ) {
self . isLoading = false ;
}
} ) ;
} ,
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
repoClass ( repo ) {
if ( repo . fork ) {
2020-02-11 20:02:41 +03:00
return 'octicon-repo-forked' ;
2019-11-15 00:39:51 +03:00
} if ( repo . mirror ) {
2020-02-11 20:02:41 +03:00
return 'octicon-repo-clone' ;
2020-02-12 04:53:18 +03:00
} if ( repo . template ) {
return ` octicon-repo-template ${ repo . private ? '-private' : '' } ` ;
2019-11-15 00:39:51 +03:00
} if ( repo . private ) {
2020-02-11 20:02:41 +03:00
return 'octicon-lock' ;
2019-11-15 00:39:51 +03:00
}
2020-02-11 20:02:41 +03:00
return 'octicon-repo' ;
2019-11-15 00:39:51 +03:00
}
}
} ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function initCtrlEnterSubmit ( ) {
2020-04-19 01:46:29 +03:00
$ ( '.js-quick-submit' ) . on ( 'keydown' , function ( e ) {
2019-11-15 00:39:51 +03:00
if ( ( ( e . ctrlKey && ! e . altKey ) || e . metaKey ) && ( e . keyCode === 13 || e . keyCode === 10 ) ) {
2020-04-19 01:46:29 +03:00
$ ( this ) . closest ( 'form' ) . trigger ( 'submit' ) ;
2019-11-15 00:39:51 +03:00
}
} ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function initVueApp ( ) {
const el = document . getElementById ( 'app' ) ;
if ( ! el ) {
return ;
}
initVueComponents ( ) ;
new Vue ( {
delimiters : [ '${' , '}' ] ,
el ,
data : {
2020-02-24 00:34:28 +03:00
searchLimit : Number ( ( document . querySelector ( 'meta[name=_search_limit]' ) || { } ) . content ) ,
2020-02-12 04:53:18 +03:00
suburl : AppSubUrl ,
2020-01-20 13:07:30 +03:00
uid : Number ( ( document . querySelector ( 'meta[name=_context_uid]' ) || { } ) . content ) ,
activityTopAuthors : window . ActivityTopAuthors || [ ] ,
} ,
components : {
ActivityTopAuthors ,
2019-11-15 00:39:51 +03:00
} ,
} ) ;
}
window . timeAddManual = function ( ) {
$ ( '.mini.modal' )
. modal ( {
duration : 200 ,
onApprove ( ) {
2020-04-19 01:46:29 +03:00
$ ( '#add_time_manual_form' ) . trigger ( 'submit' ) ;
2019-11-15 00:39:51 +03:00
}
} ) . modal ( 'show' ) ;
} ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
window . toggleStopwatch = function ( ) {
2020-04-19 01:46:29 +03:00
$ ( '#toggle_stopwatch_form' ) . trigger ( 'submit' ) ;
2019-11-15 00:39:51 +03:00
} ;
window . cancelStopwatch = function ( ) {
2020-04-19 01:46:29 +03:00
$ ( '#cancel_stopwatch_form' ) . trigger ( 'submit' ) ;
2019-11-15 00:39:51 +03:00
} ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
function initFilterBranchTagDropdown ( selector ) {
$ ( selector ) . each ( function ( ) {
const $dropdown = $ ( this ) ;
const $data = $dropdown . find ( '.data' ) ;
const data = {
items : [ ] ,
mode : $data . data ( 'mode' ) ,
searchTerm : '' ,
noResults : '' ,
canCreateBranch : false ,
menuVisible : false ,
active : 0
} ;
$data . find ( '.item' ) . each ( function ( ) {
data . items . push ( {
name : $ ( this ) . text ( ) ,
url : $ ( this ) . data ( 'url' ) ,
branch : $ ( this ) . hasClass ( 'branch' ) ,
tag : $ ( this ) . hasClass ( 'tag' ) ,
selected : $ ( this ) . hasClass ( 'selected' )
} ) ;
} ) ;
$data . remove ( ) ;
2019-11-13 17:52:13 +03:00
new Vue ( {
2019-11-15 00:39:51 +03:00
delimiters : [ '${' , '}' ] ,
el : this ,
data ,
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
beforeMount ( ) {
const vm = this ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
this . noResults = vm . $el . getAttribute ( 'data-no-results' ) ;
this . canCreateBranch = vm . $el . getAttribute ( 'data-can-create-branch' ) === 'true' ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
document . body . addEventListener ( 'click' , ( event ) => {
if ( vm . $el . contains ( event . target ) ) {
return ;
}
if ( vm . menuVisible ) {
Vue . set ( vm , 'menuVisible' , false ) ;
}
2019-11-13 17:52:13 +03:00
} ) ;
2019-11-15 00:39:51 +03:00
} ,
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
watch : {
menuVisible ( visible ) {
if ( visible ) {
this . focusSearchField ( ) ;
}
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
} ,
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
computed : {
filteredItems ( ) {
const vm = this ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
const items = vm . items . filter ( ( item ) => {
2020-03-11 22:34:54 +03:00
return ( ( vm . mode === 'branches' && item . branch ) || ( vm . mode === 'tags' && item . tag ) ) &&
( ! vm . searchTerm || item . name . toLowerCase ( ) . includes ( vm . searchTerm . toLowerCase ( ) ) ) ;
2019-11-15 00:39:51 +03:00
} ) ;
vm . active = ( items . length === 0 && vm . showCreateNewBranch ? 0 : - 1 ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
return items ;
2019-11-13 17:52:13 +03:00
} ,
2019-11-15 00:39:51 +03:00
showNoResults ( ) {
return this . filteredItems . length === 0 && ! this . showCreateNewBranch ;
2019-11-13 17:52:13 +03:00
} ,
2019-11-15 00:39:51 +03:00
showCreateNewBranch ( ) {
const vm = this ;
if ( ! this . canCreateBranch || ! vm . searchTerm || vm . mode === 'tags' ) {
return false ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
return vm . items . filter ( ( item ) => item . name . toLowerCase ( ) === vm . searchTerm . toLowerCase ( ) ) . length === 0 ;
}
} ,
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
methods : {
selectItem ( item ) {
const prev = this . getSelected ( ) ;
if ( prev !== null ) {
prev . selected = false ;
}
item . selected = true ;
window . location . href = item . url ;
2019-11-13 17:52:13 +03:00
} ,
2019-11-15 00:39:51 +03:00
createNewBranch ( ) {
if ( ! this . showCreateNewBranch ) {
return ;
}
2020-05-04 23:55:52 +03:00
$ ( this . $refs . newBranchForm ) . trigger ( 'submit' ) ;
2019-11-13 17:52:13 +03:00
} ,
2019-11-15 00:39:51 +03:00
focusSearchField ( ) {
const vm = this ;
Vue . nextTick ( ( ) => {
vm . $refs . searchField . focus ( ) ;
} ) ;
} ,
getSelected ( ) {
for ( let i = 0 , j = this . items . length ; i < j ; ++ i ) {
if ( this . items [ i ] . selected ) return this . items [ i ] ;
}
return null ;
} ,
getSelectedIndexInFiltered ( ) {
for ( let i = 0 , j = this . filteredItems . length ; i < j ; ++ i ) {
if ( this . filteredItems [ i ] . selected ) return i ;
}
return - 1 ;
} ,
scrollToActive ( ) {
let el = this . $refs [ ` listItem ${ this . active } ` ] ;
if ( ! el || el . length === 0 ) {
return ;
}
if ( Array . isArray ( el ) ) {
el = el [ 0 ] ;
}
const cont = this . $refs . scrollContainer ;
if ( el . offsetTop < cont . scrollTop ) {
cont . scrollTop = el . offsetTop ;
} else if ( el . offsetTop + el . clientHeight > cont . scrollTop + cont . clientHeight ) {
cont . scrollTop = el . offsetTop + el . clientHeight - cont . clientHeight ;
}
} ,
keydown ( event ) {
const vm = this ;
if ( event . keyCode === 40 ) {
// arrow down
event . preventDefault ( ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
if ( vm . active === - 1 ) {
vm . active = vm . getSelectedIndexInFiltered ( ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
if ( vm . active + ( vm . showCreateNewBranch ? 0 : 1 ) >= vm . filteredItems . length ) {
return ;
}
vm . active ++ ;
vm . scrollToActive ( ) ;
}
if ( event . keyCode === 38 ) {
// arrow up
event . preventDefault ( ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
if ( vm . active === - 1 ) {
vm . active = vm . getSelectedIndexInFiltered ( ) ;
}
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
if ( vm . active <= 0 ) {
return ;
}
vm . active -- ;
vm . scrollToActive ( ) ;
}
if ( event . keyCode === 13 ) {
// enter
event . preventDefault ( ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
if ( vm . active >= vm . filteredItems . length ) {
vm . createNewBranch ( ) ;
} else if ( vm . active >= 0 ) {
vm . selectItem ( vm . filteredItems [ vm . active ] ) ;
}
}
if ( event . keyCode === 27 ) {
// escape
event . preventDefault ( ) ;
vm . menuVisible = false ;
}
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
}
2019-11-13 17:52:13 +03:00
} ) ;
2019-11-15 00:39:51 +03:00
} ) ;
2019-11-13 17:52:13 +03:00
}
2020-04-19 01:46:29 +03:00
$ ( '.commit-button' ) . on ( 'click' , function ( e ) {
2019-11-15 00:39:51 +03:00
e . preventDefault ( ) ;
$ ( this ) . parent ( ) . find ( '.commit-body' ) . toggle ( ) ;
} ) ;
function initNavbarContentToggle ( ) {
const content = $ ( '#navbar' ) ;
const toggle = $ ( '#navbar-expand-toggle' ) ;
let isExpanded = false ;
2020-04-19 01:46:29 +03:00
toggle . on ( 'click' , ( ) => {
2019-11-15 00:39:51 +03:00
isExpanded = ! isExpanded ;
if ( isExpanded ) {
content . addClass ( 'shown' ) ;
toggle . addClass ( 'active' ) ;
} else {
content . removeClass ( 'shown' ) ;
toggle . removeClass ( 'active' ) ;
}
} ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
function initTopicbar ( ) {
const mgrBtn = $ ( '#manage_topic' ) ;
const editDiv = $ ( '#topic_edit' ) ;
const viewDiv = $ ( '#repo-topics' ) ;
const saveBtn = $ ( '#save_topic' ) ;
const topicDropdown = $ ( '#topic_edit .dropdown' ) ;
const topicForm = $ ( '#topic_edit.ui.form' ) ;
const topicPrompts = getPrompts ( ) ;
2020-04-19 01:46:29 +03:00
mgrBtn . on ( 'click' , ( ) => {
2019-11-15 00:39:51 +03:00
viewDiv . hide ( ) ;
editDiv . css ( 'display' , '' ) ; // show Semantic UI Grid
} ) ;
function getPrompts ( ) {
const hidePrompt = $ ( 'div.hide#validate_prompt' ) ;
const prompts = {
countPrompt : hidePrompt . children ( '#count_prompt' ) . text ( ) ,
formatPrompt : hidePrompt . children ( '#format_prompt' ) . text ( )
} ;
hidePrompt . remove ( ) ;
return prompts ;
}
2020-04-19 01:46:29 +03:00
saveBtn . on ( 'click' , ( ) => {
2019-11-15 00:39:51 +03:00
const topics = $ ( 'input[name=topics]' ) . val ( ) ;
$ . post ( saveBtn . data ( 'link' ) , {
_csrf : csrf ,
topics
} , ( _data , _textStatus , xhr ) => {
if ( xhr . responseJSON . status === 'ok' ) {
viewDiv . children ( '.topic' ) . remove ( ) ;
if ( topics . length ) {
const topicArray = topics . split ( ',' ) ;
const last = viewDiv . children ( 'a' ) . last ( ) ;
for ( let i = 0 ; i < topicArray . length ; i ++ ) {
2019-12-18 00:34:11 +03:00
const link = $ ( '<a class="ui repo-topic small label topic"></a>' ) ;
2020-02-12 04:53:18 +03:00
link . attr ( 'href' , ` ${ AppSubUrl } /explore/repos?q= ${ encodeURIComponent ( topicArray [ i ] ) } &topic=1 ` ) ;
2019-12-18 00:34:11 +03:00
link . text ( topicArray [ i ] ) ;
link . insertBefore ( last ) ;
2019-11-15 00:39:51 +03:00
}
}
editDiv . css ( 'display' , 'none' ) ;
viewDiv . show ( ) ;
}
} ) . fail ( ( xhr ) => {
if ( xhr . status === 422 ) {
if ( xhr . responseJSON . invalidTopics . length > 0 ) {
topicPrompts . formatPrompt = xhr . responseJSON . message ;
2020-03-11 22:34:54 +03:00
const { invalidTopics } = xhr . responseJSON ;
2019-11-15 00:39:51 +03:00
const topicLables = topicDropdown . children ( 'a.ui.label' ) ;
topics . split ( ',' ) . forEach ( ( value , index ) => {
for ( let i = 0 ; i < invalidTopics . length ; i ++ ) {
if ( invalidTopics [ i ] === value ) {
topicLables . eq ( index ) . removeClass ( 'green' ) . addClass ( 'red' ) ;
}
}
} ) ;
} else {
topicPrompts . countPrompt = xhr . responseJSON . message ;
}
}
} ) . always ( ( ) => {
topicForm . form ( 'validate form' ) ;
} ) ;
} ) ;
topicDropdown . dropdown ( {
allowAdditions : true ,
forceSelection : false ,
2020-03-11 22:34:54 +03:00
fields : { name : 'description' , value : 'data-value' } ,
2019-11-15 00:39:51 +03:00
saveRemoteData : false ,
label : {
transition : 'horizontal flip' ,
duration : 200 ,
variation : false ,
blue : true ,
basic : true ,
} ,
className : {
label : 'ui small label'
} ,
apiSettings : {
2020-02-12 04:53:18 +03:00
url : ` ${ AppSubUrl } /api/v1/topics/search?q={query} ` ,
2019-11-15 00:39:51 +03:00
throttle : 500 ,
cache : false ,
onResponse ( res ) {
const formattedResponse = {
success : false ,
results : [ ] ,
} ;
const stripTags = function ( text ) {
return text . replace ( /<[^>]*>?/gm , '' ) ;
} ;
const query = stripTags ( this . urlData . query . trim ( ) ) ;
let found _query = false ;
const current _topics = [ ] ;
2020-03-11 22:34:54 +03:00
topicDropdown . find ( 'div.label.visible.topic,a.label.visible' ) . each ( ( _ , e ) => { current _topics . push ( e . dataset . value ) } ) ;
2019-11-15 00:39:51 +03:00
if ( res . topics ) {
let found = false ;
for ( let i = 0 ; i < res . topics . length ; i ++ ) {
// skip currently added tags
2020-03-11 22:34:54 +03:00
if ( current _topics . includes ( res . topics [ i ] . topic _name ) ) {
2019-11-15 00:39:51 +03:00
continue ;
}
if ( res . topics [ i ] . topic _name . toLowerCase ( ) === query . toLowerCase ( ) ) {
found _query = true ;
}
2020-03-11 22:34:54 +03:00
formattedResponse . results . push ( { description : res . topics [ i ] . topic _name , 'data-value' : res . topics [ i ] . topic _name } ) ;
2019-11-15 00:39:51 +03:00
found = true ;
}
formattedResponse . success = found ;
}
if ( query . length > 0 && ! found _query ) {
formattedResponse . success = true ;
2020-03-11 22:34:54 +03:00
formattedResponse . results . unshift ( { description : query , 'data-value' : query } ) ;
2019-11-15 00:39:51 +03:00
} else if ( query . length > 0 && found _query ) {
formattedResponse . results . sort ( ( a , b ) => {
if ( a . description . toLowerCase ( ) === query . toLowerCase ( ) ) return - 1 ;
if ( b . description . toLowerCase ( ) === query . toLowerCase ( ) ) return 1 ;
if ( a . description > b . description ) return - 1 ;
if ( a . description < b . description ) return 1 ;
return 0 ;
} ) ;
}
return formattedResponse ;
} ,
} ,
onLabelCreate ( value ) {
value = value . toLowerCase ( ) . trim ( ) ;
this . attr ( 'data-value' , value ) . contents ( ) . first ( ) . replaceWith ( value ) ;
return $ ( this ) ;
} ,
onAdd ( addedValue , _addedText , $addedChoice ) {
addedValue = addedValue . toLowerCase ( ) . trim ( ) ;
$ ( $addedChoice ) . attr ( 'data-value' , addedValue ) ;
$ ( $addedChoice ) . attr ( 'data-text' , addedValue ) ;
}
} ) ;
$ . fn . form . settings . rules . validateTopic = function ( _values , regExp ) {
const topics = topicDropdown . children ( 'a.ui.label' ) ;
const status = topics . length === 0 || topics . last ( ) . attr ( 'data-value' ) . match ( regExp ) ;
if ( ! status ) {
topics . last ( ) . removeClass ( 'green' ) . addClass ( 'red' ) ;
}
return status && topicDropdown . children ( 'a.ui.label.red' ) . length === 0 ;
} ;
topicForm . form ( {
on : 'change' ,
inline : true ,
fields : {
topics : {
identifier : 'topics' ,
rules : [
{
type : 'validateTopic' ,
2019-12-02 12:13:19 +03:00
value : /^[a-z0-9][a-z0-9-]{0,35}$/ ,
2019-11-15 00:39:51 +03:00
prompt : topicPrompts . formatPrompt
} ,
{
type : 'maxCount[25]' ,
prompt : topicPrompts . countPrompt
}
]
} ,
}
} ) ;
}
window . toggleDeadlineForm = function ( ) {
$ ( '#deadlineForm' ) . fadeToggle ( 150 ) ;
} ;
window . setDeadline = function ( ) {
const deadline = $ ( '#deadlineDate' ) . val ( ) ;
window . updateDeadline ( deadline ) ;
} ;
window . updateDeadline = function ( deadlineString ) {
$ ( '#deadline-err-invalid-date' ) . hide ( ) ;
$ ( '#deadline-loader' ) . addClass ( 'loading' ) ;
let realDeadline = null ;
if ( deadlineString !== '' ) {
const newDate = Date . parse ( deadlineString ) ;
if ( Number . isNaN ( newDate ) ) {
$ ( '#deadline-loader' ) . removeClass ( 'loading' ) ;
$ ( '#deadline-err-invalid-date' ) . show ( ) ;
return false ;
}
realDeadline = new Date ( newDate ) ;
}
$ . ajax ( ` ${ $ ( '#update-issue-deadline-form' ) . attr ( 'action' ) } /deadline ` , {
data : JSON . stringify ( {
due _date : realDeadline ,
} ) ,
headers : {
'X-Csrf-Token' : csrf ,
'X-Remote' : true ,
} ,
contentType : 'application/json' ,
type : 'POST' ,
success ( ) {
reload ( ) ;
} ,
error ( ) {
$ ( '#deadline-loader' ) . removeClass ( 'loading' ) ;
$ ( '#deadline-err-invalid-date' ) . show ( ) ;
}
} ) ;
} ;
window . deleteDependencyModal = function ( id , type ) {
$ ( '.remove-dependency' )
. modal ( {
closable : false ,
duration : 200 ,
onApprove ( ) {
$ ( '#removeDependencyID' ) . val ( id ) ;
$ ( '#dependencyType' ) . val ( type ) ;
2020-04-19 01:46:29 +03:00
$ ( '#removeDependencyForm' ) . trigger ( 'submit' ) ;
2019-11-15 00:39:51 +03:00
}
} ) . modal ( 'show' ) ;
} ;
2019-11-13 17:52:13 +03:00
function initIssueList ( ) {
2019-11-15 00:39:51 +03:00
const repolink = $ ( '#repolink' ) . val ( ) ;
const repoId = $ ( '#repoId' ) . val ( ) ;
const crossRepoSearch = $ ( '#crossRepoSearch' ) . val ( ) ;
2020-01-19 09:43:38 +03:00
const tp = $ ( '#type' ) . val ( ) ;
2020-02-12 04:53:18 +03:00
let issueSearchUrl = ` ${ AppSubUrl } /api/v1/repos/ ${ repolink } /issues?q={query}&type= ${ tp } ` ;
2019-11-15 00:39:51 +03:00
if ( crossRepoSearch === 'true' ) {
2020-02-12 04:53:18 +03:00
issueSearchUrl = ` ${ AppSubUrl } /api/v1/repos/issues/search?q={query}&priority_repo_id= ${ repoId } &type= ${ tp } ` ;
2019-11-15 00:39:51 +03:00
}
$ ( '#new-dependency-drop-list' )
. dropdown ( {
apiSettings : {
url : issueSearchUrl ,
onResponse ( response ) {
2020-03-11 22:34:54 +03:00
const filteredResponse = { success : true , results : [ ] } ;
2019-11-15 00:39:51 +03:00
const currIssueId = $ ( '#new-dependency-drop-list' ) . data ( 'issue-id' ) ;
// Parse the response from the api to work with our dropdown
$ . each ( response , ( _i , issue ) => {
// Don't list current issue in the dependency list.
if ( issue . id === currIssueId ) {
return ;
}
filteredResponse . results . push ( {
name : ` # ${ issue . number } ${ htmlEncode ( issue . title )
} < div class = "text small dont-break-out" > $ { htmlEncode ( issue . repository . full _name ) } < / d i v > ` ,
value : issue . id
} ) ;
} ) ;
return filteredResponse ;
} ,
cache : false ,
} ,
fullTextSearch : true
} ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
$ ( '.menu a.label-filter-item' ) . each ( function ( ) {
2020-04-19 01:46:29 +03:00
$ ( this ) . on ( 'click' , function ( e ) {
2019-11-15 00:39:51 +03:00
if ( e . altKey ) {
e . preventDefault ( ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
const href = $ ( this ) . attr ( 'href' ) ;
const id = $ ( this ) . data ( 'label-id' ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
const regStr = ` labels=(-?[0-9]+%2c)*( ${ id } )(%2c-?[0-9]+)*& ` ;
const newStr = 'labels=$1-$2$3&' ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
window . location = href . replace ( new RegExp ( regStr ) , newStr ) ;
}
2019-11-13 17:52:13 +03:00
} ) ;
2019-11-15 00:39:51 +03:00
} ) ;
2019-11-13 17:52:13 +03:00
2020-04-19 01:46:29 +03:00
$ ( '.menu .ui.dropdown.label-filter' ) . on ( 'keydown' , ( e ) => {
2019-11-15 00:39:51 +03:00
if ( e . altKey && e . keyCode === 13 ) {
const selectedItems = $ ( '.menu .ui.dropdown.label-filter .menu .item.selected' ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
if ( selectedItems . length > 0 ) {
const item = $ ( selectedItems [ 0 ] ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
const href = item . attr ( 'href' ) ;
const id = item . data ( 'label-id' ) ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
const regStr = ` labels=(-?[0-9]+%2c)*( ${ id } )(%2c-?[0-9]+)*& ` ;
const newStr = 'labels=$1-$2$3&' ;
2019-11-13 17:52:13 +03:00
2019-11-15 00:39:51 +03:00
window . location = href . replace ( new RegExp ( regStr ) , newStr ) ;
}
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
} ) ;
2019-11-13 17:52:13 +03:00
}
2019-11-15 00:39:51 +03:00
window . cancelCodeComment = function ( btn ) {
const form = $ ( btn ) . closest ( 'form' ) ;
if ( form . length > 0 && form . hasClass ( 'comment-form' ) ) {
form . addClass ( 'hide' ) ;
form . parent ( ) . find ( 'button.comment-form-reply' ) . show ( ) ;
} else {
form . closest ( '.comment-code-cloud' ) . remove ( ) ;
}
} ;
2020-02-13 08:22:15 +03:00
window . submitReply = function ( btn ) {
const form = $ ( btn ) . closest ( 'form' ) ;
if ( form . length > 0 && form . hasClass ( 'comment-form' ) ) {
2020-04-19 01:46:29 +03:00
form . trigger ( 'submit' ) ;
2020-02-13 08:22:15 +03:00
}
} ;
2019-11-15 00:39:51 +03:00
window . onOAuthLoginClick = function ( ) {
const oauthLoader = $ ( '#oauth2-login-loader' ) ;
const oauthNav = $ ( '#oauth2-login-navigator' ) ;
oauthNav . hide ( ) ;
oauthLoader . removeClass ( 'disabled' ) ;
setTimeout ( ( ) => {
// recover previous content to let user try again
// usually redirection will be performed before this action
oauthLoader . addClass ( 'disabled' ) ;
oauthNav . show ( ) ;
} , 5000 ) ;
} ;
2020-02-18 02:11:59 +03:00
// Pull SVGs via AJAX to workaround CORS issues with <use> tags
// https://css-tricks.com/ajaxing-svg-sprite/
$ . get ( ` ${ window . config . StaticUrlPrefix } /img/svg/icons.svg ` , ( data ) => {
const div = document . createElement ( 'div' ) ;
div . style . display = 'none' ;
div . innerHTML = new XMLSerializer ( ) . serializeToString ( data . documentElement ) ;
document . body . insertBefore ( div , document . body . childNodes [ 0 ] ) ;
} ) ;