From 95759739c211401ebc99565e588d76d4ac5ca13d Mon Sep 17 00:00:00 2001 From: aaronchen2k <462826@qq.com> Date: Thu, 23 Jan 2020 09:25:27 +0800 Subject: [PATCH] cp repo codes from pro version --- module/common/lang/menuOrder.php | 8 +- module/common/lang/zh-cn.php | 10 +- module/repo/config.php | 4 + module/repo/control.php | 401 ++++++++++++++++++- module/repo/css/blame.css | 11 + module/repo/css/browse.css | 2 + module/repo/css/common.css | 2 - module/repo/js/diff.js | 500 ++++++++++++++++++++++++ module/repo/js/review.js | 4 + module/repo/js/view.js | 477 +++++++++++++++++++++- module/repo/model.php | 259 +++++++++++- module/repo/view/ajaxsidelogs.html.php | 2 +- module/repo/view/blame.html.php | 102 +++++ module/repo/view/browse.html.php | 79 ++++ module/repo/view/diff.html.php | 1 + module/repo/view/header.review.html.php | 256 ++++++++++++ module/repo/view/log.html.php | 10 +- module/repo/view/review.html.php | 70 ++++ module/repo/view/revision.html.php | 4 +- module/repo/view/view.html.php | 2 + 20 files changed, 2160 insertions(+), 44 deletions(-) create mode 100644 module/repo/css/blame.css create mode 100644 module/repo/css/browse.css create mode 100644 module/repo/js/review.js create mode 100644 module/repo/view/blame.html.php create mode 100644 module/repo/view/browse.html.php create mode 100644 module/repo/view/header.review.html.php create mode 100644 module/repo/view/review.html.php diff --git a/module/common/lang/menuOrder.php b/module/common/lang/menuOrder.php index 1f38d0f355..2857401200 100644 --- a/module/common/lang/menuOrder.php +++ b/module/common/lang/menuOrder.php @@ -77,9 +77,11 @@ $lang->testsuite->menuOrder = $lang->testcase->menuOrder; $lang->caselib->menuOrder = $lang->testcase->menuOrder; $lang->testreport->menuOrder = $lang->testcase->menuOrder; -$lang->repo->menuOrder[5] = 'browse'; -//$lang->repo->menuOrder[15] = 'settings'; -//$lang->repo->menuOrder[20] = 'delete'; +$lang->repo->menuOrder[5] = 'log'; +$lang->repo->menuOrder[10] = 'review'; +$lang->repo->menuOrder[15] = 'browse'; +$lang->repo->menuOrder[20] = 'job'; +$lang->repo->menuOrder[25] = 'jenkins'; /* doc menu order. */ $lang->doc->menuOrder[5] = 'list'; diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php index 264cd0f4cf..c19b7454d2 100644 --- a/module/common/lang/zh-cn.php +++ b/module/common/lang/zh-cn.php @@ -123,7 +123,7 @@ $lang->menu->my = ' 我的地盘|my|index'; $lang->menu->product = $lang->productCommon . '|product|index|locate=no'; $lang->menu->project = $lang->projectCommon . '|project|index|locate=no'; $lang->menu->qa = '测试|qa|index'; -$lang->menu->repo = '代码|repo|log'; +$lang->menu->repo = '持续集成|repo|log'; $lang->menu->doc = '文档|doc|index'; $lang->menu->report = '统计|report|index'; $lang->menu->company = '组织|company|index'; @@ -334,9 +334,11 @@ $lang->caselib->menu->caselib = array('link' => '用例库|caselib|browse|libI $lang->repo = new stdclass(); $lang->repo->menu = new stdclass(); -$lang->repo->menu->browse = array('link' =>'浏览|repo|log|repoID=%s&entry=', 'alias' => 'diff,view,revision,showsynccomment'); -//$lang->repo->menu->settings = '设置|repo|settings|repoID=%s'; -//$lang->repo->menu->delete = array('link' => '删除|repo|delete|repoID=%s', 'target' => 'hiddenwin'); +$lang->repo->menu->log = array('link' =>'浏览|repo|log|repoID=%s&entry=', 'alias' => 'diff,view,revision,showsynccomment'); +$lang->repo->menu->review = '评审|repo|review|repoID=%s'; +$lang->repo->menu->browse = '代码库|repo|browse'; +$lang->repo->menu->job = '构建|repo|job|repoID=%s'; +$lang->repo->menu->jenkins = 'Jenkins|repo|job|repoID=%s'; /* 文档视图菜单设置。*/ $lang->doc = new stdclass(); diff --git a/module/repo/config.php b/module/repo/config.php index d8ea864336..b5e1de4207 100644 --- a/module/repo/config.php +++ b/module/repo/config.php @@ -26,3 +26,7 @@ $config->repo->syncTime = 10; $config->repo->batchNum = 100; $config->repo->images = '|png|gif|jpg|ico|jpeg|bmp|'; $config->repo->binary = '|pdf|'; + +$config->repo->editor = new stdclass(); +$config->repo->editor->view = array('id' => 'commentText', 'tools' => 'simpleTools'); +$config->repo->editor->diff = array('id' => 'commentText', 'tools' => 'simpleTools'); diff --git a/module/repo/control.php b/module/repo/control.php index 7192ea3ede..63952e03a4 100644 --- a/module/repo/control.php +++ b/module/repo/control.php @@ -77,7 +77,7 @@ class repo extends control { die(js::locate($this->repo->createLink('showSyncComment', "repoID=$repoID"), 'parent')); } - die(js::locate($this->repo->createLink('log', "repoID=$repoID"), 'parent')); + die(js::locate($this->repo->createLink('browse', "repoID=$repoID"), 'parent')); } $this->view->title = $this->lang->repo->settings; @@ -106,7 +106,92 @@ class repo extends control $this->dao->delete()->from(TABLE_REPOFILES)->where('repo')->eq($repoID)->exec(); $this->dao->delete()->from(TABLE_REPOBRANCH)->where('repo')->eq($repoID)->exec(); echo js::alert($this->lang->repo->notice->successDelete); - die(js::locate($this->repo->createLink('log'), 'parent')); + die(js::locate($this->repo->createLink('browse'), 'parent')); + } + + /** + * Browse repo. + * + * @param int $repoID + * @param string $path + * @param string $revision + * @param int $refresh + * @access public + * @return void + */ + public function browse($repoID = 0, $path = '', $revision = 'HEAD', $refresh = 0) + { + if($this->get->path) $path = $this->get->path; + if(empty($refresh) and $this->cookie->repoRefresh) $refresh = $this->cookie->repoRefresh; + $this->repo->setMenu($this->repos, $repoID); + $this->repo->setBackSession('list', $withOtherModule = true); + if($repoID == 0) $repoID = $this->session->repoID; + + $repo = $this->repo->getRepoByID($repoID); + if(!$repo->synced) $this->locate($this->repo->createLink('showSyncComment', "repoID=$repoID")); + + $path = $this->repo->decodePath($path); + $cacheFile = $this->repo->getCacheFile($repoID, $path, $revision); + $this->scm->setEngine($repo); + + $this->app->loadClass('pager', $static = true); + $pager = new pager(0, 8, 1); + + /* Cache infos. */ + if($refresh or !$cacheFile or !file_exists($cacheFile) or (time() - filemtime($cacheFile)) / 60 > $this->config->repo->cacheTime) + { + $infos = $this->scm->ls($path, $revision); + + if($infos and $repo->SCM == 'Subversion') + { + $revisionList = array(); + foreach($infos as $info) $revisionList[$info->revision] = $info->revision; + $comments = $this->repo->getHistory($repoID, $revisionList); + foreach($infos as $info) + { + if(isset($comments[$info->revision])) + { + $comment = $comments[$info->revision]; + $info->comment = $comment->comment; + } + } + } + + if($cacheFile) + { + if(!file_exists($cacheFile . '.lock')) + { + touch($cacheFile . '.lock'); + file_put_contents($cacheFile, serialize($infos)); + unlink($cacheFile . '.lock'); + } + } + } + else + { + $infos = unserialize(file_get_contents($cacheFile)); + } + if($refresh) $this->repo->updateLatestCommit($repo); + if($this->cookie->repoRefresh) setcookie('repoRefresh', 0, 0, $this->config->webRoot); + + $logType = 'dir'; + $revisions = $this->repo->getLogs($repo, $path, $revision, $logType, $pager); + $commiters = $this->loadModel('user')->getCommiters(); + foreach($infos as $info) $info->committer = zget($commiters, $info->account, $info->account); + foreach($revisions as $log) $log->committer = zget($commiters, $log->committer, $log->committer); + + $this->view->title = $this->lang->repo->common; + $this->view->repo = $repo; + $this->view->revisions = $revisions; + $this->view->revision = $revision; + $this->view->infos = $infos; + $this->view->repoID = $repoID; + $this->view->pager = $pager; + $this->view->path = urldecode($path); + $this->view->logType = $logType; + $this->view->cacheTime = date('m-d H:i', filemtime($cacheFile)); + + $this->display(); } /** @@ -196,22 +281,6 @@ class repo extends control $this->display(); } - /** - * Browse repo. - * - * @param int $repoID - * @param string $path - * @param string $revision - * @param int $refresh - * @access public - * @return void - */ - public function browse($repoID = 0, $path = '', $revision = 'HEAD', $refresh = 0) - { - if($this->get->path) $path = $this->get->path; - $this->locate($this->repo->createLink('log', "repoID=$repoID&entry=&revision=$revision", empty($path) ? '' : "entry=$path")); - } - /** * show repo log. * @@ -225,7 +294,7 @@ class repo extends control * @access public * @return void */ - public function log($repoID = 0, $entry = '', $revision = 'HEAD', $type = 'dir', $recTotal = 0, $recPerPage = 50, $pageID = 1) + public function log($repoID, $entry, $revision = 'HEAD', $type = 'dir', $recTotal = 0, $recPerPage = 50, $pageID = 1) { if($this->get->entry) $entry = $this->get->entry; $this->repo->setMenu($this->repos, $repoID); @@ -259,6 +328,51 @@ class repo extends control $this->display(); } + /** + * Blame repo file. + * + * @param int $repoID + * @param string $entry + * @param string $revision + * @param string $encoding + * @access public + * @return void + */ + public function blame($repoID, $entry, $revision = 'HEAD', $encoding = '') + { + if($this->get->entry) $entry = $this->get->entry; + $this->repo->setMenu($this->repos, $repoID); + if($repoID == 0) $repoID = $this->session->repoID; + $repo = $this->repo->getRepoByID($repoID); + $file = $entry; + $entry = $this->repo->decodePath($entry); + + $this->scm->setEngine($repo); + $encoding = empty($encoding) ? $repo->encoding : $encoding; + $encoding = strtolower(str_replace('_', '-', $encoding)); + $blames = $this->scm->blame($entry, $revision); + $revisions = array(); + foreach($blames as $i => $blame) + { + if(isset($blame['revision'])) $revisions[$blame['revision']] = $blame['revision']; + if($encoding != 'utf-8') $blames[$i]['content'] = helper::convertEncoding($blame['content'], $encoding); + } + + $log = $repo->SCM == 'Git' ? $this->dao->select('revision,commit')->from(TABLE_REPOHISTORY)->where('revision')->eq($revision)->andWhere('repo')->eq($repo->id)->fetch() : ''; + + $this->view->title = $this->lang->repo->common; + $this->view->repoID = $repoID; + $this->view->repo = $repo; + $this->view->revision = $revision; + $this->view->entry = $entry; + $this->view->file = $file; + $this->view->encoding = str_replace('-', '_', $encoding); + $this->view->historys = $repo->SCM == 'Git' ? $this->dao->select('revision,commit')->from(TABLE_REPOHISTORY)->where('revision')->in($revisions)->andWhere('repo')->eq($repo->id)->fetchPairs() : ''; + $this->view->revisionName = ($log and $repo->SCM == 'Git') ? $this->repo->getGitRevisionName($log->revision, $log->commit) : $revision; + $this->view->blames = $blames; + $this->display(); + } + /** * Show repo revision. * @@ -302,7 +416,7 @@ class repo extends control } else { - $change['view'] = $viewPriv ? html::a($this->repo->createLink('log', "repoID=$repoID&entry=&revision=$revision", "entry=$encodePath"), $this->lang->repo->log) : ''; + $change['view'] = $viewPriv ? html::a($this->repo->createLink('browse', "repoID=$repoID&path=&revision=$revision", "path=$encodePath"), $this->lang->repo->browse) : ''; if($change['action'] == 'M') $change['diff'] = $diffPriv ? html::a($this->repo->createLink('diff', "repoID=$repoID&entry=&oldRevision=$oldRevision&newRevision=$revision", "entry=$encodePath"), $this->lang->repo->diffAB) : ''; } $changes[$path] = $change; @@ -576,6 +690,222 @@ class repo extends control echo $type == 'batch' ? $commitCount : 'finish'; } + /** + * addBug + * + * @param int $repoID + * @param string $file + * @param int $v1 + * @param int $v2 + * @access public + * @return void + */ + public function addBug($repoID, $file, $v1, $v2) { + if($this->get->file) $file = $this->get->file; + if(!empty($_POST)) + { + $result = $this->repo->saveBug($repoID, $file, $v1, $v2); + if(dao::isError()) die(json_encode($result)); + + $bugID = $result['id']; + $repo = $this->repo->getRepoById($repoID); + $entry = $repo->name . '/' . $this->repo->decodePath($file); + $location = sprintf($this->lang->repo->reviewLocation, $entry, $repo->SCM == 'Git' ? substr($v2, 0, 10) : $v2, $this->post->begin, $this->post->end); + if(empty($v1)) + { + $revision = $repo->SCM == 'Git' ? substr($v2, 0, 10) : $v2; + $link = $this->repo->createLink('view', "repoID=$repoID&entry=&revision=$v2&showBug=true", "entry={$file}") . '#L' . $this->post->begin; + } + else + { + $revision = $repo->SCM == 'Git' ? substr($v1, 0, 10) : $v1; + $revision .= ' : '; + $revision .= $repo->SCM == 'Git' ? substr($v2, 0, 10) : $v2; + $link = $this->repo->createLink('diff', "repoID=$repoID&entry=&oldRevision=$v1&newRevision=$v2&showBug=true", "entry={$file}") . '#L' . $this->post->begin; + } + + $actionID = $this->loadModel('action')->create('bug', $bugID, 'repoCreated', '', html::a($link, $location, 'blank')); + $this->loadModel('bug')->sendmail($bugID, $actionID); + + echo json_encode($result); + } + } + + /** + * Add comment. + * + * @access public + * @return void + */ + public function addComment() + { + if(!empty($_POST)) + { + $now = helper::now(); + $bug = $this->loadModel('bug')->getByID($this->post->objectID); + $data = fixer::input('post') + ->add('objectType', 'bug') + ->add('product', ',' . $bug->product . ',') + ->add('project', $bug->project) + ->add('actor', $this->app->user->account) + ->add('action', 'commented') + ->add('date', $now) + ->get(); + + $this->dao->insert(TABLE_ACTION)->data($data)->exec(); + $actionID = $this->dao->lastInsertID(); + + $response = array('bugID' => $actionID, 'id' => $actionID, 'realname' => $this->app->user->realname, 'date' => substr($now, 5, 11), 'edit' => true, 'comment' => $data->comment); + echo json_encode($response); + } + } + + /** + * Show review. + * + * @param int $repoID + * @param string $browseType + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function review($repoID, $browseType = 'all', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + $browseType = strtolower($browseType); + $this->app->loadLang('bug'); + $this->repo->setMenu($this->repos, $repoID); + $this->repo->setBackSession('list', $withOtherModule = true); + if($repoID == 0) $repoID = $this->session->repoID; + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + + $bugs = $this->repo->getBugsByRepo($repoID, $browseType, $orderBy, $pager); + $repo = $this->repo->getRepoById($repoID); + + if($repo->SCM == 'Git') + { + $revisions = array(); + foreach($bugs as $bug) + { + $revisions[] = $bug->v2; + if(!empty($bug->v1)) $revisions[] = $bug->v1; + } + $this->view->historys = $this->dao->select('revision,commit')->from(TABLE_REPOHISTORY)->where('revision')->in($revisions)->andWhere('repo')->eq($repoID)->fetchPairs('revision', 'commit'); + } + + $this->view->repoID = $repoID; + $this->view->repo = $repo; + $this->view->bugs = $bugs; + $this->view->pager = $pager; + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->view->title = $this->lang->repo->review; + $this->view->browseType = $browseType; + $this->display(); + } + + /** + * Edit bug. + * + * @param int $bugID + * @access public + * @return void + */ + public function editBug($bugID) + { + if(!empty($_POST)) + { + $oldBug = $this->loadModel('bug')->getById($bugID); + $data = fixer::input('post')->get(); + $data->commentText = $this->loadModel('file')->pasteImage($data->commentText); + $title = $data->commentText; + if($title and $title != $oldBug->title) + { + $this->repo->updateBug($bugID, $title); + + $old = new stdclass(); + $old->title = $oldBug->title; + $new = new stdclass(); + $new->title = $title; + $changes = common::createChanges($old, $new); + $actionID = $this->loadModel('action')->create('bug', $bugID, 'Edited'); + $this->action->logHistory($actionID, $changes); + } + echo $title; + } + } + + /** + * Delete bug. + * + * @param int $bugID + * @param string $confirm + * @access public + * @return void + */ + public function deleteBug($bugID, $confirm = 'no') + { + if($confirm == 'yes') + { + $this->loadModel('bug')->delete(TABLE_BUG, $bugID); + echo 'deleted'; + } + return false; + } + + /** + * Edit comment. + * + * @param int $commentID + * @access public + * @return void + */ + public function editComment($commentID) + { + if(!empty($_POST)) + { + $comment = $this->loadModel('file')->pasteImage($this->post->commentText); + $result = $this->repo->updateComment($commentID, $comment); + echo $comment; + } + } + + /** + * Delete comment. + * + * @param int $commentID + * @param string $confirm + * @access public + * @return void + */ + public function deleteComment($commentID, $confirm = 'no') + { + if($confirm == 'yes') + { + $result = $this->repo->deleteComment($commentID); + if($result) echo 'deleted'; + } + return false; + } + + /** + * Ajax get projects. + * + * @param int $productID + * @param int $branch + * @access public + * @return void + */ + public function ajaxGetProjects($productID, $branch = 0) + { + $projects = $this->repo->getProjectPairs($productID, $branch); + echo html::select('project', array('' => '') + $projects, '', 'class="form-control chosen"'); + } + /** * Ajax show side logs. * @@ -608,4 +938,35 @@ class repo extends control $this->view->path = urldecode($path); $this->display(); } + + /** + * Ajax get committer. + * + * @param int $repoID + * @param string $entry + * @param int $revision + * @param int $line + * @access public + * @return void + */ + public function ajaxGetCommitter($repoID, $entry, $revision, $line) + { + if($this->get->entry) $entry = $this->get->entry; + $repo = $this->repo->getRepoByID($repoID); + $entry = $this->repo->decodePath($entry); + + $this->scm->setEngine($repo); + $blames = $this->scm->blame($entry, $revision); + $committer = ''; + while($line > 0) + { + if(isset($blames[$line]['committer'])) + { + $committer = $blames[$line]['committer']; + break; + } + $line--; + } + die($committer); + } } diff --git a/module/repo/css/blame.css b/module/repo/css/blame.css new file mode 100644 index 0000000000..57c589e9de --- /dev/null +++ b/module/repo/css/blame.css @@ -0,0 +1,11 @@ +.panel.code > .content{border: 1px solid #ccc;} +.blame{font-size:13px;} +.blame thead{font-weight:bold; background:#ddd;} +.blame td{border-left:1px solid #ccc;} +.blame tr.topLine > td:first-child{border-left:none;} +.blame .info{ background-color:#f7f7f7; vertical-align:top; padding:0px 4px} +.topLine{border-top: 1px solid #ccc;} +.topLine td.info{padding:8px 4px;} +pre {margin-bottom:0px;display:block; white-space: pre-wrap; padding:0px 3px; border:0px; background:none;} +.line{ font-weight: bold; border: 1px solid #E4E4E4; background-color: #ECECEC; text-align: right;} +.panel-actions.pull-right{margin-top:-5px; } diff --git a/module/repo/css/browse.css b/module/repo/css/browse.css new file mode 100644 index 0000000000..9b6db01afb --- /dev/null +++ b/module/repo/css/browse.css @@ -0,0 +1,2 @@ +#mainMenu > .btn-toolbar{height: 35px; line-height: 35px;} +.btn-toolbar > .last-sync-time{display:inline-block; float:left; margin-right: 10px;} diff --git a/module/repo/css/common.css b/module/repo/css/common.css index 95d97fd170..1040249fa2 100644 --- a/module/repo/css/common.css +++ b/module/repo/css/common.css @@ -177,5 +177,3 @@ h3 {font-size:16px;} #sidebar>.sidebar-toggle{left:5px; right:auto;} #sidebar>.sidebar-toggle>.icon{right:-4px; left:auto;} - -#logForm .fixed-footer a.allLogs{color:#fff !important;} diff --git a/module/repo/js/diff.js b/module/repo/js/diff.js index e2caeb5a9e..c322d6f73f 100644 --- a/module/repo/js/diff.js +++ b/module/repo/js/diff.js @@ -1,3 +1,503 @@ +$(document).ready(function() +{ + $("#inline").click(function(){$('#arrange').val('inline');this.form.submit();}); + $("#appose").click(function(){$('#arrange').val('appose');this.form.submit();}); + $(".label-exchange").click(function(){ $('#exchange').submit();}); + + var $diffCode = $('.diff'); + var hidePreview; + var $bugsPreview = $('#bugsPreview'); + var $bugsPreviewMenu = $('#bugsPreview').children('.dropdown-menu'); + var $rows = $diffCode.find('tr'); + var rowTip = $('#rowTip').html(); + var lastLine; + $rows.each(function() + { + var $row = $(this); + if(!$row.hasClass('empty')) + { + $row.children('th').first().prepend("
"); + $row.children('td').first().append(rowTip); + } + + if(lastLine && !$row.data('line')) + { + $row.attr('data-line', lastLine); + } + else + { + lastLine = $row.data('line'); + } + }).hover(function() + { + var $this = $(this); + if($this.hasClass('empty')) return; + $this.addClass("over"); + }, + function() + { + $(this).removeClass("over"); + }); + + var isInline = $.cookie('arrange') == 'inline'; + var $bugFormRow = $('' + (isInline ? '' : '') + ''); + var $bugForm = $('#bugForm'); + var $commentCell = $('#commentCell'); + var $bugPanel = $('#bugPanel'); +// $bugForm.find('input[name="begin"], input[name="end"]').attr('max', lastLine); + $bugFormRow.find('td').append($bugForm.removeClass('hide')); + + var highlight = function($e) + { + $('.highlight').removeClass('highlight'); + $e.addClass('highlight'); + }; + + var createComment = function(comment, $comments) + { + console.log(); + var $comment = $commentCell.clone() + .removeClass('hide') + .attr('id', 'comment-' + comment.id) + .attr('data-comment', comment.id); + $comment.find('.realname').text(comment.realname); + $comment.find('.comment-content').text(comment.comment); + $comment.find('.date').text(comment.date); + $comment.find('.edit').toggle(comment.edit); + $comment.find('.comment-edit-form').attr('action', createLink('repo', 'editComment', 'commentID=' + comment.id)); + + if($comments) + { + if(typeof $comments !== 'object') $comments = $('#bug-' + $comments + ' .comments'); + ($comments.hasClass('comments') ? $comments : $comments.find('.comments')).append($comment); + } + + return $comment; + }; + + var createBug = function(bug, line, $commentRow, show) + { + var commentCount, j; + var $bug = $bugPanel.clone().removeClass('hide').attr('id', 'bug-' + bug.id).attr('data-bug', bug.id); + $bug.find('.bugid').text(bug.id); + $bug.find('.realname').text(bug.realname); + $bug.find('.openedDate').text(bug.openedDate); + $bug.find('.title').text(bug.title); + $bug.find('.steps').toggle(bug.steps != '').html(bug.steps); + $bug.find('.edit').toggle(bug.edit); + $bug.find('.code-lines').text(bug.lines); + $bug.find('.delete').toggle(bug.delete); + $bug.find('input[name="objectID"]').val(bug.id); + $bug.find('.bug-edit-form').attr('action', createLink('repo', 'editBug', 'bugID=' + bug.id)); + $bug.find('a.view-bug').attr('href', createLink('bug', 'view', "bugID=" + bug.id)); + $bug.data('data', bug); + $bug.toggleClass('show', show > 1); + if(show > 2) highlight($bug); + + if(bug.comments) + { + commentCount = bug.comments.length; + $bugComments = $bug.find('.comments'); + for(j = 0; j < commentCount; j++) + { + createComment(bug.comments[j], $bugComments); + } + } + + if(!line && bug.line) line = bug.line; + if(line) + { + if(!$commentRow) + { + var $row = $rows.filter('[data-line="' + line + '"]').last(); + $commentRow = $row.next('tr'); + if(!$commentRow.hasClass('comment-row')) + { + $commentRow = $('' + (isInline ? '
' : '
') + ''); + $row.addClass('commented').after($commentRow); + } + } + ($commentRow.hasClass('comment-list') ? $commentRow : $commentRow.find('.comment-list')).append($bug); + + if(show && $commentRow.hasClass('comment-row')) $commentRow.addClass('show'); + } + + return $bug; + }; + + var toggleComment = function($row, show) + { + var $commentRow; + if($row.hasClass('comment-row')) + { + $commentRow = $row; + $row = $commentRow.prev('tr'); + if($row.hasClass('action-row')) + { + $row = $row.prev('tr'); + } + } + else + { + $commentRow = $row.next('tr'); + if($commentRow.hasClass('action-row')) + { + $commentRow = $commentRow.next('tr'); + } + } + if(show === undefined) + { + show = !$row.hasClass('open'); + } + if($row.hasClass('commented') && $commentRow.hasClass('comment-row')) + { + $commentRow.toggleClass('show', show); + $row.toggleClass('open', show); + } + }; + + $diffCode.on('click', '.comment-btn', function(e) + { + $rows.removeClass('selected'); + var $row = $(this).closest('tr'); + if($diffCode.hasClass('with-action-row') && $row.hasClass('with-action-row')) + { + $diffCode.removeClass('with-action-row'); + } + else + { + $diffCode.addClass('with-action-row'); + var line = $row.data('line'); + if(!$row.hasClass('with-action-row')) + { + $rows.removeClass('with-action-row') + $row.addClass('with-action-row'); + + $bugForm.find('input[name="begin"]').val(line); + $bugForm.find('input[name="end"]').attr('min', line).val(line); + $bugForm.find('select#assignedTo').val(blamePairs[line]); + $bugForm.find('select#assignedTo').trigger("chosen:updated"); + + $row.after($bugFormRow); + + KindEditor.remove('#commentText'); + $('#commentText').kindeditor(); + + var getCommiterLink = createLink('repo', 'ajaxgetcommitter', 'repoID=' + repoID + "&entry=" + file + "&revision=" + revision + "&line=" + line); + var connector = getCommiterLink.indexOf('&') >= 0 ? '&' : '?'; + getCommiterLink = getCommiterLink + connector + 'entry=' + file; + $.ajax({url: getCommiterLink}).done(function(responseText) + { + $bugForm.find('#assignedTo').val(responseText).trigger("chosen:updated"); + }); + } + highlight($bugForm); + $bugForm.find('input[name="title"]').focus(); + $row.addClass('selected'); + } + e.stopPropagation(); + }).on('click', '.bugCancel', function() + { + $rows.removeClass('selected'); + $diffCode.removeClass('with-action-row'); + }).on('click', '.bugEdit', function(e) + { + var $panelBug = $(this).closest('.panel-bug'); + + if($panelBug.hasClass('show-edit-form')) + { + $panelBug.removeClass('show-edit-form'); + e.stopPropagation(); + return; + } + + $panelBug.addClass('show show-edit-form').find('input[name="commentText"]').val($panelBug.find('.title').first().text()).focus(); + e.stopPropagation(); + return false; + }).on('submit', '.bug-edit-form', function() + { + var $form = $(this); + $(this).ajaxSubmit( + { + success:function(text) + { + var $bug = $form.closest('.panel-bug'); + $bug.find('.title').text(text); + $bug.removeClass('show-edit-form'); + }, + beforeSubmit:function(formData, jqForm) + { + var form = jqForm[0]; + if(!form.commentText.value) + { + alert(contentError); + return false; + } + } + }); + return false; + }).on('click', '.bugEditCancel', function() + { + $(this).closest('.panel-bug').removeClass('show-edit-form'); + }).on('click', '.bugDelete', function(e) + { + var $bug = $(this).closest('.panel-bug'); + if(!$bug.length) return; + + if(confirm(confirmDelete)) + { + var link = createLink('repo', 'deleteBug', 'bugID=' + $bug.data('bug') + '&confirm=yes'); + $.get(link, function(data) + { + if(data == 'deleted') + { + var $commentRow = $bug.closest('.comment-row'); + if($commentRow.find('.panel-bug').length === 1) + { + $commentRow.removeClass('show').prev('tr').removeClass('commented'); + } + $bug.remove(); + } + }); + } + e.stopPropagation(); + return false; + }).on('click', '.addComment', function() + { + $(this).closest('.panel-bug').addClass('show-form').find('.commentForm textarea').focus(); + }).on('click', '.commentCancel', function() + { + $(this).closest('.panel-bug').removeClass('show-form'); + }).on('submit', '.commentForm', function() + { + var $form = $(this); + $form.ajaxSubmit( + { + success:function(json) + { + var $panelBug = $form.closest('.panel-bug'); + $form.find('textarea').val(''); + $panelBug.removeClass('show-form'); + createComment($.parseJSON(json), $panelBug.data('bug')); + }, + beforeSubmit:function(formData, jqForm) + { + var form = jqForm[0]; + if(!form.comment.value) + { + alert(commentError); + return false; + } + } + }); + return false; + }).on('click', '.commentEdit', function() + { + var $comment = $(this).closest('.comment'); + + if($comment.hasClass('show-form')) + { + $comment.removeClass('show-form'); + return; + } + $comment.addClass('show-form').find('textarea').val($comment.find('.comment-content').text()).focus(); + }).on('click', '.commentEditCancel', function() + { + $(this).closest('.comment').removeClass('show-form'); + }).on('submit', '.comment-edit-form', function() + { + var $form = $(this); + $form.ajaxSubmit( + { + success:function(html) + { + var $comment = $form.closest('.comment'); + $comment.find('.comment-content').html(html); + $comment.removeClass('show-form'); + }, + beforeSubmit:function(formData, jqForm) + { + var form = jqForm[0]; + if(!form.commentText.value) + { + alert(contentError); + return false; + } + } + }); + return false; + }).on('click', '.commentDelete', function() + { + var $container = $(this).closest('.commentContainer'); + if(!$container.length) return; + + if(confirm(confirmDeleteComment)) + { + var commentID = $container.data('comment'); + var link = createLink('repo', 'deleteComment', 'commentID=' + commentID + '&confirm=yes'); + + $.get(link, function(data) + { + if(data == 'deleted') + { + var $commentRow = $container.closest('.comment-row'); + if($commentRow.find('.bugContainer, .commentContainer').length === 1) + { + $commentRow.removeClass('show').prev('tr').removeClass('commented'); + } + $container.remove(); + } + }); + } + return false; + }).on('click', 'tr.commented', function() + { + toggleComment($(this)); + }).on('click', '.panel-bug > .panel-heading', function() + { + $(this).closest('.panel-bug').toggleClass('show'); + }).on('mouseenter', 'tr.commented td .preview-icon', function(e) + { + var $cell = $(this).closest('td'); + var $row = $cell.closest('tr'); + var $commentRow = $row.next('tr'); + + var $bugs = $commentRow.find('.panel-bug'), line = '?'; + $bugsPreviewMenu.children('li:not(.dropdown-header)').remove(); + $bugsPreviewMenu.find('.bug-count').text($bugs.length); + $bugsPreviewMenu.find('.comment-count').text($commentRow.find('.comment').length); + $bugs.each(function() + { + var bug = $(this).data('data'); + line = bug.line; + $bugsPreviewMenu.append('
  • #' + bug.id + ' ' + bug.title + '
  • '); + }); + $bugsPreviewMenu.find('.code-line').text(line); + + $bugsPreview.prependTo($cell); + clearTimeout(hidePreview); + $bugsPreviewMenu.css({top: 0-$bugsPreviewMenu.outerHeight(), left: Math.max(0, e.offsetX-$bugsPreviewMenu.outerWidth())}).addClass('show'); + setTimeout(function(){$bugsPreviewMenu.addClass('in');}, 50); + }).on('mouseleave', 'tr.commented td', function() + { + $bugsPreviewMenu.removeClass('in'); + hidePreview = setTimeout(function(){$bugsPreviewMenu.removeClass('show');}, 200); + }); + + $bugsPreviewMenu.on('click', 'li', function(e) + { + var $bug = $($(this).find('a').data('id')); + if($bug.length) + { + $bug.addClass('show'); + toggleComment($bug.closest('tr.comment-row'), true); + highlight($bug); + + $bugsPreviewMenu.removeClass('in'); + hidePreview = setTimeout(function(){$bugsPreviewMenu.removeClass('show');}, 200); + } + e.stopPropagation(); + }); + + $bugForm.submit(function() + { + $(this).ajaxSubmit( + { + success:function(json) + { + json = $.parseJSON(json); + if(json.result == 'fail') + { + alert(json.message); + return false; + } + + createBug(json, null, null, 3); + $diffCode.removeClass('with-action-row'); + $diffCode.find('tr.with-action-row.selected').removeClass('selected'); + $bugForm.find('#title').val(''); + KindEditor.html('#commentText', ''); + }, + beforeSubmit:function(formData, jqForm) + { + var form = jqForm[0]; + if(!form.product.value) + { + alert(productError); + return false; + } + if(!form.title.value) + { + alert(titleError); + $bugForm.find('input[name="title"]').focus(); + return false; + } + } + }); + return false; + }).on('change', 'input[name="begin"]', function() + { + var begin = $(this).val(); + var $end = $bugForm.find('input[name="end"]').attr('min', begin); + if(parseInt($end.val()) < parseInt(begin)) + { + $end.val(begin); + } + }); + + if(bugs) + { + var lineBugs, bugsCount, i; + for(var line in bugs) + { + if(line) + { + lineBugs = bugs[line]; + bugsCount = lineBugs.length; + + for(i = 0; i < bugsCount; i++) + { + createBug(lineBugs[i], line); + } + } + } + } + + setTimeout(anchor, 200); + + $(document).on('click', function() + { + $('.highlight').removeClass('highlight'); + }); + + function anchor() + { + var hash = window.location.hash; + if(hash) + { + var line = hash.substr(1).replace('L', ''); + var $row = $('.diff tr[data-line="' + line +'"]').first(); + if($row.length) + { + var anchor = $row.offset().top; + + $('body,html').animate({scrollTop:anchor - 50}, 500); + + $row.addClass('highlight'); + if($row.hasClass('commented')) + { + toggleComment($row, true); + var $commentRow = $row.next('tr'); + if($commentRow.hasClass('comment-row')) + { + $commentRow.addClass('highlight'); + } + } + } + } + } +}); + function changeEncoding(encoding) { $('#encoding').val(encoding); diff --git a/module/repo/js/review.js b/module/repo/js/review.js new file mode 100644 index 0000000000..2de7ce4272 --- /dev/null +++ b/module/repo/js/review.js @@ -0,0 +1,4 @@ +$(document).ready(function() +{ + $('#' + browseType + 'Tab').addClass('btn-active-text'); +}); diff --git a/module/repo/js/view.js b/module/repo/js/view.js index ae6b0bea3e..69f5a21275 100644 --- a/module/repo/js/view.js +++ b/module/repo/js/view.js @@ -1,8 +1,15 @@ $(document).ready(function() { + var $bugFormRow = $(''); + var $bugForm = $('#bugForm'); + $bugFormRow.find('td').append($bugForm.removeClass('hide')); + var $bugPanel = $('#bugPanel'); + var $commentCell = $('#commentCell'); var $pre = $('.repoCode .content pre'); - var rowTip = ''; - if($('#rowTip').length > 0) rowTip = $('#rowTip').html(); + var $bugsPreview = $('#bugsPreview'); + var hidePreview; + var $bugsPreviewMenu = $('#bugsPreview').children('.dropdown-menu'); + var rowTip = $('#rowTip').html(); if($pre.length) { @@ -13,8 +20,472 @@ $(document).ready(function() for(var i = 0 ; i < arr.length ; i++) { line = i + 1; - code += "
    " + line + "" + (arr[i] || ' ') + rowTip + ""; + code += "
    " + line + "" + (arr[i] || ' ') + rowTip + ""; } + $bugForm.find('input[name="begin"], input[name="end"]').attr('max', line); $pre.html("" + code + "
    "); } + + var $rows = $pre.find('tr'); + var highlight = function($e) + { + $('.highlight').removeClass('highlight'); + $e.addClass('highlight'); + }; + + var createComment = function(comment, $comments) + { + var $comment = $commentCell.clone() + .removeClass('hide') + .attr('id', 'comment-' + comment.id) + .attr('data-comment', comment.id); + $comment.find('.realname').text(comment.realname); + $comment.find('.comment-content').text(comment.comment); + $comment.find('.date').text(comment.date); + $comment.find('.edit').toggle(comment.edit); + $comment.find('.comment-edit-form').attr('action', createLink('repo', 'editComment', 'commentID=' + comment.id)); + + if($comments) + { + if(typeof $comments !== 'object') $comments = $('#bug-' + $comments + ' .comments'); + ($comments.hasClass('comments') ? $comments : $comments.find('.comments')).append($comment); + } + + return $comment; + }; + + var createBug = function(bug, line, $commentRow, show) + { + var commentCount, j; + var $bug = $bugPanel.clone().removeClass('hide').attr('id', 'bug-' + bug.id).attr('data-bug', bug.id); + $bug.find('.bugid').text(bug.id); + $bug.find('.realname').text(bug.realname); + $bug.find('.openedDate').text(bug.openedDate); + $bug.find('.title').text(bug.title); + $bug.find('.steps').toggle(bug.steps != '').html(bug.steps); + $bug.find('.edit').toggle(bug.edit); + $bug.find('.code-lines').text(bug.lines); + $bug.find('.delete').toggle(bug.delete); + $bug.find('input[name="objectID"]').val(bug.id); + $bug.find('.bug-edit-form').attr('action', createLink('repo', 'editBug', 'bugID=' + bug.id)); + $bug.find('a.view-bug').attr('href', createLink('bug', 'view', "bugID=" + bug.id)); + $bug.data('data', bug); + $bug.toggleClass('show', show > 1); + if(show > 2) highlight($bug); + + if(bug.comments) + { + commentCount = bug.comments.length; + $bugComments = $bug.find('.comments'); + for(j = 0; j < commentCount; j++) + { + createComment(bug.comments[j], $bugComments); + } + } + + if(!line && bug.line) line = bug.line; + if(line) + { + var $row = $rows.filter('[data-line="' + line + '"]').first(); + if(!$commentRow) + { + $commentRow = $row.next('tr'); + if(!$commentRow.hasClass('comment-row')) + { + $commentRow = $('
    '); + $row.after($commentRow); + } + } + $row.addClass('commented'); + ($commentRow.hasClass('comment-list') ? $commentRow : $commentRow.find('.comment-list')).append($bug); + + if(show && $commentRow.hasClass('comment-row')) $commentRow.addClass('show'); + } + + return $bug; + }; + + var toggleComment = function($row, show) + { + var $commentRow; + if($row.hasClass('comment-row')) + { + $commentRow = $row; + $row = $commentRow.prev('tr'); + if($row.hasClass('action-row')) + { + $row = $row.prev('tr'); + } + } + else + { + $commentRow = $row.next('tr'); + if($commentRow.hasClass('action-row')) + { + $commentRow = $commentRow.next('tr'); + } + } + if(show === undefined) + { + show = !$row.hasClass('open'); + } + if($row.hasClass('commented') && $commentRow.hasClass('comment-row')) + { + $commentRow.toggleClass('show', show); + $row.toggleClass('open', show); + } + }; + + $rows.hover(function() + { + var $this = $(this); + $this.addClass("over"); + }, + function() + { + $(this).removeClass("over"); + }); + + $pre.on('click', '.comment-btn', function(e) + { + $rows.removeClass('selected'); + var $row = $(this).closest('tr'); + if($pre.hasClass('with-action-row') && $row.hasClass('with-action-row')) + { + $pre.removeClass('with-action-row'); + } + else + { + $pre.addClass('with-action-row'); + var line = $row.data('line'); + if(!$row.hasClass('with-action-row')) + { + $rows.removeClass('with-action-row') + $row.addClass('with-action-row'); + + $bugForm.find('input[name="begin"]').val(line); + $bugForm.find('input[name="end"]').attr('min', line).val(line); + $bugForm.find('select#assignedTo').val(blamePairs[line]); + $bugForm.find('select#assignedTo').trigger("chosen:updated"); + + var $nextRow = $row.next('tr'); + ($nextRow.hasClass('comment-row') ? $nextRow : $row).after($bugFormRow); + + KindEditor.remove('#commentText'); + $('#commentText').kindeditor(); + + var getCommiterLink = createLink('repo', 'ajaxgetcommitter', 'repoID=' + repoID + "&entry=&revision=" + revision + "&line=" + line); + var connector = getCommiterLink.indexOf('&') >= 0 ? '&' : '?'; + getCommiterLink = getCommiterLink + connector + 'entry=' + file; + $.ajax({url: getCommiterLink}).done(function(responseText) + { + $bugForm.find('#assignedTo').val(responseText).trigger("chosen:updated"); + }); + } + $row.addClass('selected'); + + highlight($bugForm); + $bugForm.find('input[name="title"]').focus(); + } + + e.stopPropagation(); + }).on('click', '.bugCancel', function() + { + $rows.removeClass('selected'); + $pre.removeClass('with-action-row'); + }).on('click', '.bugEdit', function(e) + { + var $panelBug = $(this).closest('.panel-bug'); + + if($panelBug.hasClass('show-edit-form')) + { + $panelBug.removeClass('show-edit-form'); + e.stopPropagation(); + return; + } + + $panelBug.addClass('show show-edit-form').find('input[name="commentText"]').val($panelBug.find('.title').first().text()).focus(); + e.stopPropagation(); + return false; + }).on('submit', '.bug-edit-form', function() + { + var $form = $(this); + $(this).ajaxSubmit( + { + success:function(text) + { + var $bug = $form.closest('.panel-bug'); + $bug.find('.title').text(text); + $bug.removeClass('show-edit-form'); + }, + beforeSubmit:function(formData, jqForm) + { + var form = jqForm[0]; + if(!form.commentText.value) + { + alert(contentError); + return false; + } + } + }); + return false; + }).on('click', '.bugEditCancel', function() + { + $(this).closest('.panel-bug').removeClass('show-edit-form'); + }).on('click', '.bugDelete', function(e) + { + var $bug = $(this).closest('.panel-bug'); + if(!$bug.length) return; + + if(confirm(confirmDelete)) + { + var link = createLink('repo', 'deleteBug', 'bugID=' + $bug.data('bug') + '&confirm=yes'); + $.get(link, function(data) + { + if(data == 'deleted') + { + var $commentRow = $bug.closest('.comment-row'); + if($commentRow.find('.panel-bug').length === 1) + { + $commentRow.removeClass('show').prev('tr').removeClass('commented'); + } + $bug.remove(); + } + }); + } + e.stopPropagation(); + return false; + }).on('click', '.addComment', function() + { + $(this).closest('.panel-bug').addClass('show-form').find('.commentForm textarea').focus(); + }).on('click', '.commentCancel', function() + { + $(this).closest('.panel-bug').removeClass('show-form'); + }).on('submit', '.commentForm', function() + { + var $form = $(this); + $form.ajaxSubmit( + { + success:function(json) + { + var $panelBug = $form.closest('.panel-bug'); + $form.find('textarea').val(''); + $panelBug.removeClass('show-form'); + createComment($.parseJSON(json), $panelBug.data('bug')); + }, + beforeSubmit:function(formData, jqForm) + { + var form = jqForm[0]; + if(!form.comment.value) + { + alert(commentError); + return false; + } + } + }); + return false; + }).on('click', '.commentEdit', function() + { + var $comment = $(this).closest('.comment'); + + if($comment.hasClass('show-form')) + { + $comment.removeClass('show-form'); + return; + } + $comment.addClass('show-form').find('textarea').val($comment.find('.comment-content').text()).focus(); + }).on('click', '.commentEditCancel', function() + { + $(this).closest('.comment').removeClass('show-form'); + }).on('submit', '.comment-edit-form', function() + { + var $form = $(this); + $form.ajaxSubmit( + { + success:function(html) + { + var $comment = $form.closest('.comment'); + $comment.find('.comment-content').html(html); + $comment.removeClass('show-form'); + }, + beforeSubmit:function(formData, jqForm) + { + var form = jqForm[0]; + if(!form.commentText.value) + { + alert(contentError); + return false; + } + } + }); + return false; + }).on('click', '.commentDelete', function() + { + var $container = $(this).closest('.commentContainer'); + if(!$container.length) return; + + if(confirm(confirmDeleteComment)) + { + var commentID = $container.data('comment'); + var link = createLink('repo', 'deleteComment', 'commentID=' + commentID + '&confirm=yes'); + + $.get(link, function(data) + { + if(data == 'deleted') + { + var $commentRow = $container.closest('.comment-row'); + if($commentRow.find('.bugContainer, .commentContainer').length === 1) + { + $commentRow.removeClass('show').prev('tr').removeClass('commented'); + } + $container.remove(); + } + }); + } + return false; + }).on('click', 'tr.commented', function() + { + toggleComment($(this)); + }).on('click', '.panel-bug > .panel-heading', function() + { + $(this).closest('.panel-bug').toggleClass('show'); + }).on('mouseenter', 'tr.commented td .preview-icon', function(e) + { + var $cell = $(this).closest('td'); + var $row = $cell.closest('tr'); + var $commentRow = $row.next('tr'); + + var $bugs = $commentRow.find('.panel-bug'), line = '?'; + $bugsPreviewMenu.children('li:not(.dropdown-header)').remove(); + $bugsPreviewMenu.find('.bug-count').text($bugs.length); + $bugsPreviewMenu.find('.comment-count').text($commentRow.find('.comment').length); + $bugs.each(function() + { + var bug = $(this).data('data'); + line = bug.line; + $bugsPreviewMenu.append('
  • #' + bug.id + ' ' + bug.title + '
  • '); + }); + $bugsPreviewMenu.find('.code-line').text(line); + + $bugsPreview.prependTo($cell); + clearTimeout(hidePreview); + $bugsPreviewMenu.css({top: 0-$bugsPreviewMenu.outerHeight(), left: Math.max(0, e.offsetX-$bugsPreviewMenu.outerWidth())}).addClass('show'); + setTimeout(function(){$bugsPreviewMenu.addClass('in');}, 50); + }).on('mouseleave', 'tr.commented td', function() + { + $bugsPreviewMenu.removeClass('in'); + hidePreview = setTimeout(function(){$bugsPreviewMenu.removeClass('show');}, 200); + }); + + $bugsPreviewMenu.on('click', 'li', function(e) + { + var $bug = $($(this).find('a').data('id')); + if($bug.length) + { + + $bug.addClass('show'); + toggleComment($bug.closest('tr.comment-row'), true); + highlight($bug); + + $bugsPreviewMenu.removeClass('in'); + hidePreview = setTimeout(function(){$bugsPreviewMenu.removeClass('show');}, 200); + } + e.stopPropagation(); + }); + + $bugForm.submit(function() + { + $(this).ajaxSubmit( + { + success:function(json) + { + json = $.parseJSON(json); + if(json.result == 'fail') + { + alert(json.message); + return false; + } + + createBug(json, null, null, 3); + $pre.removeClass('with-action-row'); + $pre.find('tr.with-action-row.selected').removeClass('selected'); + $bugForm.find('#title').val(''); + KindEditor.html('#commentText', ''); + }, + beforeSubmit:function(formData, jqForm) + { + var form = jqForm[0]; + if(!form.product.value) + { + alert(productError); + return false; + } + if(!form.title.value) + { + alert(titleError); + $bugForm.find('input[name="title"]').focus(); + return false; + } + } + }); + return false; + }).on('change', 'input[name="begin"]', function() + { + var begin = $(this).val(); + var $end = $bugForm.find('input[name="end"]').attr('min', begin); + if(parseInt($end.val()) < parseInt(begin)) $end.val(begin); + $bugForm.find('select#assignedTo').val(blamePairs[begin]); + $bugForm.find('select#assignedTo').trigger("chosen:updated"); + }); + + if(bugs) + { + var lineBugs, bugsCount, i; + for(var line in bugs) + { + if(line) + { + lineBugs = bugs[line]; + bugsCount = lineBugs.length; + + for(i = 0; i < bugsCount; i++) + { + createBug(lineBugs[i], line); + } + } + } + } + + setTimeout(anchor, 200); + + $(document).on('click', function() + { + $('.highlight').removeClass('highlight'); + }); + + function anchor() + { + var hash = window.location.hash; + if(hash) + { + var $row = $(hash).closest('tr'); + if($row.length) + { + var anchor = $row.offset().top; + + $('body,html').animate({scrollTop:anchor - 50}, 500); + + $row.addClass('highlight'); + if($row.hasClass('commented')) + { + var $commentRow = $row.next('tr'); + if($commentRow.hasClass('comment-row')) + { + toggleComment($row, true); + $commentRow.addClass('highlight').find('.panel-bug').first().addClass('show'); + } + } + } + } + } }); diff --git a/module/repo/model.php b/module/repo/model.php index 2daf855e04..d543765da7 100644 --- a/module/repo/model.php +++ b/module/repo/model.php @@ -121,7 +121,7 @@ class repoModel extends model foreach($repos as $id => $name) { $class = $repoID == $id ? "class='active'" : ''; - $selectHtml .= html::a(helper::createLink('repo', 'log', "repoID={$id}"), $name, '', $class); + $selectHtml .= html::a(helper::createLink('repo', 'browse', "repoID={$id}"), $name, '', $class); } $selectHtml .= ""; @@ -229,12 +229,8 @@ class repoModel extends model { $historyIdList = $this->dao->select('DISTINCT t2.id')->from(TABLE_REPOFILES)->alias('t1') ->leftJoin(TABLE_REPOHISTORY)->alias('t2')->on('t1.revision=t2.id') - ->leftJoin(TABLE_REPOBRANCH)->alias('t3')->on('t2.id=t3.revision') ->where('1=1') ->andWhere('t1.repo')->eq($repo->id) - ->andWhere('t2.`time`')->le($revisionTime) - ->andWhere('left(t2.comment, 12)')->ne('Merge branch') - ->beginIF($this->cookie->repoBranch)->andWhere('t3.branch')->eq($this->cookie->repoBranch)->fi() ->beginIF($type == 'dir') ->andWhere('t1.parent', true)->like(rtrim($entry, '/') . "/%") ->orWhere('t1.parent')->eq(rtrim($entry, '/')) @@ -242,7 +238,7 @@ class repoModel extends model ->fi() ->beginIF($type == 'file')->andWhere('t1.path')->eq("$entry")->fi() ->orderBy('t2.`time` desc') - ->page($pager, 't2.id') + ->page($pager) ->fetchPairs('id', 'id'); } @@ -295,6 +291,145 @@ class repoModel extends model return $lastComment; } + /** + * Get revisions from db. + * + * @param int $repoID + * @param string $limit + * @param string $maxRevision + * @param string $minRevision + * @access public + * @return array + */ + public function getRevisionsFromDB($repoID, $limit = '', $maxRevision = '', $minRevision = '') + { + $revisions = $this->dao->select('DISTINCT t1.*')->from(TABLE_REPOHISTORY)->alias('t1') + ->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision') + ->where('t1.repo')->eq($repoID) + ->beginIF(!empty($maxRevision))->andWhere('t1.revision')->le($maxRevision)->fi() + ->beginIF(!empty($minRevision))->andWhere('t1.revision')->ge($minRevision)->fi() + ->beginIF($this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi() + ->orderBy('t1.revision desc') + ->beginIF(!empty($limit))->limit($limit)->fi() + ->fetchAll('revision'); + $commiters = $this->loadModel('user')->getCommiters(); + foreach($revisions as $revision) + { + $revision->comment = $this->replaceCommentLink($revision->comment); + $revision->committer = isset($commiters[$revision->committer]) ? $commiters[$revision->committer] : $revision->committer; + } + return $revisions; + } + + /** + * Get history. + * + * @param int $repoID + * @param array $revisions + * @access public + * @return array + */ + public function getHistory($repoID, $revisions) + { + return $this->dao->select('DISTINCT t1.*')->from(TABLE_REPOHISTORY)->alias('t1') + ->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision') + ->where('t1.repo')->eq($repoID) + ->andWhere('t1.revision')->in($revisions) + ->beginIF($this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi() + ->fetchAll('revision'); + } + + /** + * Get review. + * + * @param int $repoID + * @param string $entry + * @param string $revision + * @access public + * @return array + */ + public function getReview($repoID, $entry, $revision) + { + $reviews = array(); + $bugs = $this->dao->select('t1.*, t2.realname')->from(TABLE_BUG)->alias('t1') + ->leftJoin(TABLE_USER)->alias('t2') + ->on('t1.openedBy = t2.account') + ->where('t1.repo')->eq($repoID) + ->andWhere('t1.entry')->eq($entry) + ->andWhere('t1.v2')->eq($revision) + ->andWhere('t1.deleted')->eq(0) + ->fetchAll('id'); + $comments = $this->dao->select('t1.*, t2.realname')->from(TABLE_ACTION)->alias('t1') + ->leftJoin(TABLE_USER)->alias('t2') + ->on('t1.actor = t2.account') + ->where('t1.objectType')->eq('bug') + ->andWhere('t1.objectID')->in(array_keys($bugs)) + ->andWhere('t1.action')->eq('commented') + ->fetchGroup('objectID', 'id'); + foreach($bugs as $bug) + { + if(common::hasPriv('bug', 'edit')) $bug->edit = true; + if(common::hasPriv('bug', 'delete')) $bug->delete = true; + $lines = explode(',', trim($bug->lines, ',')); + $line = $lines[0]; + $reviews[$line]['bugs'][$bug->id] = $bug; + + if(isset($comments[$bug->id])) + { + foreach($comments[$bug->id] as $key => $comment) + { + if($comment->actor == $this->app->user->account) $comment->edit = true; + } + $reviews[$line]['comments'] = $comments; + } + } + + return $reviews; + } + + /** + * Get bugs by repo. + * + * @param int $repoID + * @param string $browseType + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getBugsByRepo($repoID, $browseType, $orderBy, $pager) + { + $bugs = $this->dao->select('*')->from(TABLE_BUG) + ->where('repo')->eq($repoID) + ->andWhere('deleted')->eq('0') + ->beginIF($browseType == 'assigntome')->andWhere('assignedTo')->eq($this->app->user->account)->fi() + ->beginIF($browseType == 'openedbyme')->andWhere('openedBy')->eq($this->app->user->account)->fi() + ->beginIF($browseType == 'resolvedbyme')->andWhere('resolvedBy')->eq($this->app->user->account)->fi() + ->beginIF($browseType == 'assigntonull')->andWhere('assignedTo')->eq('')->fi() + ->beginIF($browseType == 'unresolved')->andWhere('resolvedBy')->eq('')->fi() + ->beginIF($browseType == 'unclosed')->andWhere('status')->ne('closed')->fi() + ->orderBy($orderBy) + ->page($pager) + ->fetchAll(); + return $bugs; + } + + /** + * Get project pairs. + * + * @param int $product + * @param int $branch + * @access public + * @return array + */ + public function getProjectPairs($product, $branch = 0) + { + $pairs = array(); + $projects = $this->loadModel('project')->getList('undone', 0, $product, $branch); + foreach($projects as $project) $pairs[$project->id] = $project->name; + return $pairs; + } + /** * Get git revisionName. * @@ -430,6 +565,49 @@ class repoModel extends model return $count; } + /** + * Save bug. + * + * @param int $repoID + * @param string $file + * @param int $v1 + * @param int $v2 + * @access public + * @return array + */ + public function saveBug($repoID, $file, $v1, $v2) + { + $now = helper::now(); + $data = fixer::input('post') + ->add('severity', 3) + ->add('openedBy', $this->app->user->account) + ->add('openedDate', $now) + ->add('openedBuild', 'trunk') + ->add('assignedDate', $now) + ->add('type', 'codeimprovement') + ->add('repo', $repoID) + ->add('entry', $file) + ->add('lines', $this->post->begin . ',' . $this->post->end) + ->add('v1', $v1) + ->add('v2', $v2) + ->remove('commentText,begin,end,uid') + ->get(); + + $data->steps = $this->loadModel('file')->pasteImage($this->post->commentText, $this->post->uid); + $this->dao->insert(TABLE_BUG)->data($data)->exec(); + + if(!dao::isError()) + { + $bugID = $this->dao->lastInsertID(); + $this->file->updateObjectID($this->post->uid, $bugID, 'bug'); + setcookie("repoPairs[$repoID]", $data->product); + + return array('result' => 'success', 'id' => $bugID, 'realname' => $this->app->user->realname, 'openedDate' => substr($now, 5, 11), 'edit' => true, 'delete' => true, 'lines' => $data->lines, 'line' => $this->post->begin, 'steps' => $data->steps, 'title' => $data->title); + } + + return array('result' => 'fail', 'message' => join("\n", dao::getError())); + } + /** * Save exists log branch. * @@ -496,6 +674,75 @@ class repoModel extends model $this->dao->update(TABLE_REPO)->set('lastSync')->eq(helper::now())->where('id')->eq($repoID)->exec(); } + /** + * Update bug. + * + * @param int $bugID + * @param string $title + * @access public + * @return string + */ + public function updateBug($bugID, $title) + { + $this->dao->update(TABLE_BUG)->set('title')->eq($title)->where('id')->eq($bugID)->exec(); + return $title; + } + + /** + * Update comment. + * + * @param int $commentID + * @param string $comment + * @access public + * @return string + */ + public function updateComment($commentID, $comment) + { + $this->dao->update(TABLE_ACTION)->set('comment')->eq($comment)->where('id')->eq($commentID)->exec(); + return $comment; + } + + /** + * Delete comment. + * + * @param int $commentID + * @access public + * @return void + */ + public function deleteComment($commentID) + { + return $this->dao->delete()->from(TABLE_ACTION)->where('id')->eq($commentID)->exec(); + } + + /** + * Get cache file. + * + * @param int $repoID + * @param string $path + * @param int $revision + * @access public + * @return string + */ + public function getCacheFile($repoID, $path, $revision) + { + $cachePath = $this->app->getCacheRoot() . '/' . 'repo'; + if(!is_dir($cachePath)) mkdir($cachePath, 0777, true); + if(!is_writable($cachePath)) return false; + return $cachePath . '/' . $repoID . '-' . md5("{$this->cookie->repoBranch}-$path-$revision"); + } + + /** + * Get last review info. + * + * @param string $entry + * @access public + * @return object + */ + public function getLastReviewInfo($entry) + { + return $this->dao->select('*')->from(TABLE_BUG)->where('entry')->eq($entry)->orderby('id_desc')->fetch(); + } + /** * Get pre and next revision. * diff --git a/module/repo/view/ajaxsidelogs.html.php b/module/repo/view/ajaxsidelogs.html.php index a2102967f0..316ce92eba 100644 --- a/module/repo/view/ajaxsidelogs.html.php +++ b/module/repo/view/ajaxsidelogs.html.php @@ -51,7 +51,7 @@ if(isset($entry)) $pathInfo .= '&type=file';