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 = $('');
+ $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("");
}
+
+ 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';