Unify files/history/etc into one box (bug 651151-2)

This commit is contained in:
Gregory Koberger 2011-05-26 14:13:13 -07:00
Родитель 6f175b5113
Коммит 2d71e533bd
12 изменённых файлов: 414 добавлений и 176 удалений

Просмотреть файл

@ -232,6 +232,14 @@ class REQUEST_SUPER_REVIEW(_LOG):
review_queue = True
class COMMENT_VERSION(_LOG):
id = 49
action_class = None
format = _(u'Comment on {addon} {version}.')
keep = True
review_queue = True
class ADD_TAG(_LOG):
id = 25
action_class = 'tag'
@ -392,9 +400,11 @@ def log(action, *args, **kw):
e.g. amo.log(amo.LOG.CREATE_ADDON, []),
amo.log(amo.LOG.ADD_FILE_TO_VERSION, file, version)
"""
from devhub.models import ActivityLog, AddonLog, UserLog, CommentLog
from devhub.models import (ActivityLog, AddonLog, UserLog,
CommentLog, VersionLog)
from addons.models import Addon
from users.models import UserProfile
from versions.models import Version
from amo import get_user, logger_log
user = kw.get('user', get_user())
@ -422,11 +432,15 @@ def log(action, *args, **kw):
if isinstance(arg, tuple):
if arg[0] == Addon:
AddonLog(addon_id=arg[1], activity_log=al).save()
elif arg[0] == Version:
VersionLog(version_id=arg[1], activity_log=al).save()
elif arg[0] == UserProfile:
UserLog(user_id=arg[1], activity_log=al).save()
if isinstance(arg, Addon):
AddonLog(addon=arg, activity_log=al).save()
elif isinstance(arg, Version):
VersionLog(version=arg, activity_log=al).save()
elif isinstance(arg, UserProfile):
# Index by any user who is mentioned as an argument.
UserLog(activity_log=al, user=arg).save()

Просмотреть файл

@ -86,8 +86,7 @@ class AddonLog(amo.models.ModelBase):
class CommentLog(amo.models.ModelBase):
"""
This table is for indexing the activity log by user.
Note: This includes activity performed unto the user.
This table is for indexing the activity log by comment.
"""
activity_log = models.ForeignKey('ActivityLog')
comments = models.CharField(max_length=255)
@ -97,6 +96,18 @@ class CommentLog(amo.models.ModelBase):
ordering = ('-created',)
class VersionLog(amo.models.ModelBase):
"""
This table is for indexing the activity log by version.
"""
activity_log = models.ForeignKey('ActivityLog')
version = models.ForeignKey(Version)
class Meta:
db_table = 'log_activity_version'
ordering = ('-created',)
class UserLog(amo.models.ModelBase):
"""
This table is for indexing the activity log by user.
@ -123,6 +134,11 @@ class ActivityLogManager(amo.models.ManagerBase):
else:
return self.none()
def for_version(self, version):
vals = (VersionLog.objects.filter(version=version)
.values_list('activity_log', flat=True))
return self.filter(pk__in=list(vals))
def for_user(self, user):
vals = (UserLog.objects.filter(user=user)
.values_list('activity_log', flat=True))

Просмотреть файл

@ -117,6 +117,15 @@ class TestActivityLog(test_utils.TestCase):
entries = ActivityLog.objects.for_user(u)
eq_(len(entries), 1)
def test_version_log(self):
request = self.request
amo.log(amo.LOG['CUSTOM_TEXT'], 'hi there')
version = Version.objects.all()[0]
amo.log(amo.LOG.REJECT_VERSION, version.addon, version,
user=self.request.amo_user)
entries = ActivityLog.objects.for_version(version)
eq_(len(entries), 1)
def test_xss_arguments(self):
addon = Addon.objects.get()
au = AddonUser(addon=addon, user=self.user)

Просмотреть файл

