summaryrefslogtreecommitdiffstats
path: root/template/en/default/attachment
diff options
context:
space:
mode:
authorjkeiser%netscape.com <>2003-07-31 05:04:48 +0200
committerjkeiser%netscape.com <>2003-07-31 05:04:48 +0200
commit2a3c2708fd1f55bd06d0b48a132d487a1745c075 (patch)
tree0c2706af44e39b5497303aa4f85d067eaa60be9c /template/en/default/attachment
parente98093ef9f40cf2ab88a939e05de89400906352a (diff)
downloadbugzilla-2a3c2708fd1f55bd06d0b48a132d487a1745c075.tar.gz
bugzilla-2a3c2708fd1f55bd06d0b48a132d487a1745c075.tar.xz
Patch Viewer, a pretty way of viewing and manipulating patches (bug 174942). Requires PatchIterator to be installed, classes uploaded to that bug and will be soon in CPAN.
Diffstat (limited to 'template/en/default/attachment')
-rw-r--r--template/en/default/attachment/diff-file.html.tmpl129
-rw-r--r--template/en/default/attachment/diff-footer.html.tmpl33
-rw-r--r--template/en/default/attachment/diff-header.html.tmpl307
-rw-r--r--template/en/default/attachment/edit.html.tmpl107
-rw-r--r--template/en/default/attachment/list.html.tmpl8
5 files changed, 552 insertions, 32 deletions
diff --git a/template/en/default/attachment/diff-file.html.tmpl b/template/en/default/attachment/diff-file.html.tmpl
new file mode 100644
index 000000000..51072269d
--- /dev/null
+++ b/template/en/default/attachment/diff-file.html.tmpl
@@ -0,0 +1,129 @@
+<!-- 1.0@bugzilla.org -->
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): John Keiser <jkeiser@netscape.com>
+ #%]
+
+[%# This line is really long for a reason: to get rid of any possible textnodes
+ # between the elements. This is necessary because DOM parent-child-sibling
+ # relations can change and screw up the javascript for restoring, collapsing
+ # and expanding. Do not change without testing all three of those.
+ #%]
+<table class="file_table"><thead><tr><td class="file_head" colspan="2"><a href="#" onclick="return twisty_click(this)">[% collapsed ? '(+)' : '(-)' %]</a><input type="checkbox" name="[% file.filename FILTER html %]"[% collapsed ? '' : ' checked' %] style="display: none">
+ [% IF lxr_prefix && !file.is_add %]
+ <a href="[% lxr_prefix %]">[% file.filename FILTER html %]</a>
+ [% ELSE %]
+ [% file.filename FILTER html %]
+ [% END %]
+ [% IF file.plus_lines %]
+ [% IF file.minus_lines %]
+ (-[% file.minus_lines %]&nbsp;/&nbsp;+[% file.plus_lines %]&nbsp;lines)
+ [% ELSE %]
+ (+[% file.plus_lines %]&nbsp;lines)
+ [% END %]
+ [% ELSE %]
+ [% IF file.minus_lines %]
+ (-[% file.minus_lines %]&nbsp;lines)
+ [% END %]
+ [% END %]
+</td></tr></thead><tbody class="[% collapsed ? 'file_collapse' : 'file' %]">
+<script type="application/x-javascript" language="JavaScript">
+incremental_restore()
+</script>
+
+[% section_num = 0 %]
+[% FOREACH section = sections %]
+ [% section_num = section_num + 1 %]
+ <tr><th class="section_head" colspan="2">
+ [% IF file.is_add %]
+ Added
+ [% ELSIF file.is_remove %]
+ [% IF bonsai_prefix %]
+ <a href="[% bonsai_prefix %]">Removed</a>
+ [% ELSE %]
+ Removed
+ [% END %]
+ [% ELSE %]
+ [% IF bonsai_prefix %]
+ <a href="[% bonsai_prefix %]#[% section.old_start %]">
+ [% END %]
+ [% IF section.old_lines > 1 %]
+ Lines [% section.old_start %]-[% section.old_start + section.old_lines - 1 %]
+ [% ELSE %]
+ Line [% section.old_start %]
+ [% END %]
+ [% IF bonsai_prefix %]
+ </a>
+ [% END %]
+ [% END %]
+ (<a name="[% file.filename FILTER html %]_sec[% section_num %]"><a href="#[% file.filename FILTER html %]_sec[% section_num %]">Link Here</a></a>)
+ </th></tr>
+ [% FOREACH group = section.groups %]
+ [% IF group.context %]
+ [% FOREACH line = group.context %]
+ <tr><td><pre>[% line FILTER html %]</pre></td><td><pre>[% line FILTER html %]</pre></td></tr>
+ [% END %]
+ [% END %]
+ [% IF group.plus.size %]
+ [% IF group.minus.size %]
+ [% i = 0 %]
+ [% WHILE (i < group.plus.size || i < group.minus.size) %]
+ [% currentloop = 0 %]
+ [% WHILE currentloop < 500 && (i < group.plus.size || i < group.minus.size) %]
+ <tr class="changed">
+ <td><pre>[% group.minus.$i FILTER html %]</pre></td>
+ <td><pre>[% group.plus.$i FILTER html %]</pre></td>
+ </tr>
+ [% currentloop = currentloop + 1 %]
+ [% i = i + 1 %]
+ [% END %]
+ [% END %]
+ [% ELSE %]
+ [% FOREACH line = group.plus %]
+ [% IF file.is_add %]
+ <tr>
+ <td class="added" colspan="2"><pre>[% line FILTER html %]</pre></td>
+ </tr>
+ [% ELSE %]
+ <tr>
+ <td></td>
+ <td class="added"><pre>[% line FILTER html %]</pre></td>
+ </tr>
+ [% END %]
+ [% END %]
+ [% END %]
+ [% ELSE %]
+ [% IF group.minus.size %]
+ [% FOREACH line = group.minus %]
+ [% IF file.is_remove %]
+ <tr>
+ <td class="removed" colspan="2"><pre>[% line FILTER html %]</pre></td>
+ </tr>
+ [% ELSE %]
+ <tr>
+ <td class="removed"><pre>[% line FILTER html %]</pre></td>
+ <td></td>
+ </tr>
+ [% END %]
+ [% END %]
+ [% END %]
+ [% END %]
+ [% END %]
+[% END %]
+
+</table>
diff --git a/template/en/default/attachment/diff-footer.html.tmpl b/template/en/default/attachment/diff-footer.html.tmpl
new file mode 100644
index 000000000..4eb94aca2
--- /dev/null
+++ b/template/en/default/attachment/diff-footer.html.tmpl
@@ -0,0 +1,33 @@
+<!-- 1.0@bugzilla.org -->
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): John Keiser <jkeiser@netscape.com>
+ #%]
+
+</form>
+
+[% IF headers %]
+
+ <br>
+
+ [% PROCESS global/footer.html.tmpl %]
+
+[% ELSE %]
+</body>
+</html>
+[% END %]
diff --git a/template/en/default/attachment/diff-header.html.tmpl b/template/en/default/attachment/diff-header.html.tmpl
new file mode 100644
index 000000000..c1b70173e
--- /dev/null
+++ b/template/en/default/attachment/diff-header.html.tmpl
@@ -0,0 +1,307 @@
+<!-- 1.0@bugzilla.org -->
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): John Keiser <jkeiser@netscape.com>
+ #%]
+
+[%# Define strings that will serve as the title and header of this page %]
+
+[% title = BLOCK %]Attachment #[% attachid %] for Bug #[% bugid %][% END %]
+
+[% style = BLOCK %]
+.file_head {
+ font-size: x-large;
+ font-weight: bold;
+ background-color: #d3d3d3;
+ border: 1px solid black;
+ width: 100%;
+}
+.file_collapse {
+ display: none;
+}
+.section_head {
+ width: 100%;
+ font-weight: bold;
+ background-color: #d3d3d3;
+ border: 1px solid black;
+ text-align: left;
+}
+table.file_table {
+ table-layout: fixed;
+ width: 100%;
+ empty-cells: show;
+ border-spacing: 0px;
+ border-collapse: collapse;
+}
+tbody.file td {
+ border-left: 1px dashed black;
+ border-right: 1px dashed black;
+ width: 50%;
+}
+tbody.file pre {
+ display: inline;
+ white-space: -moz-pre-wrap;
+ font-size: 0.9em;
+}
+tbody.file pre:empty {
+ display: block;
+ height: 1em;
+}
+.changed {
+ background-color: lightblue;
+}
+.added {
+ background-color: lightgreen;
+}
+.removed {
+ background-color: #FFCC99;
+}
+.warning {
+ color: red
+}
+[% END %]
+
+[%# SCRIPT FUNCTIONS %]
+[% javascript = BLOCK %]
+ function collapse_all() {
+ var elem = document.checkboxform.firstChild;
+ while (elem != null) {
+ if (elem.firstChild != null) {
+ var tbody = elem.firstChild.nextSibling;
+ if (tbody.className == 'file') {
+ tbody.className = 'file_collapse';
+ twisty = get_twisty_from_tbody(tbody);
+ twisty.firstChild.nodeValue = '(+)';
+ twisty.nextSibling.checked = false;
+ }
+ }
+ elem = elem.nextSibling;
+ }
+ return false;
+ }
+
+ function expand_all() {
+ var elem = document.checkboxform.firstChild;
+ while (elem != null) {
+ if (elem.firstChild != null) {
+ var tbody = elem.firstChild.nextSibling;
+ if (tbody.className == 'file_collapse') {
+ tbody.className = 'file';
+ twisty = get_twisty_from_tbody(tbody);
+ twisty.firstChild.nodeValue = '(-)';
+ twisty.nextSibling.checked = true;
+ }
+ }
+ elem = elem.nextSibling;
+ }
+ return false;
+ }
+
+ var current_restore_elem;
+
+ function restore_all() {
+ current_restore_elem = null;
+ incremental_restore();
+ }
+
+ function incremental_restore() {
+ if (!document.checkboxform.restore_indicator.checked) {
+ return;
+ }
+ var next_restore_elem;
+ if (current_restore_elem) {
+ next_restore_elem = current_restore_elem.nextSibling;
+ } else {
+ next_restore_elem = document.checkboxform.firstChild;
+ }
+ while (next_restore_elem != null) {
+ current_restore_elem = next_restore_elem;
+ if (current_restore_elem.firstChild != null) {
+ restore_elem(current_restore_elem.firstChild.nextSibling);
+ }
+ next_restore_elem = current_restore_elem.nextSibling;
+ }
+ }
+
+ function restore_elem(elem, alertme) {
+ if (elem.className == 'file_collapse') {
+ twisty = get_twisty_from_tbody(elem);
+ if (twisty.nextSibling.checked) {
+ elem.className = 'file';
+ twisty.firstChild.nodeValue = '(-)';
+ }
+ } else if (elem.className == 'file') {
+ twisty = get_twisty_from_tbody(elem);
+ if (!twisty.nextSibling.checked) {
+ elem.className = 'file_collapse';
+ twisty.firstChild.nodeValue = '(+)';
+ }
+ }
+ }
+
+ function twisty_click(twisty) {
+ tbody = get_tbody_from_twisty(twisty);
+ if (tbody.className == 'file') {
+ tbody.className = 'file_collapse';
+ twisty.firstChild.nodeValue = '(+)';
+ twisty.nextSibling.checked = false;
+ } else {
+ tbody.className = 'file';
+ twisty.firstChild.nodeValue = '(-)';
+ twisty.nextSibling.checked = true;
+ }
+ return false;
+ }
+
+ function get_tbody_from_twisty(twisty) {
+ return twisty.parentNode.parentNode.parentNode.nextSibling;
+ }
+ function get_twisty_from_tbody(tbody) {
+ return tbody.previousSibling.firstChild.firstChild.firstChild;
+ }
+[% END %]
+
+[% onload = 'restore_all(); document.checkboxform.restore_indicator.checked = true' %]
+
+[% IF headers %]
+ [% h1 = BLOCK %]
+ [% IF attachid %]
+ [% description FILTER html %] (#[% attachid %])
+ [% ELSE %]
+ [% old_url = url('attachment.cgi', action = 'diff', id = oldid) %]
+ [% new_url = url('attachment.cgi', action = 'diff', id = newid) %]
+ Diff Between
+ <a href="[% old_url %]">[% old_desc FILTER html %]</a>
+ (<a href="[% old_url %]">#[% oldid %]</a>)
+ and
+ <a href="[% new_url %]">[% new_desc FILTER html %]</a>
+ (<a href="[% new_url %]">#[% newid %]</a>)
+ [% END %]
+ for <a href="show_bug.cgi?id=[% bugid %]">Bug #[% bugid %]</a>
+ [% END %]
+ [% h2 = BLOCK %]
+ [% bugsummary FILTER html %]
+ [% END %]
+ [% PROCESS global/header.html.tmpl %]
+[% ELSE %]
+ <html>
+ <head>
+ <style type="text/css">
+ [% style %]
+ </style>
+ <script type="text/javascript" language="JavaScript">
+ <!--
+ [% javascript %]
+ -->
+ </script>
+ </head>
+ <body onload="[% onload FILTER html %]">
+[% END %]
+
+[%# If we have attachid, we are in diff, otherwise we're in interdiff %]
+[% IF attachid %]
+ [%# HEADER %]
+ [% IF headers %]
+ [% USE url('attachment.cgi', id = attachid) %]
+ <a href="[% url() %]">View</a>
+ | <a href="[% url(action = 'edit') %]">Edit</a>
+ [% USE url('attachment.cgi', id = attachid, context = context,
+ collapsed = collapsed, headers = headers,
+ action = 'diff') %]
+ | <a href="[% url(format = 'raw') %]">Raw Unified</a>
+ [% END %]
+ [% IF other_patches %]
+ [% IF headers %] |[%END%]
+ Differences between
+ <form style="display: inline">
+ <select name="oldid">
+ [% FOREACH patch = other_patches %]
+ <option value="[% patch.id %]"
+ [% IF patch.selected %] selected[% END %]
+ >[% patch.desc FILTER html %]</option>
+ [% END %]
+ </select>
+ and this patch
+ <input type="submit" value="Diff">
+ <input type="hidden" name="action" value="interdiff">
+ <input type="hidden" name="newid" value="[% attachid %]">
+ <input type="hidden" name="headers" value="[% headers FILTER html %]">
+ </form>
+ [% END %]
+ <br>
+[% ELSE %]
+ [% IF headers %]
+ [% USE url('attachment.cgi', newid = newid, oldid = oldid, action = 'interdiff') %]
+ <a href="[% url(format = 'raw') %]">Raw Unified</a>
+ [% IF attachid %]
+ <br>
+ [% ELSE %]
+ |
+ [% END %]
+ [% END %]
+[% END %]
+
+[%# Collapse / Expand %]
+<a href="#"
+ onmouseover="lastStatus = window.status; window.status='Collapse All'; return true"
+ onmouseout="window.status = lastStatus; return true"
+ onclick="return collapse_all()">Collapse All</a> |
+<a href="#"
+ onmouseover="lastStatus = window.status; window.status='Expand All'; return true"
+ onmouseout="window.status = lastStatus; return true"
+ onclick="return expand_all()">Expand All</a>
+
+[% IF do_context %]
+ | <span style='font-weight: bold'>Context:</span>
+ [% IF context == "patch" %]
+ (<strong>Patch</strong> /
+ [% ELSE %]
+ (<a href="[% url(context = '') %]">Patch</a> /
+ [% END %]
+ [% IF context == "file" %]
+ <strong>File</strong> /
+ [% ELSE %]
+ <a href="[% url(context = 'file') %]">File</a> /
+ [% END %]
+
+ [% IF context == "patch" || context == "file" %]
+ [% context = 3 %]
+ [% END %]
+ [%# textbox for context %]
+ <form style="display: inline"><input type="hidden" name="action" value="diff"><input type="hidden" name="id" value="[% attachid %]"><input type="hidden" name="collapsed" value="[% collapsed FILTER html %]"><input type="hidden" name="headers" value="[% headers FILTER html %]"><input type="text" name="context" value="[% context FILTER html %]" size="3"></form>)
+[% END %]
+
+[% IF warning %]
+<h2 class="warning">Warning:
+ [% IF warning == "interdiff1" %]
+ this difference between two patches may show things in the wrong places due
+ to a limitation in Bugzilla when comparing patches with different sets of
+ files.
+ [% END %]
+ [% IF warning == "interdiff2" %]
+ this difference between two patches may be inaccurate due to a limitation in
+ Bugzilla when comparing patches made against different revisions.
+ [% END %]
+</h2>
+[% END %]
+
+[%# Restore Stuff %]
+<form name="checkboxform">
+<input type="checkbox" name="restore_indicator" style="display: none">
+
+
diff --git a/template/en/default/attachment/edit.html.tmpl b/template/en/default/attachment/edit.html.tmpl
index 14c2dc1fe..2cfc0e088 100644
--- a/template/en/default/attachment/edit.html.tmpl
+++ b/template/en/default/attachment/edit.html.tmpl
@@ -42,6 +42,10 @@
<script type="application/x-javascript" language="JavaScript">
<!--
+ var prev_mode = 'raw';
+ var current_mode = 'raw';
+ var has_edited = 0;
+ var has_viewed_as_diff = 0;
function editAsComment()
{
// Get the content of the document as a string.
@@ -69,44 +73,81 @@
// with a newline.
theContent = theContent.replace( /(.*\n|.+)/g , ">$1" );
- hideElementById('viewFrame');
- hideElementById('editButton');
- hideElementById('smallCommentFrame');
-
- showElementById('undoEditButton');
-
- // Show the TEXTAREA that will contain the editable attachment
- // and copy the content of the attachment into it.
- showElementById('editFrame');
+ switchToMode('edit');
+ // Copy the contents of the diff into the textarea
var editFrame = document.getElementById('editFrame');
editFrame.value = theContent;
editFrame.value += "\n\n";
+
+ has_edited = 1;
}
function undoEditAsComment()
{
- // Hide the "edit attachment as comment" TEXTAREA and the "undo" button.
- hideElementById('undoEditButton');
- hideElementById('editFrame');
-
- // Show the "view attachment" IFRAME, the "redo" button that allows the user
- // to go back to editing the attachment as a comment, and the small comment field.
- showElementById('viewFrame');
- showElementById('redoEditButton');
- showElementById('smallCommentFrame');
-
+ switchToMode(prev_mode);
}
function redoEditAsComment()
{
- // Hide the "view attachment" IFRAME, the "redo" button that allows the user
- // to go back to editing the attachment as a comment, and the small comment field.
- hideElementById('viewFrame');
- hideElementById('redoEditButton');
- hideElementById('smallCommentFrame');
-
- // Show the "edit attachment as comment" TEXTAREA and the "undo" button.
- showElementById('undoEditButton');
- showElementById('editFrame');
+ switchToMode('edit');
+ }
+ function viewDiff()
+ {
+ switchToMode('diff');
+
+ // If we have not viewed as diff before, set the view diff frame URL
+ if (!has_viewed_as_diff) {
+ var viewDiffFrame = document.getElementById('viewDiffFrame');
+ viewDiffFrame.src =
+ 'attachment.cgi?id=[% attachid %]&action=diff&headers=0';
+ has_viewed_as_diff = 1;
+ }
+ }
+ function viewRaw()
+ {
+ switchToMode('raw');
+ }
+
+ function switchToMode(mode)
+ {
+ if (mode == current_mode) {
+ alert('switched to same mode! This should not happen.');
+ return;
+ }
+
+ // Switch out of current mode
+ if (current_mode == 'edit') {
+ hideElementById('editFrame');
+ hideElementById('undoEditButton');
+ } else if (current_mode == 'raw') {
+ hideElementById('viewFrame');
+ hideElementById('viewDiffButton');
+ hideElementById(has_edited ? 'redoEditButton' : 'editButton');
+ hideElementById('smallCommentFrame');
+ } else if (current_mode == 'diff') {
+ hideElementById('viewDiffFrame');
+ hideElementById('viewRawButton');
+ hideElementById(has_edited ? 'redoEditButton' : 'editButton');
+ hideElementById('smallCommentFrame');
+ }
+
+ // Switch into new mode
+ if (mode == 'edit') {
+ showElementById('editFrame');
+ showElementById('undoEditButton');
+ } else if (mode == 'raw') {
+ showElementById('viewFrame');
+ showElementById('viewDiffButton');
+ showElementById(has_edited ? 'redoEditButton' : 'editButton');
+ showElementById('smallCommentFrame');
+ } else if (mode == 'diff') {
+ showElementById('viewDiffFrame');
+ showElementById('viewRawButton');
+ showElementById(has_edited ? 'redoEditButton' : 'editButton');
+ showElementById('smallCommentFrame');
+ }
+
+ prev_mode = current_mode;
+ current_mode = mode;
}
function hideElementById(id)
@@ -184,8 +225,11 @@
<textarea name="comment" rows="5" cols="25" wrap="soft"></textarea><br>
</div>
- <input type="submit" value="Submit">
-
+ <input type="submit" value="Submit"><br><br>
+ <strong>Actions:</strong> <a href="attachment.cgi?id=[% attachid %]">View</a>
+ [% IF ispatch %]
+ | <a href="attachment.cgi?id=[% attachid %]&action=diff">Diff</a>
+ [% END %]
</small>
</td>
@@ -199,9 +243,12 @@
<script type="application/x-javascript" language="JavaScript">
<!--
if (typeof document.getElementById == "function") {
+ document.write('<iframe id="viewDiffFrame" style="height: 400px; width: 100%; display: none;"></iframe>');
document.write('<button type="button" id="editButton" onclick="editAsComment();">Edit Attachment As Comment</button>');
document.write('<button type="button" id="undoEditButton" onclick="undoEditAsComment();" style="display: none;">Undo Edit As Comment</button>');
document.write('<button type="button" id="redoEditButton" onclick="redoEditAsComment();" style="display: none;">Redo Edit As Comment</button>');
+ document.write('<button type="button" id="viewDiffButton" onclick="viewDiff();">View Attachment As Diff</button>');
+ document.write('<button type="button" id="viewRawButton" onclick="viewRaw();" style="display: none;">View Attachment As Raw</button>');
}
//-->
</script>
diff --git a/template/en/default/attachment/list.html.tmpl b/template/en/default/attachment/list.html.tmpl
index fc5852923..598f8172b 100644
--- a/template/en/default/attachment/list.html.tmpl
+++ b/template/en/default/attachment/list.html.tmpl
@@ -69,8 +69,12 @@
<td valign="top">
[% IF attachment.canedit %]
<a href="attachment.cgi?id=[% attachment.attachid %]&amp;action=edit">Edit</a>
- [% ELSE %]
- None
+ [% END %]
+ [% IF attachment.ispatch %]
+ [% IF attachment.canedit %]
+ |
+ [% END %]
+ <a href="attachment.cgi?id=[% attachment.attachid %]&amp;action=diff">Diff</a>
[% END %]
</td>
</tr>