@ -1,38 +1,15 @@
<tr>
<td>
{% if record.arguments.1 %}
{{ record.arguments.1.version }}
{% else %}
{{ _('Deleted version') }}
{% endif %}
</td>
<td>
{{ record.user.name }}<br>
<span class="light">{{ record.created|datetime }}</span>
</td>
<td>
<strong>{{ record }}</strong>
{% if record.details %}
<div class="history_comment">
{{ record.details.comments|nl2br }}
</div>
{% if record.details.files %}
<ul>
{% for file_id in record.details.files %}
{% if file_id in files: %}
{% set file = files[file_id] %}
<li>
<strong>{{ file.platform }}</strong>
[<a href="{{ file.get_url_path(addon.app, 'editor') }}" class="install"
data-type="{{ amo.ADDON_SLUGS[addon.type] }}">{{ file.filename }}</a>]
</li>
{% else %}
<li><em>{{ _('File deleted') }}</em></li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
{% endif %}
</td>
</tr>
<li>
<strong>{{ record }}</strong>
{% if record.details %}
<div class="history_comment">
{{ record.details.comments|nl2br }}
</div>
{% endif %}
<div class="light">
<em>
{% trans user = record.user|user_link, date = record.created|babel_datetime %}
By {{ user }} on {{ date }}
{% endtrans %}
</em>
</div>
</li>

Просмотреть файл

@ -0,0 +1,30 @@
{% if num_pages > 1 %}
<ol class="pagination">
{% if pager.has_next() %}
<li>
<a rel="prev" href="{{ pager.url|urlparams(page=pager.next_page_number()) }}#history">
{{ _('Older') }}
</a>
</li>
{% endif %}
<li>
{% trans begin=pager.start_index(), end=pager.end_index(),
count=count|numberfmt %}
Results <strong>{{ begin }}</strong>&ndash;<strong>{{ end }}</strong>
of <strong>{{ count }}</strong>
{% endtrans %}
</li>
{% if pager.has_previous() %}
<li>
<a rel="next" href="{{ pager.url|urlparams(page=pager.previous_page_number()) }}#history">
{{ _('Newer') }}
</a>
</li>
{% endif %}
{% for x in pager.page_range %}
<li {{ x|class_selected(pager.number) }}>
<a href="{{ pager.url|urlparams(page=x) }}">{{ x }}</a>
</li>
{% endfor %}
</ol>
{% endif %}

Просмотреть файл

@ -43,82 +43,75 @@
<p>{{ version.approvalnotes|nl2br }}</p>
{% endif %}
<h3 id="file-history">{{ _('Item History') }}</h3>
{% if history %}
<div id="review-files-header">
<h3 id="history">{{ _('Add-on History') }}</h3>
<div id="review-files-paginate">
{% include "editors/includes/paginator_history.html" %}
</div>
</div>
<div class="results">
<div class="results-inner">
<table id="review-files" class="data-grid">
<thead>
<tr class="listing-header">
<th width="15%">{{ _('Version') }}</th>
<th width="20%">{{ _('Review') }}</th>
<th>{{ _('Comments') }}</th>
</tr>
</thead>
<tbody>
{% for record in history %}
{% include 'editors/includes/history.html' %}
{% endfor %}
</tbody>
<table id="review-files" class="item-history">
{% for i in range(pager.object_list.count(), 0, -1) %}
{% set version = pager.object_list[i-1] %}
<tr class="listing-header">
<th colspan="2">
{% trans version = version.version, created = version.created|datetime %}
Version {{ version }} &middot; {{ created }}
{% endtrans %}
</th>
</tr>
<tr>
<td class="files">
<div><strong>{{ _('Files in this version:') }}</strong></div>
{% for file in version.all_files %}
<div>
<span class="light">
<strong><a href="{{ file.get_url_path(addon.app, 'editor') }}" class="install"
data-type="{{ amo.ADDON_SLUGS[addon.type] }}">{{ file.platform }}</a></strong>
<div>
{{ file_review_status(addon, file) }}
</div>
<a href="{{ url('devhub.file_validation', addon.slug, file.id) }}">{{ _('Validation') }}</a>
&middot;
{% if waffle.switch('zamboni-file-viewer') %}
<a href="{{ url('files.list', file.id) }}">{{ _('Contents') }}</a>
{% else %}
<a href="{{ remora_url('/files/browse/%d/1' % file.id) }}">{{ _('Contents') }}</a>
{% endif %}
{% if show_diff %}
&middot;
{% if waffle.switch('zamboni-file-viewer') %}
<a href="{{ url('files.compare', file.id, file_compare(file, show_diff)) }}">{{ _('Compare') }}</a>
{% else %}
<a href="{{ remora_url('/files/diff/%d/' % file.id) }}">{{ _('Compare') }}</a>
{% endif %}
{% endif %}
</span>
</div>
{% endfor %}
</td>
<td>
{% set records = version.all_activity %}
{% if records %}
<ul class="activity">
{% for record_version in records %}
{% set record = record_version.activity_log %}
{% include 'editors/includes/history.html' %}
{% endfor %}
</ul>
{% else %}
<div class="no-activity">
{{ _('This version has no activity yet.') }}
</div>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% else %}
<p class="article">
<em>
{{ _('No previous review entries could be found.') }}
</em>
</p>
{% endif %}
<h3 id="compatibility">{{ _('Compatibility') }}</h3>
<div class="article">
<ul>
{% for compat in version.compatible_apps.values() %}
<li>
{% if compat.application_id in amo.APPS_ALL %}
<div class="app-icon ed-sprite-{{ amo.APPS_ALL[compat.application_id].short }}"
title="{{ amo.APPS_ALL[compat.application_id].pretty}}"></div>
{% endif %}
{{ compat }}
</li>
{% endfor %}
</ul>
</div>
<h3 id="validation">
{{ _('Files and Validation Results') }}
<span>[<a href="{{ remora_url('pages/validation') }}">{{ _('help') }}</a>]</span>
</h3>
<div class="article">
<ul class="files">
{% for file in version.files.all() %}
<li>
<strong>{{ file.platform }}</strong>
[<a href="{{ file.get_url_path(addon.app, 'editor') }}" class="install"
data-type="{{ amo.ADDON_SLUGS[addon.type] }}">{{ file.filename }}</a>]
<br>
{{ file_review_status(addon, file) }}
&middot;
<a href="{{ url('devhub.file_validation', addon.slug, file.id) }}">{{ _('Validation Results') }}</a>
&middot;
{% if waffle.switch('zamboni-file-viewer') %}
<a href="{{ url('files.list', file.id) }}">{{ _('View Contents') }}</a>
{% else %}
<a href="{{ remora_url('/files/browse/%d/1' % file.id) }}">{{ _('View Contents') }}</a>
{% endif %}
{% if show_diff %}
&middot;
{% if waffle.switch('zamboni-file-viewer') %}
<a href="{{ url('files.compare', file.id, file_compare(file, show_diff)) }}">{{ _('Compare With Public Version') }}</a>
{% else %}
<a href="{{ remora_url('/files/diff/%d/' % file.id) }}">{{ _('Compare With Public Version') }}</a>
{% endif %}
{% endif %}
</li>
{% endfor %}
</ul>
</div>
<form method="POST" action="#review-actions">
{{ csrf() }}
@ -218,26 +211,20 @@
</div>
{% endif %}
<strong>{{ _('This Add-on') }}</strong>
<strong>{{ _('Add-on Actions ') }}</strong>
<ul id="actions-addon">
<li><a href="{{ addon.get_url_path() }}">{{ _('View Listing') }}</a></li>
<li><a href="{{ addon.get_url_path() }}">{{ _('View Add-on Listing') }}</a></li>
{% if is_admin %}
<li><a href="{{ url('devhub.addons.edit', addon.slug) }}">{{ _('Edit Add-on') }}</a> <em>{{ _('(admin)') }}</em></li>
<li><a href="{{ remora_url('admin/addons/status/%s' % addon.id) }}">{{ _('Admin Page') }}</a> <em>{{ _('(admin)') }}</em></li>
{% endif %}
</ul>
{% if is_admin %}
<strong>{{ _('Admin Tools') }}</strong>
<ul id="actions-admin">
<li><a href="{{ url('devhub.addons.edit', addon.slug) }}">{{ _('Edit Add-on') }}</a></li>
<li><a href="{{ remora_url('admin/addons/status/%s' % addon.id) }}">{{ _('Admin Page') }}</a></li>
</ul>
{% endif %}
<strong>{{ _('Review This Add-on') }}</strong>
<ul>
<li><a href="#more-about">{{ _('More Information') }}</a></li>
<li><a href="#file-about">{{ _('Item History') }}</a></li>
<li><a href="#compatibility">{{ _('Compatibility') }}</a></li>
<li><a href="#validation">{{ _('Add-on Files') }}</a></li>
<li><a href="#review-actions">{{ _('Editor Actions') }}</a></li>
<li><a href="#history">{{ _('Add-on History') }}</a></li>
</ul>
<strong>{{ _('Authors') }}</strong>
@ -254,6 +241,19 @@
{% endfor %}
</ul>
<strong>{{ _('Compatibility') }}</strong>
<ul>
{% for compat in version.compatible_apps.values() %}
<li>
{% if compat.application_id in amo.APPS_ALL %}
<div class="app-icon ed-sprite-{{ amo.APPS_ALL[compat.application_id].short }}"
title="{{ amo.APPS_ALL[compat.application_id].pretty}}"></div>
{% endif %}
{{ compat }}
</li>
{% endfor %}
</ul>
{% if flags: %}
<strong>{{ _('Flags') }}</strong>
<ul>

Просмотреть файл

@ -1355,16 +1355,52 @@ class TestReview(ReviewBase):
response = self.client.get(self.url)
eq_(response.status_code, 200)
validation = pq(response.content).find('#validation').next()
doc = pq(response.content)
validation = doc('#review-files .files div').eq(1)
eq_(validation.children().length, 1)
eq_(validation.find('a').eq(0).text(), 'Public.xpi')
eq_(validation.find('a').eq(0).text(), 'All Platforms')
eq_(validation.find('a').eq(1).text(), "Validation Results")
eq_(validation.find('a').eq(2).text(), "View Contents")
eq_(validation.find('a').eq(1).text(), "Validation")
eq_(validation.find('a').eq(2).text(), "Contents")
eq_(validation.find('a').length, 3)
def test_item_history(self):
self.addon_file(u'something', u'0.2', amo.STATUS_PUBLIC,
amo.STATUS_UNREVIEWED)
eq_(self.addon.versions.count(), 1)
url = reverse('editors.review', args=[self.addon.slug])
self.review_version(self.version, url)
v2 = self.versions['something']
v2.addon = self.addon
v2.created = v2.created + timedelta(days=1)
v2.save()
self.review_version(v2, url)
eq_(self.addon.versions.count(), 2)
r = self.client.get(url)
doc = pq(r.content)
# View the history verify two versions:
ths = doc('table#review-files tr th:first-child')
assert '0.1' in ths.eq(0).text()
assert '0.2' in ths.eq(1).text()
for i in [0, 2]:
tds = doc('table#review-files tr td')
eq_(tds.eq(i).find('strong').eq(0).text(), "Files in this version:")
eq_(tds.eq(i).find('div').length, 3)
eq_(tds.eq(1).find('ul li').length, 1)
eq_(tds.eq(1).find('ul li a').length, 3)
eq_(tds.eq(1).find('ul li .history_comment').text(), "something")
eq_(tds.eq(1).find('ul li em a').text(), "An editor")
def test_files_in_item_history(self):
data = {'action': 'public', 'operating_systems': 'win',
'applications': 'something', 'comments': 'something',
@ -1375,20 +1411,21 @@ class TestReview(ReviewBase):
r = self.client.get(self.url)
doc = pq(r.content)
eq_(doc('#review-files tbody tr').length, 1)
li = doc('#review-files .history_comment').parent().find('li')
eq_(li.length, 1)
eq_(li.find('strong').text(), "All Platforms")
eq_(doc('#review-files .files > div').length, 2)
div = doc('#review-files .files div').eq(1)
eq_(div.length, 1)
eq_(div.find('a.install').text(), "All Platforms")
def test_no_items(self):
response = self.client.get(self.url)
eq_(pq(response.content).find('#file-history').next().text(),
"No previous review entries could be found.")
doc = pq(response.content)
div = doc('#review-files-header').next().find('td').eq(1).find('div')
eq_(div.text(), "This version has no activity yet.")
def test_listing_link(self):
response = self.client.get(self.url)
text = pq(response.content).find('#actions-addon li a').eq(0).text()
eq_(text, "View Listing")
eq_(text, "View Add-on Listing")
def test_admin_links_as_admin(self):
self.login_as_admin()
@ -1396,14 +1433,14 @@ class TestReview(ReviewBase):
doc = pq(response.content)
admin = doc('#actions-admin')
assert admin.length > 0
admin = doc('#actions-addon li')
assert admin.length == 3
a = admin.find('a').eq(0)
a = admin.find('a').eq(1)
eq_(a.text(), "Edit Add-on")
assert "developers/addon/%s" % self.addon.slug in a.attr('href')
a = admin.find('a').eq(1)
a = admin.find('a').eq(2)
eq_(a.text(), "Admin Page")
assert "admin/addons/status/%s" % self.addon.id in a.attr('href')
@ -1412,8 +1449,8 @@ class TestReview(ReviewBase):
response = self.client.get(self.url)
doc = pq(response.content)
admin = doc('#actions-admin')
eq_(admin.length, 0)
admin = doc('#actions-addon li')
eq_(admin.length, 1)
def test_no_public(self):
s = amo.STATUS_PUBLIC
@ -1430,9 +1467,9 @@ class TestReview(ReviewBase):
response = self.client.get(self.url)
validation = pq(response.content).find('#validation').next()
eq_(validation.find('a').eq(1).text(), "Validation Results")
eq_(validation.find('a').eq(2).text(), "View Contents")
validation = pq(response.content).find('.files')
eq_(validation.find('a').eq(1).text(), "Validation")
eq_(validation.find('a').eq(2).text(), "Contents")
eq_(validation.find('a').length, 3)
@ -1456,9 +1493,9 @@ class TestReview(ReviewBase):
response = self.client.get(self.url)
validation = pq(response.content).find('#validation').next()
eq_(validation.find('a').eq(1).text(), "Validation Results")
eq_(validation.find('a').eq(2).text(), "View Contents")
validation = pq(response.content).find('.files')
eq_(validation.find('a').eq(1).text(), "Validation")
eq_(validation.find('a').eq(2).text(), "Contents")
eq_(validation.find('a').length, 3)
@ -1486,17 +1523,19 @@ class TestReview(ReviewBase):
doc = pq(r.content)
# View the history verify two versions:
eq_(doc('table#review-files tr td:first-child').eq(0).text(), '0.1')
eq_(doc('table#review-files tr td:first-child').eq(1).text(), '0.2')
ths = doc('table#review-files tr th:first-child')
assert '0.1' in ths.eq(0).text()
assert '0.2' in ths.eq(1).text()
# Delete a version:
v2.delete()
# Verify two versions, one deleted:
r = self.client.get(url)
doc = pq(r.content)
ths = doc('table#review-files tr th:first-child')
eq_(doc('table#review-files tr td:first-child').eq(1).text(),
'Deleted version')
eq_(doc('table#review-files tr td:first-child').eq(0).text(), '0.1')
eq_(doc('table#review-files tr th:first-child').length, 1)
assert '0.1' in ths.eq(0).text()
def review_version(self, version, url):
version.files.all()[0].update(status=amo.STATUS_UNREVIEWED)
@ -1586,8 +1625,9 @@ class TestReview(ReviewBase):
doc = pq(r.content)
assert r.context['show_diff']
eq_(doc('.files a:last-child').text(), 'Compare With Public Version')
eq_(doc('.files a:last-child').attr('href'),
eq_(doc('.files').eq(1).find('a').eq(3).text(), 'Compare')
eq_(doc('.files').eq(1).find('a').eq(3).attr('href'),
reverse('files.compare', args=(next_file.pk,
first_file.pk)))
@ -1769,7 +1809,7 @@ class TestStatusFile(ReviewBase):
for status in [amo.STATUS_UNREVIEWED, amo.STATUS_LITE]:
self.addon.update(status=status)
res = self.client.get(self.url)
node = pq(res.content)('ul.files li:first-child')
node = pq(res.content)('td.files div').eq(1)
assert 'Pending Preliminary Review' in node.text()
def test_status_full(self):
@ -1777,7 +1817,7 @@ class TestStatusFile(ReviewBase):
amo.STATUS_PUBLIC]:
self.addon.update(status=status)
res = self.client.get(self.url)
node = pq(res.content)('ul.files li:first-child')
node = pq(res.content)('td.files div').eq(1)
assert 'Pending Full Review' in node.text()
def test_status_full_reviewed(self):
@ -1788,11 +1828,12 @@ class TestStatusFile(ReviewBase):
amo.STATUS_NOMINATED, amo.STATUS_LITE_AND_NOMINATED]:
self.addon.update(status=status)
res = self.client.get(self.url)
node = pq(res.content)('ul.files li:first-child')
node = pq(res.content)('td.files div').eq(1)
assert 'Fully Reviewed' in node.text()
def test_other(self):
self.addon.update(status=amo.STATUS_BETA)
res = self.client.get(self.url)
node = pq(res.content)('ul.files li:first-child')
node = pq(res.content)('td.files div').eq(1)
assert unicode(amo.STATUS_CHOICES[self.file.status]) in node.text()

Просмотреть файл

@ -121,18 +121,6 @@ def _editor_progress():
return (progress, percentage)
def _get_history(addon):
files = []
history = (ActivityLog.objects.for_addons(addon).order_by('created')
.filter(action__in=amo.LOG_REVIEW_QUEUE))
for h in history:
if 'files' in h.details:
files.extend(h.details['files'])
return history, File.objects.in_bulk(files)
@editor_required
def performance(request, user_id=False):
user = request.amo_user
@ -442,15 +430,21 @@ def review(request, addon):
# We only allow the user to check/uncheck files for "pending"
allow_unchecking_files = form.helper.review_type == "pending"
history, files = _get_history(addon)
versions = (Version.objects.filter(addon=addon).order_by('-created')
.transform(Version.transformer_activity)
.transform(Version.transformer))
pager = amo.utils.paginate(request, versions, 5)
num_pages = pager.paginator.num_pages
count = pager.paginator.count
ctx = context(version=version, addon=addon,
pager=pager, num_pages=num_pages, count=count,
flags=Review.objects.filter(addon=addon, flag=True),
form=form, paging=paging, canned=canned, is_admin=is_admin,
status_types=amo.STATUS_CHOICES, show_diff=show_diff,
allow_unchecking_files=allow_unchecking_files,
actions=actions, actions_minimal=actions_minimal,
history=history, files=files)
actions=actions, actions_minimal=actions_minimal)
return jingo.render(request, 'editors/review.html', ctx)

Просмотреть файл

@ -150,6 +150,13 @@ class Version(amo.models.ModelBase):
amo.log(amo.LOG.DELETE_VERSION, self.addon, str(self.version))
super(Version, self).delete()
@amo.cached_property(writable=True)
def all_activity(self):
from devhub.models import VersionLog # yucky
al = (VersionLog.objects.filter(version=self.id).order_by('created')
.select_related(depth=1).no_cache())
return al
@amo.cached_property(writable=True)
def compatible_apps(self):
"""Get a mapping of {APP: ApplicationVersion}."""
@ -259,6 +266,28 @@ class Version(amo.models.ModelBase):
version.compatible_apps = cls._compat_map(av_dict.get(v_id, []))
version.all_files = file_dict.get(v_id, [])
@classmethod
def transformer_activity(cls, versions):
"""Attach all the activity to the versions."""
from devhub.models import VersionLog # yucky
ids = set(v.id for v in versions)
if not versions:
return
al = (VersionLog.objects.filter(version__in=ids).order_by('created')
.select_related(depth=1).no_cache())
def rollup(xs):
groups = amo.utils.sorted_groupby(xs, 'version_id')
return dict((k, list(vs)) for k, vs in groups)
al_dict = rollup(al)
for version in versions:
v_id = version.id
version.all_activity = al_dict.get(v_id, [])
def disable_old_files(self):
if not self.files.filter(status=amo.STATUS_BETA).exists():
qs = File.objects.filter(version__addon=self.addon_id,

Просмотреть файл

@ -507,17 +507,105 @@ div.editor-stats-table > div.editor-stats-dark {
/* Review Page */
.primary {
width: 85%;
width: 83.5%;
}
.secondary {
width: 13.49%;
width: 15%;
}
.secondary.scroll_sidebar_parent {
position: relative;
}
.scroll_sidebar_parent #scroll_sidebar li a {
font-weight: normal;
}
#review-files {
margin: 0;
width: 100%;
}
#review-files .files {
background-color: #FAFAFA;
border-right: 1px dotted #DDDDDD;
border-top: 0 none;
width: 255px;
padding: 15px;
}
#review-files-header h3 {
float: left;
}
#review-files-header #review-files-paginate {
float: right;
margin-top: 2em;
}
#review-files-paginate ol {
float: right;
margin: 0;
padding-left: 10px;
text-align: right;
top: -20px;
width: auto;
}
#review-files td .activity li {
border-top: 1px dotted #DDDDDD;
margin: 1em 1em 0;
padding-top: 1em;
}
#review-files td .activity li:first-child {
border-top: 0 none;
margin-top: 0;
padding-top: 0;
}
#review-files tr.listing-header:first-child {
border-top: none;
}
#review-files tr.listing-header {
border-top: 1px solid #A5BFCE;
}
#review-files tr.listing-header:hover {
cursor: pointer;
background-color: #CCE6FF;
background: -webkit-gradient(
linear,
left bottom,
left top,
from(#FFFFFF),
to(#CCE6FF)
);
background: -moz-linear-gradient(#FFFFFF, #CCE6FF);
}
#review-files tr.listing-header:active {
cursor: pointer;
background-color: #FFF;
background: -webkit-gradient(
linear,
left bottom,
left top,
from(#CCE6FF),
to(#FFFFFF)
);
background: -moz-linear-gradient(#E0EFFD, #FFF);
}
#review-files .files div:first-child {
padding-top: 0;
}
#review-files .files > div {
padding-top: 5px;
line-height: 1.3em;
}
#review-files .no-activity {
color: #777777;
font-style: italic;
text-align: center;
}
#review-files .install {
padding: 0;
}
#review-actions-form textarea,
#review-actions-form input[type=text],
#review-actions-form select {
@ -662,7 +750,6 @@ div.editor-stats-table > div.editor-stats-dark {
.history_comment {
overflow: auto;
width: 613px;
}
.review-paging {
@ -705,3 +792,22 @@ span.currently_viewing_warning {
}
.paginator {
margin: 0pt;
overflow: auto;
}
.paginator li {
float: left;
width: 33%;
}
.paginator .next {
text-align: right;
}
.paginator .page {
text-align: center;
}

Просмотреть файл

@ -131,6 +131,12 @@ function initReviewActions() {
});
}
check_currently_viewing();
/* Item History */
$('#review-files tr:not(.listing-header, :last-child)').hide();
$('#review-files tr.listing-header').click(function() {
$(this).next().toggle();
});
}
function insertAtCursor(textarea, text) {

Просмотреть файл

@ -0,0 +1,16 @@
DROP TABLE IF EXISTS log_activity_version;
CREATE TABLE `log_activity_version` (
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
`activity_log_id` int(11) NOT NULL,
`version_id` int(11) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
;
ALTER TABLE `log_activity_version`
ADD CONSTRAINT FOREIGN KEY (`activity_log_id`) REFERENCES `log_activity` (`id`);
ALTER TABLE `log_activity_version`
ADD CONSTRAINT FOREIGN KEY (`version_id`) REFERENCES `versions` (`id`);