Bug 330707: Add optional support for MarkDown
r=dkl,a=sgreen git-svn-id: svn://10.0.0.236/trunk@265524 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
parent
a337d8ca24
commit
daef60d81c
@ -1 +1 @@
|
|||||||
9116
|
9117
|
||||||
@ -1 +1 @@
|
|||||||
82346032ecfef148e78a8d19e17c5ed41ed41d10
|
ec5caa57cc14a328b8b994d49cb8def8eb95aea7
|
||||||
@ -397,6 +397,11 @@ sub logout_request {
|
|||||||
# there. Don't rely on it: use Bugzilla->user->login instead!
|
# there. Don't rely on it: use Bugzilla->user->login instead!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub markdown {
|
||||||
|
require Bugzilla::Markdown;
|
||||||
|
return $_[0]->request_cache->{markdown} ||= Bugzilla::Markdown->new();
|
||||||
|
}
|
||||||
|
|
||||||
sub job_queue {
|
sub job_queue {
|
||||||
require Bugzilla::JobQueue;
|
require Bugzilla::JobQueue;
|
||||||
return $_[0]->request_cache->{job_queue} ||= Bugzilla::JobQueue->new();
|
return $_[0]->request_cache->{job_queue} ||= Bugzilla::JobQueue->new();
|
||||||
@ -944,6 +949,10 @@ Returns the local timezone of the Bugzilla installation,
|
|||||||
as a DateTime::TimeZone object. This detection is very time
|
as a DateTime::TimeZone object. This detection is very time
|
||||||
consuming, so we cache this information for future references.
|
consuming, so we cache this information for future references.
|
||||||
|
|
||||||
|
=item C<markdown>
|
||||||
|
|
||||||
|
The current L<Markdown|Bugzilla::Markdown> object, to be used for Markdown rendering.
|
||||||
|
|
||||||
=item C<job_queue>
|
=item C<job_queue>
|
||||||
|
|
||||||
Returns a L<Bugzilla::JobQueue> that you can use for queueing jobs.
|
Returns a L<Bugzilla::JobQueue> that you can use for queueing jobs.
|
||||||
|
|||||||
@ -691,6 +691,8 @@ sub create {
|
|||||||
unless defined $params->{rep_platform};
|
unless defined $params->{rep_platform};
|
||||||
# Make sure a comment is always defined.
|
# Make sure a comment is always defined.
|
||||||
$params->{comment} = '' unless defined $params->{comment};
|
$params->{comment} = '' unless defined $params->{comment};
|
||||||
|
$params->{is_markdown} = 0
|
||||||
|
unless defined $params->{is_markdown} && $params->{is_markdown} eq '1';
|
||||||
|
|
||||||
$class->check_required_create_fields($params);
|
$class->check_required_create_fields($params);
|
||||||
$params = $class->run_create_validators($params);
|
$params = $class->run_create_validators($params);
|
||||||
@ -704,6 +706,7 @@ sub create {
|
|||||||
my $blocked = delete $params->{blocked};
|
my $blocked = delete $params->{blocked};
|
||||||
my $keywords = delete $params->{keywords};
|
my $keywords = delete $params->{keywords};
|
||||||
my $creation_comment = delete $params->{comment};
|
my $creation_comment = delete $params->{comment};
|
||||||
|
my $is_markdown = delete $params->{is_markdown};
|
||||||
my $see_also = delete $params->{see_also};
|
my $see_also = delete $params->{see_also};
|
||||||
|
|
||||||
# We don't want the bug to appear in the system until it's correctly
|
# We don't want the bug to appear in the system until it's correctly
|
||||||
@ -791,6 +794,7 @@ sub create {
|
|||||||
|
|
||||||
# We now have a bug id so we can fill this out
|
# We now have a bug id so we can fill this out
|
||||||
$creation_comment->{'bug_id'} = $bug->id;
|
$creation_comment->{'bug_id'} = $bug->id;
|
||||||
|
$creation_comment->{'is_markdown'} = $is_markdown;
|
||||||
|
|
||||||
# Insert the comment. We always insert a comment on bug creation,
|
# Insert the comment. We always insert a comment on bug creation,
|
||||||
# but sometimes it's blank.
|
# but sometimes it's blank.
|
||||||
@ -2413,7 +2417,8 @@ sub set_all {
|
|||||||
# there are lots of things that want to check if we added a comment.
|
# there are lots of things that want to check if we added a comment.
|
||||||
$self->add_comment($params->{'comment'}->{'body'},
|
$self->add_comment($params->{'comment'}->{'body'},
|
||||||
{ isprivate => $params->{'comment'}->{'is_private'},
|
{ isprivate => $params->{'comment'}->{'is_private'},
|
||||||
work_time => $params->{'work_time'} });
|
work_time => $params->{'work_time'},
|
||||||
|
is_markdown => $params->{'comment'}->{'is_markdown'} });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exists $params->{alias} && $params->{alias}{set}) {
|
if (exists $params->{alias} && $params->{alias}{set}) {
|
||||||
|
|||||||
@ -43,6 +43,7 @@ use constant DB_COLUMNS => qw(
|
|||||||
already_wrapped
|
already_wrapped
|
||||||
type
|
type
|
||||||
extra_data
|
extra_data
|
||||||
|
is_markdown
|
||||||
);
|
);
|
||||||
|
|
||||||
use constant UPDATE_COLUMNS => qw(
|
use constant UPDATE_COLUMNS => qw(
|
||||||
@ -65,6 +66,7 @@ use constant VALIDATORS => {
|
|||||||
work_time => \&_check_work_time,
|
work_time => \&_check_work_time,
|
||||||
thetext => \&_check_thetext,
|
thetext => \&_check_thetext,
|
||||||
isprivate => \&_check_isprivate,
|
isprivate => \&_check_isprivate,
|
||||||
|
is_markdown => \&Bugzilla::Object::check_boolean,
|
||||||
extra_data => \&_check_extra_data,
|
extra_data => \&_check_extra_data,
|
||||||
type => \&_check_type,
|
type => \&_check_type,
|
||||||
};
|
};
|
||||||
@ -177,6 +179,7 @@ sub body { return $_[0]->{'thetext'}; }
|
|||||||
sub bug_id { return $_[0]->{'bug_id'}; }
|
sub bug_id { return $_[0]->{'bug_id'}; }
|
||||||
sub creation_ts { return $_[0]->{'bug_when'}; }
|
sub creation_ts { return $_[0]->{'bug_when'}; }
|
||||||
sub is_private { return $_[0]->{'isprivate'}; }
|
sub is_private { return $_[0]->{'isprivate'}; }
|
||||||
|
sub is_markdown { return $_[0]->{'is_markdown'}; }
|
||||||
sub work_time {
|
sub work_time {
|
||||||
# Work time is returned as a string (see bug 607909)
|
# Work time is returned as a string (see bug 607909)
|
||||||
return 0 if $_[0]->{'work_time'} + 0 == 0;
|
return 0 if $_[0]->{'work_time'} + 0 == 0;
|
||||||
@ -274,6 +277,7 @@ sub body_full {
|
|||||||
sub set_is_private { $_[0]->set('isprivate', $_[1]); }
|
sub set_is_private { $_[0]->set('isprivate', $_[1]); }
|
||||||
sub set_type { $_[0]->set('type', $_[1]); }
|
sub set_type { $_[0]->set('type', $_[1]); }
|
||||||
sub set_extra_data { $_[0]->set('extra_data', $_[1]); }
|
sub set_extra_data { $_[0]->set('extra_data', $_[1]); }
|
||||||
|
sub set_is_markdown { $_[0]->set('is_markdown', $_[1]); }
|
||||||
|
|
||||||
sub add_tag {
|
sub add_tag {
|
||||||
my ($self, $tag) = @_;
|
my ($self, $tag) = @_;
|
||||||
@ -522,6 +526,10 @@ C<string> Time spent as related to this comment.
|
|||||||
|
|
||||||
C<boolean> Comment is marked as private.
|
C<boolean> Comment is marked as private.
|
||||||
|
|
||||||
|
=item C<is_markdown>
|
||||||
|
|
||||||
|
C<boolean> Whether this comment needs L<Markdown|Bugzilla::Markdown> rendering to be applied.
|
||||||
|
|
||||||
=item C<already_wrapped>
|
=item C<already_wrapped>
|
||||||
|
|
||||||
If this comment is stored in the database word-wrapped, this will be C<1>.
|
If this comment is stored in the database word-wrapped, this will be C<1>.
|
||||||
@ -617,6 +625,16 @@ A string, the full text of the comment as it would be displayed to an end-user.
|
|||||||
|
|
||||||
=cut
|
=cut
|
||||||
|
|
||||||
|
=head2 Modifiers
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item C<set_is_markdown>
|
||||||
|
|
||||||
|
Sets whether this comment needs L<Markdown|Bugzilla::Markdown> rendering to be applied.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
=head1 B<Methods in need of POD>
|
=head1 B<Methods in need of POD>
|
||||||
|
|
||||||
=over
|
=over
|
||||||
|
|||||||
@ -191,6 +191,8 @@ use Memoize;
|
|||||||
AUDIT_REMOVE
|
AUDIT_REMOVE
|
||||||
|
|
||||||
MOST_FREQUENT_THRESHOLD
|
MOST_FREQUENT_THRESHOLD
|
||||||
|
|
||||||
|
MARKDOWN_TAB_WIDTH
|
||||||
);
|
);
|
||||||
|
|
||||||
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
|
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
|
||||||
@ -628,6 +630,10 @@ use constant AUDIT_REMOVE => '__remove__';
|
|||||||
# on the "Most frequently reported bugs" page.
|
# on the "Most frequently reported bugs" page.
|
||||||
use constant MOST_FREQUENT_THRESHOLD => 2;
|
use constant MOST_FREQUENT_THRESHOLD => 2;
|
||||||
|
|
||||||
|
# The number of spaces used to represent each tab character
|
||||||
|
# by Markdown engine
|
||||||
|
use constant MARKDOWN_TAB_WIDTH => 2;
|
||||||
|
|
||||||
sub bz_locations {
|
sub bz_locations {
|
||||||
# Force memoize() to re-compute data per project, to avoid
|
# Force memoize() to re-compute data per project, to avoid
|
||||||
# sharing the same data across different installations.
|
# sharing the same data across different installations.
|
||||||
|
|||||||
@ -410,7 +410,8 @@ use constant ABSTRACT_SCHEMA => {
|
|||||||
DEFAULT => 'FALSE'},
|
DEFAULT => 'FALSE'},
|
||||||
type => {TYPE => 'INT2', NOTNULL => 1,
|
type => {TYPE => 'INT2', NOTNULL => 1,
|
||||||
DEFAULT => '0'},
|
DEFAULT => '0'},
|
||||||
extra_data => {TYPE => 'varchar(255)'}
|
extra_data => {TYPE => 'varchar(255)'},
|
||||||
|
is_markdown => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}
|
||||||
],
|
],
|
||||||
INDEXES => [
|
INDEXES => [
|
||||||
longdescs_bug_id_idx => [qw(bug_id work_time)],
|
longdescs_bug_id_idx => [qw(bug_id work_time)],
|
||||||
|
|||||||
@ -90,6 +90,8 @@ sub SETTINGS {
|
|||||||
bugmail_new_prefix => { options => ['on', 'off'], default => 'on' },
|
bugmail_new_prefix => { options => ['on', 'off'], default => 'on' },
|
||||||
# 2013-07-26 joshi_sunil@in.com -- Bug 669535
|
# 2013-07-26 joshi_sunil@in.com -- Bug 669535
|
||||||
possible_duplicates => { options => ['on', 'off'], default => 'on' },
|
possible_duplicates => { options => ['on', 'off'], default => 'on' },
|
||||||
|
# 2014-05-24 koosha.khajeh@gmail.com -- Bug 1014164
|
||||||
|
use_markdown => { options => ['on', 'off'], default => 'on' },
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -726,6 +726,10 @@ sub update_table_definitions {
|
|||||||
# 2014-08-11 sgreen@redhat.com - Bug 1012506
|
# 2014-08-11 sgreen@redhat.com - Bug 1012506
|
||||||
_update_alias();
|
_update_alias();
|
||||||
|
|
||||||
|
# 2014-08-14 koosha.khajeh@gmail.com - Bug 330707
|
||||||
|
$dbh->bz_add_column('longdescs', 'is_markdown',
|
||||||
|
{TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
# New --TABLE-- changes should go *** A B O V E *** this point #
|
# New --TABLE-- changes should go *** A B O V E *** this point #
|
||||||
################################################################
|
################################################################
|
||||||
|
|||||||
@ -405,6 +405,14 @@ sub OPTIONAL_MODULES {
|
|||||||
version => '0',
|
version => '0',
|
||||||
feature => ['memcached'],
|
feature => ['memcached'],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
# Markdown
|
||||||
|
{
|
||||||
|
package => 'Text-Markdown',
|
||||||
|
module => 'Text::Markdown',
|
||||||
|
version => '1.0.26',
|
||||||
|
feature => ['markdown'],
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
my $extra_modules = _get_extension_requirements('OPTIONAL_MODULES');
|
my $extra_modules = _get_extension_requirements('OPTIONAL_MODULES');
|
||||||
@ -428,6 +436,7 @@ use constant FEATURE_FILES => (
|
|||||||
'Bugzilla/JobQueue/*', 'jobqueue.pl'],
|
'Bugzilla/JobQueue/*', 'jobqueue.pl'],
|
||||||
patch_viewer => ['Bugzilla/Attachment/PatchReader.pm'],
|
patch_viewer => ['Bugzilla/Attachment/PatchReader.pm'],
|
||||||
updates => ['Bugzilla/Update.pm'],
|
updates => ['Bugzilla/Update.pm'],
|
||||||
|
markdown => ['Bugzilla/Markdown.pm'],
|
||||||
memcached => ['Bugzilla/Memcache.pm'],
|
memcached => ['Bugzilla/Memcache.pm'],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
493
mozilla/webtools/bugzilla/Bugzilla/Markdown.pm
Normal file
493
mozilla/webtools/bugzilla/Bugzilla/Markdown.pm
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
#
|
||||||
|
# This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
# defined by the Mozilla Public License, v. 2.0.
|
||||||
|
|
||||||
|
package Bugzilla::Markdown;
|
||||||
|
|
||||||
|
use 5.10.1;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Bugzilla::Constants;
|
||||||
|
use Bugzilla::Template;
|
||||||
|
|
||||||
|
use Digest::MD5 qw(md5_hex);
|
||||||
|
|
||||||
|
use parent qw(Text::Markdown);
|
||||||
|
|
||||||
|
@Bugzilla::Markdown::EXPORT = qw(new);
|
||||||
|
|
||||||
|
# Regex to match balanced [brackets]. See Friedl's
|
||||||
|
# "Mastering Regular Expressions", 2nd Ed., pp. 328-331.
|
||||||
|
our ($g_nested_brackets, $g_nested_parens);
|
||||||
|
$g_nested_brackets = qr{
|
||||||
|
(?> # Atomic matching
|
||||||
|
[^\[\]]+ # Anything other than brackets
|
||||||
|
|
|
||||||
|
\[
|
||||||
|
(??{ $g_nested_brackets }) # Recursive set of nested brackets
|
||||||
|
\]
|
||||||
|
)*
|
||||||
|
}x;
|
||||||
|
# Doesn't allow for whitespace, because we're using it to match URLs:
|
||||||
|
$g_nested_parens = qr{
|
||||||
|
(?> # Atomic matching
|
||||||
|
[^()\s]+ # Anything other than parens or whitespace
|
||||||
|
|
|
||||||
|
\(
|
||||||
|
(??{ $g_nested_parens }) # Recursive set of nested brackets
|
||||||
|
\)
|
||||||
|
)*
|
||||||
|
}x;
|
||||||
|
|
||||||
|
our %g_escape_table;
|
||||||
|
foreach my $char (split //, '\\`*_{}[]()>#+-.!~') {
|
||||||
|
$g_escape_table{$char} = md5_hex($char);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my $invocant = shift;
|
||||||
|
my $class = ref $invocant || $invocant;
|
||||||
|
return $class->SUPER::new(tab_width => MARKDOWN_TAB_WIDTH,
|
||||||
|
# Bugzilla uses HTML not XHTML
|
||||||
|
empty_element_suffix => '>');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub markdown {
|
||||||
|
my $self = shift;
|
||||||
|
my $text = shift;
|
||||||
|
my $user = Bugzilla->user;
|
||||||
|
|
||||||
|
if (Bugzilla->feature('markdown')
|
||||||
|
&& $user->settings->{use_markdown}->{is_enabled}
|
||||||
|
&& $user->setting('use_markdown') eq 'on')
|
||||||
|
{
|
||||||
|
return $self->SUPER::markdown($text, @_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Bugzilla::Template::quoteUrls($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _Markdown {
|
||||||
|
my $self = shift;
|
||||||
|
my $text = shift;
|
||||||
|
|
||||||
|
$text = Bugzilla::Template::quoteUrls($text);
|
||||||
|
|
||||||
|
return $self->SUPER::_Markdown($text, @_);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _RunSpanGamut {
|
||||||
|
# These are all the transformations that occur *within* block-level
|
||||||
|
# tags like paragraphs, headers, and list items.
|
||||||
|
|
||||||
|
my ($self, $text) = @_;
|
||||||
|
|
||||||
|
$text = $self->_DoCodeSpans($text);
|
||||||
|
$text = $self->_EscapeSpecialCharsWithinTagAttributes($text);
|
||||||
|
$text = $self->_EscapeSpecialChars($text);
|
||||||
|
|
||||||
|
$text = $self->_DoAnchors($text);
|
||||||
|
|
||||||
|
# Strikethroughs is Bugzilla's extension
|
||||||
|
$text = $self->_DoStrikethroughs($text);
|
||||||
|
|
||||||
|
$text = $self->_DoAutoLinks($text);
|
||||||
|
$text = $self->_EncodeAmpsAndAngles($text);
|
||||||
|
$text = $self->_DoItalicsAndBold($text);
|
||||||
|
|
||||||
|
$text =~ s/ {2,}\n/ <br$self->{empty_element_suffix}\n/g;
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Override to check for HTML-escaped <>" chars.
|
||||||
|
sub _StripLinkDefinitions {
|
||||||
|
#
|
||||||
|
# Strips link definitions from text, stores the URLs and titles in
|
||||||
|
# hash references.
|
||||||
|
#
|
||||||
|
my ($self, $text) = @_;
|
||||||
|
my $less_than_tab = $self->{tab_width} - 1;
|
||||||
|
|
||||||
|
# Link defs are in the form: ^[id]: url "optional title"
|
||||||
|
while ($text =~ s{
|
||||||
|
^[ ]{0,$less_than_tab}\[(.+)\]: # id = \$1
|
||||||
|
[ \t]*
|
||||||
|
\n? # maybe *one* newline
|
||||||
|
[ \t]*
|
||||||
|
(?:<)?<a\s+href="(.+?)">\2</a>(?:>)? # url = \$2
|
||||||
|
[ \t]*
|
||||||
|
\n? # maybe one newline
|
||||||
|
[ \t]*
|
||||||
|
(?:
|
||||||
|
(?<=\s) # lookbehind for whitespace
|
||||||
|
(?:"|\()
|
||||||
|
(.+?) # title = \$3
|
||||||
|
(?:"|\))
|
||||||
|
[ \t]*
|
||||||
|
)? # title is optional
|
||||||
|
(?:\n+|\Z)
|
||||||
|
}{}omx) {
|
||||||
|
$self->{_urls}{lc $1} = $self->_EncodeAmpsAndAngles( $2 ); # Link IDs are case-insensitive
|
||||||
|
if ($3) {
|
||||||
|
$self->{_titles}{lc $1} = $3;
|
||||||
|
$self->{_titles}{lc $1} =~ s/"/"/g;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
# We need to look for HTML-escaped '<' and '>' (i.e. < and >).
|
||||||
|
# We also remove Email linkification from the original implementation
|
||||||
|
# as it is already done in Bugzilla's quoteUrls().
|
||||||
|
sub _DoAutoLinks {
|
||||||
|
my ($self, $text) = @_;
|
||||||
|
|
||||||
|
$text =~ s{(?:<|<)((?:https?|ftp):[^'">\s]+)(?:>|>)}{<a href="$1">$1</a>}gi;
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
# The main reasons for overriding this method are
|
||||||
|
# resolving URL conflicts with Bugzilla's quoteUrls()
|
||||||
|
# and also changing '"' to '"' in regular expressions wherever needed.
|
||||||
|
sub _DoAnchors {
|
||||||
|
#
|
||||||
|
# Turn Markdown link shortcuts into <a> tags.
|
||||||
|
#
|
||||||
|
my ($self, $text) = @_;
|
||||||
|
|
||||||
|
# We revert linkifications of non-email links and only
|
||||||
|
# those links whose URL and title are the same because
|
||||||
|
# this way we can be sure that link is generated by quoteUrls()
|
||||||
|
$text =~ s@<a \s+ href="(?! mailto ) (.+?)">\1</a>@$1@xmg;
|
||||||
|
|
||||||
|
#
|
||||||
|
# First, handle reference-style links: [link text] [id]
|
||||||
|
#
|
||||||
|
$text =~ s{
|
||||||
|
( # wrap whole match in $1
|
||||||
|
\[
|
||||||
|
($g_nested_brackets) # link text = $2
|
||||||
|
\]
|
||||||
|
|
||||||
|
[ ]? # one optional space
|
||||||
|
(?:\n[ ]*)? # one optional newline followed by spaces
|
||||||
|
|
||||||
|
\[
|
||||||
|
(.*?) # id = $3
|
||||||
|
\]
|
||||||
|
)
|
||||||
|
}{
|
||||||
|
my $whole_match = $1;
|
||||||
|
my $link_text = $2;
|
||||||
|
my $link_id = lc $3;
|
||||||
|
|
||||||
|
if ($link_id eq "") {
|
||||||
|
$link_id = lc $link_text; # for shortcut links like [this][].
|
||||||
|
}
|
||||||
|
|
||||||
|
$link_id =~ s{[ ]*\n}{ }g; # turn embedded newlines into spaces
|
||||||
|
|
||||||
|
$self->_GenerateAnchor($whole_match, $link_text, $link_id);
|
||||||
|
}xsge;
|
||||||
|
|
||||||
|
#
|
||||||
|
# Next, inline-style links: [link text](url "optional title")
|
||||||
|
#
|
||||||
|
$text =~ s{
|
||||||
|
( # wrap whole match in $1
|
||||||
|
\[
|
||||||
|
($g_nested_brackets) # link text = $2
|
||||||
|
\]
|
||||||
|
\( # literal paren
|
||||||
|
[ \t]*
|
||||||
|
($g_nested_parens) # href = $3
|
||||||
|
[ \t]*
|
||||||
|
( # $4
|
||||||
|
("|') # quote char = $5
|
||||||
|
(.*?) # Title = $6
|
||||||
|
\5 # matching quote
|
||||||
|
[ \t]* # ignore any spaces/tabs between closing quote and )
|
||||||
|
)? # title is optional
|
||||||
|
\)
|
||||||
|
)
|
||||||
|
}{
|
||||||
|
my $result;
|
||||||
|
my $whole_match = $1;
|
||||||
|
my $link_text = $2;
|
||||||
|
my $url = $3;
|
||||||
|
my $title = $6;
|
||||||
|
|
||||||
|
# Remove Bugzilla quoteUrls() linkification
|
||||||
|
if ($url =~ /^a href="/ && $url =~ m|</a$|) {
|
||||||
|
$url =~ s/^[^>]+>//;
|
||||||
|
$url =~ s@</a$@@;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Limit URL to HTTP/HTTPS links
|
||||||
|
$url = "http://$url" unless $url =~ m!^https?://!i;
|
||||||
|
|
||||||
|
$self->_GenerateAnchor($whole_match, $link_text, undef, $url, $title);
|
||||||
|
}xsge;
|
||||||
|
|
||||||
|
#
|
||||||
|
# Last, handle reference-style shortcuts: [link text]
|
||||||
|
# These must come last in case you've also got [link test][1]
|
||||||
|
# or [link test](/foo)
|
||||||
|
#
|
||||||
|
$text =~ s{
|
||||||
|
( # wrap whole match in $1
|
||||||
|
\[
|
||||||
|
([^\[\]]+) # link text = $2; can't contain '[' or ']'
|
||||||
|
\]
|
||||||
|
)
|
||||||
|
}{
|
||||||
|
my $result;
|
||||||
|
my $whole_match = $1;
|
||||||
|
my $link_text = $2;
|
||||||
|
(my $link_id = lc $2) =~ s{[ ]*\n}{ }g; # lower-case and turn embedded newlines into spaces
|
||||||
|
|
||||||
|
$self->_GenerateAnchor($whole_match, $link_text, $link_id);
|
||||||
|
}xsge;
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
# The purpose of overriding this function is to add support
|
||||||
|
# for a Github Flavored Markdown (GFM) feature called 'Multiple
|
||||||
|
# underscores in words'. The standard markdown specification
|
||||||
|
# specifies the underscore for making the text emphasized/bold.
|
||||||
|
# However, some variable names in programming languages contain underscores
|
||||||
|
# and we do not want a part of those variables to look emphasized/bold.
|
||||||
|
# Instead, we render them as the way they originally are.
|
||||||
|
sub _DoItalicsAndBold {
|
||||||
|
my ($self, $text) = @_;
|
||||||
|
|
||||||
|
# Handle at beginning of lines:
|
||||||
|
$text =~ s{ (^__ (?=\S) (.+?[*_]*) (?<=\S) __ (?!\S)) }
|
||||||
|
{
|
||||||
|
my $result = _has_multiple_underscores($2) ? $1 : "<strong>$2</strong>";
|
||||||
|
$result;
|
||||||
|
}gsxe;
|
||||||
|
|
||||||
|
$text =~ s{ ^\*\* (?=\S) (.+?[*_]*) (?<=\S) \*\* }{<strong>$1</strong>}gsx;
|
||||||
|
|
||||||
|
$text =~ s{ (^_ (?=\S) (.+?) (?<=\S) _ (?!\S)) }
|
||||||
|
{
|
||||||
|
my $result = _has_multiple_underscores($2) ? $1 : "<em>$2</em>";
|
||||||
|
$result;
|
||||||
|
}gsxe;
|
||||||
|
|
||||||
|
$text =~ s{ ^\* (?=\S) (.+?) (?<=\S) \* }{<em>$1</em>}gsx;
|
||||||
|
|
||||||
|
# <strong> must go first:
|
||||||
|
$text =~ s{ ( (?<=\W) __ (?=\S) (.+?[*_]*) (?<=\S) __ (?!\S) ) }
|
||||||
|
{
|
||||||
|
my $result = _has_multiple_underscores($2) ? $1 : "<strong>$2</strong>";
|
||||||
|
$result;
|
||||||
|
}gsxe;
|
||||||
|
|
||||||
|
|
||||||
|
$text =~ s{ (?<=\W) \*\* (?=\S) (.+?[*_]*) (?<=\S) \*\* }{<strong>$1</strong>}gsx;
|
||||||
|
|
||||||
|
$text =~ s{ ( (?<=\W) _ (?=\S) (.+?) (?<=\S) _ (?!\S) ) }
|
||||||
|
{
|
||||||
|
my $result = _has_multiple_underscores($2) ? $1 : "<em>$2</em>";
|
||||||
|
$result;
|
||||||
|
}gsxe;
|
||||||
|
|
||||||
|
$text =~ s{ (?<=\W) \* (?=\S) (.+?) (?<=\S) \* }{<em>$1</em>}gsx;
|
||||||
|
|
||||||
|
# And now, a second pass to catch nested strong and emphasis special cases
|
||||||
|
$text =~ s{ ( (?<=\W) __ (?=\S) (.+?[*_]*) (?<=\S) __ (\S*) ) }
|
||||||
|
{
|
||||||
|
my $result = _has_multiple_underscores($3) ? $1 : "<strong>$2</strong>$3";
|
||||||
|
$result;
|
||||||
|
}gsxe;
|
||||||
|
|
||||||
|
$text =~ s{ (?<=\W) \*\* (?=\S) (.+?[*_]*) (?<=\S) \*\* }{<strong>$1</strong>}gsx;
|
||||||
|
$text =~ s{ ( (?<=\W) _ (?=\S) (.+?) (?<=\S) _ (\S*) ) }
|
||||||
|
{
|
||||||
|
my $result = _has_multiple_underscores($3) ? $1 : "<em>$2</em>$3";
|
||||||
|
$result;
|
||||||
|
}gsxe;
|
||||||
|
|
||||||
|
$text =~ s{ (?<=\W) \* (?=\S) (.+?) (?<=\S) \* }{<em>$1</em>}gsx;
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Override this function to ignore 'wrap_in_p_tags' from
|
||||||
|
# the caller and to not generate <p> tags around the output.
|
||||||
|
sub _FormParagraphs {
|
||||||
|
my ($self, $text) = @_;
|
||||||
|
return $self->SUPER::_FormParagraphs($text, { wrap_in_p_tags => 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _DoStrikethroughs {
|
||||||
|
my ($self, $text) = @_;
|
||||||
|
|
||||||
|
$text =~ s{ ^ ~~ (?=\S) ([^~]+?) (?<=\S) ~~ (?!~) }{<del>$1</del>}gsx;
|
||||||
|
$text =~ s{ (?<=_|[^~\w]) ~~ (?=\S) ([^~]+?) (?<=\S) ~~ (?!~) }{<del>$1</del>}gsx;
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
# The original _DoCodeSpans() uses the 's' modifier in its regex
|
||||||
|
# which prevents _DoCodeBlocks() to match GFM fenced code blocks.
|
||||||
|
# We copy the code from the original implementation and remove the
|
||||||
|
# 's' modifier from it.
|
||||||
|
sub _DoCodeSpans {
|
||||||
|
my ($self, $text) = @_;
|
||||||
|
|
||||||
|
$text =~ s@
|
||||||
|
(?<!\\) # Character before opening ` can't be a backslash
|
||||||
|
(`+) # $1 = Opening run of `
|
||||||
|
(.+?) # $2 = The code block
|
||||||
|
(?<!`)
|
||||||
|
\1 # Matching closer
|
||||||
|
(?!`)
|
||||||
|
@
|
||||||
|
my $c = "$2";
|
||||||
|
$c =~ s/^[ \t]*//g; # leading whitespace
|
||||||
|
$c =~ s/[ \t]*$//g; # trailing whitespace
|
||||||
|
$c = $self->_EncodeCode($c);
|
||||||
|
"<code>$c</code>";
|
||||||
|
@egx;
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Override to add GFM Fenced Code Blocks
|
||||||
|
sub _DoCodeBlocks {
|
||||||
|
my ($self, $text) = @_;
|
||||||
|
|
||||||
|
$text =~ s{
|
||||||
|
^ `{3,} [\s\t]* \n
|
||||||
|
( # $1 = the entire code block
|
||||||
|
(?: .* \n+)+?
|
||||||
|
)
|
||||||
|
`{3,} [\s\t]* $
|
||||||
|
}{
|
||||||
|
my $codeblock = $1;
|
||||||
|
my $result;
|
||||||
|
|
||||||
|
$codeblock = $self->_EncodeCode($codeblock);
|
||||||
|
$codeblock = $self->_Detab($codeblock);
|
||||||
|
$codeblock =~ s/\n\z//; # remove the trailing newline
|
||||||
|
|
||||||
|
$result = "\n\n<pre><code>" . $codeblock . "</code></pre>\n\n";
|
||||||
|
$result;
|
||||||
|
}egmx;
|
||||||
|
|
||||||
|
# And now do the standard code blocks
|
||||||
|
$text = $self->SUPER::_DoCodeBlocks($text);
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _EncodeCode {
|
||||||
|
my ($self, $text) = @_;
|
||||||
|
|
||||||
|
# We need to unescape the escaped HTML characters in code blocks.
|
||||||
|
# These are the reverse of the escapings done in Bugzilla::Util::html_quote()
|
||||||
|
$text =~ s/</</g;
|
||||||
|
$text =~ s/>/>/g;
|
||||||
|
$text =~ s/"/"/g;
|
||||||
|
$text =~ s/@/@/g;
|
||||||
|
# '&' substitution must be the last one, otherwise a literal like '>'
|
||||||
|
# will turn to '>' because '&' is already changed to '&' in Bugzilla::Util::html_quote().
|
||||||
|
# In other words, html_quote() will change '>' to '&gt;' and then we will
|
||||||
|
# change '&gt' -> '>' -> '>' if we write this substitution as the first one.
|
||||||
|
$text =~ s/&/&/g;
|
||||||
|
$text = $self->SUPER::_EncodeCode($text);
|
||||||
|
$text =~ s/~/$g_escape_table{'~'}/go;
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _EncodeBackslashEscapes {
|
||||||
|
my ($self, $text) = @_;
|
||||||
|
|
||||||
|
$text = $self->SUPER::_EncodeBackslashEscapes($text);
|
||||||
|
$text =~ s/\\~/$g_escape_table{'~'}/go;
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _UnescapeSpecialChars {
|
||||||
|
my ($self, $text) = @_;
|
||||||
|
|
||||||
|
$text = $self->SUPER::_UnescapeSpecialChars($text);
|
||||||
|
$text =~ s/$g_escape_table{'~'}/~/go;
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if the passed string is of the form multiple_underscores_in_a_word.
|
||||||
|
# To check that, we first need to make sure that the string does not contain
|
||||||
|
# any white-space. Then, if the string is composed of non-space chunks which
|
||||||
|
# are bound together with underscores, the string has the desired form.
|
||||||
|
sub _has_multiple_underscores {
|
||||||
|
my $string = shift;
|
||||||
|
return 0 unless defined($string) && length($string);
|
||||||
|
return 0 if $string =~ /[\t\s]+/;
|
||||||
|
return 1 if scalar (split /_/, $string) > 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
|
__END__
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
Bugzilla::Markdown - Generates HTML output from structured plain-text input.
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
use Bugzilla::Markdown;
|
||||||
|
|
||||||
|
my $markdown = Bugzilla::Markdown->new();
|
||||||
|
print $markdown->markdown($text);
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
Bugzilla::Markdown implements a Markdown engine that produces
|
||||||
|
an HTML-based output from a given plain-text input.
|
||||||
|
|
||||||
|
The majority of the implementation is done by C<Text::Markdown>
|
||||||
|
CPAN module. It also applies the linkifications done in L<Bugzilla::Template>
|
||||||
|
to the input resulting in an output which is a combination of both Markdown
|
||||||
|
structures and those defined by Bugzilla itself.
|
||||||
|
|
||||||
|
=head2 Accessors
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item C<markdown>
|
||||||
|
|
||||||
|
C<string> Produces an HTML-based output string based on the structures
|
||||||
|
and format defined in the given plain-text input.
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item B<Params>
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item C<text>
|
||||||
|
|
||||||
|
C<string> A plain-text string which includes Markdown structures.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=back
|
||||||
@ -807,6 +807,23 @@ sub create {
|
|||||||
1
|
1
|
||||||
],
|
],
|
||||||
|
|
||||||
|
markdown => [ sub {
|
||||||
|
my ($context, $bug, $comment, $user) = @_;
|
||||||
|
return sub {
|
||||||
|
my $text = shift;
|
||||||
|
return unless $text;
|
||||||
|
|
||||||
|
if ((ref($comment) eq 'HASH' && $comment->{is_markdown})
|
||||||
|
|| (ref($comment) eq 'Bugzilla::Comment' && $comment->is_markdown))
|
||||||
|
{
|
||||||
|
return Bugzilla->markdown->markdown($text);
|
||||||
|
}
|
||||||
|
return quoteUrls($text, $bug, $comment, $user);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
1
|
||||||
|
],
|
||||||
|
|
||||||
bug_link => [ sub {
|
bug_link => [ sub {
|
||||||
my ($context, $bug, $options) = @_;
|
my ($context, $bug, $options) = @_;
|
||||||
return sub {
|
return sub {
|
||||||
|
|||||||
@ -331,7 +331,9 @@ sub render_comment {
|
|||||||
Bugzilla->switch_to_shadow_db();
|
Bugzilla->switch_to_shadow_db();
|
||||||
my $bug = $params->{id} ? Bugzilla::Bug->check($params->{id}) : undef;
|
my $bug = $params->{id} ? Bugzilla::Bug->check($params->{id}) : undef;
|
||||||
|
|
||||||
my $tmpl = '[% text FILTER quoteUrls(bug) %]';
|
my $markdown = $params->{markdown} ? 1 : 0;
|
||||||
|
my $tmpl = $markdown ? '[% text FILTER markdown(bug, { is_markdown => 1 }) %]' : '[% text FILTER markdown(bug) %]';
|
||||||
|
|
||||||
my $html;
|
my $html;
|
||||||
my $template = Bugzilla->template;
|
my $template = Bugzilla->template;
|
||||||
$template->process(
|
$template->process(
|
||||||
|
|||||||
@ -785,6 +785,29 @@ Don't use sigs in comments. Signing your name ("Bill") is acceptable,
|
|||||||
if you do it out of habit, but full mail/news-style
|
if you do it out of habit, but full mail/news-style
|
||||||
four line ASCII art creations are not.
|
four line ASCII art creations are not.
|
||||||
|
|
||||||
|
.. _markdown:
|
||||||
|
|
||||||
|
Markdown
|
||||||
|
--------
|
||||||
|
|
||||||
|
Markdown lets you write your comments in a structured plain-text format and
|
||||||
|
have your comments generated as HTML. For example, you may use Markdown for
|
||||||
|
making a part of your comment look italic or bold in the generated HTML. Bugzilla
|
||||||
|
supports most of the structures defined by `standard Markdown <http://daringfireball.net/projects/markdown/basics>`_.
|
||||||
|
but does NOT support inline images and inline HTML.
|
||||||
|
|
||||||
|
Additionally, three Github Flavored Markdown features are supported.
|
||||||
|
|
||||||
|
- `Multiple underscores in words <https://help.github.com/articles/github-flavored-markdown#multiple-underscores-in-words>`_
|
||||||
|
|
||||||
|
- `strikethrough <https://help.github.com/articles/github-flavored-markdown#strikethrough>`_
|
||||||
|
|
||||||
|
- `fenced code blocks <https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks>`_
|
||||||
|
|
||||||
|
To use the Markdown feature, make sure that ``Enable Markdown support for comments`` is set to ``on``
|
||||||
|
in your :ref:`userpreferences` and that you also check the ``Use Markdown for this comment`` option below
|
||||||
|
the comment box when you want to submit a new comment.
|
||||||
|
|
||||||
.. _comment-wrapping:
|
.. _comment-wrapping:
|
||||||
|
|
||||||
Server-Side Comment Wrapping
|
Server-Side Comment Wrapping
|
||||||
|
|||||||
@ -979,11 +979,13 @@ function initDirtyFieldTracking() {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var last_comment_text = '';
|
var last_comment_text = '';
|
||||||
|
var last_markdown_cb_value = null;
|
||||||
|
|
||||||
function show_comment_preview(bug_id) {
|
function show_comment_preview(bug_id) {
|
||||||
var Dom = YAHOO.util.Dom;
|
var Dom = YAHOO.util.Dom;
|
||||||
var comment = document.getElementById('comment');
|
var comment = document.getElementById('comment');
|
||||||
var preview = document.getElementById('comment_preview');
|
var preview = document.getElementById('comment_preview');
|
||||||
|
var markdown_cb = document.getElementById('use_markdown');
|
||||||
|
|
||||||
if (!comment || !preview) return;
|
if (!comment || !preview) return;
|
||||||
if (Dom.hasClass('comment_preview_tab', 'active_comment_tab')) return;
|
if (Dom.hasClass('comment_preview_tab', 'active_comment_tab')) return;
|
||||||
@ -1003,7 +1005,7 @@ function show_comment_preview(bug_id) {
|
|||||||
|
|
||||||
Dom.addClass('comment_preview_error', 'bz_default_hidden');
|
Dom.addClass('comment_preview_error', 'bz_default_hidden');
|
||||||
|
|
||||||
if (last_comment_text == comment.value)
|
if (last_comment_text == comment.value && last_markdown_cb_value == markdown_cb.checked)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Dom.addClass('comment_preview_text', 'bz_default_hidden');
|
Dom.addClass('comment_preview_text', 'bz_default_hidden');
|
||||||
@ -1024,6 +1026,7 @@ function show_comment_preview(bug_id) {
|
|||||||
Dom.addClass('comment_preview_loading', 'bz_default_hidden');
|
Dom.addClass('comment_preview_loading', 'bz_default_hidden');
|
||||||
Dom.removeClass('comment_preview_text', 'bz_default_hidden');
|
Dom.removeClass('comment_preview_text', 'bz_default_hidden');
|
||||||
last_comment_text = comment.value;
|
last_comment_text = comment.value;
|
||||||
|
last_markdown_cb_value = markdown_cb.checked;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
failure: function(res) {
|
failure: function(res) {
|
||||||
@ -1039,7 +1042,8 @@ function show_comment_preview(bug_id) {
|
|||||||
params: {
|
params: {
|
||||||
Bugzilla_api_token: BUGZILLA.api_token,
|
Bugzilla_api_token: BUGZILLA.api_token,
|
||||||
id: bug_id,
|
id: bug_id,
|
||||||
text: comment.value
|
text: comment.value,
|
||||||
|
markdown: (markdown_cb != null) && markdown_cb.checked ? 1 : 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@ -118,6 +118,7 @@ foreach my $field (qw(cc groups)) {
|
|||||||
$bug_params{$field} = [$cgi->param($field)];
|
$bug_params{$field} = [$cgi->param($field)];
|
||||||
}
|
}
|
||||||
$bug_params{'comment'} = $comment;
|
$bug_params{'comment'} = $comment;
|
||||||
|
$bug_params{'is_markdown'} = $cgi->param('use_markdown');
|
||||||
|
|
||||||
my @multi_selects = grep {$_->type == FIELD_TYPE_MULTI_SELECT && $_->enter_bug}
|
my @multi_selects = grep {$_->type == FIELD_TYPE_MULTI_SELECT && $_->enter_bug}
|
||||||
Bugzilla->active_custom_fields;
|
Bugzilla->active_custom_fields;
|
||||||
|
|||||||
@ -233,9 +233,13 @@ if (should_set('keywords')) {
|
|||||||
$set_all_fields{keywords}->{$action} = $cgi->param('keywords');
|
$set_all_fields{keywords}->{$action} = $cgi->param('keywords');
|
||||||
}
|
}
|
||||||
if (should_set('comment')) {
|
if (should_set('comment')) {
|
||||||
|
my $is_markdown = ($user->settings->{use_markdown}->{is_enabled} &&
|
||||||
|
$cgi->param('use_markdown') eq '1') ? 1 : 0;
|
||||||
|
|
||||||
$set_all_fields{comment} = {
|
$set_all_fields{comment} = {
|
||||||
body => scalar $cgi->param('comment'),
|
body => scalar $cgi->param('comment'),
|
||||||
is_private => scalar $cgi->param('comment_is_private'),
|
is_private => scalar $cgi->param('comment_is_private'),
|
||||||
|
is_markdown => $is_markdown,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (should_set('see_also')) {
|
if (should_set('see_also')) {
|
||||||
|
|||||||
@ -728,7 +728,7 @@ input.required, select.required, span.required_explanation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#comment {
|
#comment {
|
||||||
margin: 0px 0px 1em 0px;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************/
|
/*******************/
|
||||||
|
|||||||
@ -88,6 +88,7 @@ foreach my $include_path (@include_paths) {
|
|||||||
wrap_comment => sub { return $_ },
|
wrap_comment => sub { return $_ },
|
||||||
none => sub { return $_ } ,
|
none => sub { return $_ } ,
|
||||||
ics => [ sub { return sub { return $_; } }, 1] ,
|
ics => [ sub { return sub { return $_; } }, 1] ,
|
||||||
|
markdown => sub { return $_ } ,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -212,7 +212,7 @@ sub directive_ok {
|
|||||||
return 1 if $directive =~ /FILTER\ (html|csv|js|base64|css_class_quote|ics|
|
return 1 if $directive =~ /FILTER\ (html|csv|js|base64|css_class_quote|ics|
|
||||||
quoteUrls|time|uri|xml|lower|html_light|
|
quoteUrls|time|uri|xml|lower|html_light|
|
||||||
obsolete|inactive|closed|unitconvert|
|
obsolete|inactive|closed|unitconvert|
|
||||||
txt|html_linebreak|none)\b/x;
|
txt|html_linebreak|markdown|none)\b/x;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,3 +35,11 @@
|
|||||||
<pre id="comment_preview_text" class="bz_comment_text"></pre>
|
<pre id="comment_preview_text" class="bz_comment_text"></pre>
|
||||||
</div>
|
</div>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
|
[% IF feature_enabled('markdown') AND user.settings.use_markdown.value == 'on' %]
|
||||||
|
<div id="comment_markdown">
|
||||||
|
<input type="checkbox" name="use_markdown" id="use_markdown" value="1"
|
||||||
|
[% "checked=\"checked\"" IF user.settings.use_markdown.value == 'on' %] >
|
||||||
|
<label id="use_markdown_label" for="use_markdown">Use Markdown for this [% terms.comment %]</label>
|
||||||
|
</div>
|
||||||
|
[% END %]
|
||||||
|
|||||||
@ -229,7 +229,7 @@
|
|||||||
[% IF mode == "edit" || comment.collapsed %]
|
[% IF mode == "edit" || comment.collapsed %]
|
||||||
id="comment_text_[% comment.count FILTER none %]"
|
id="comment_text_[% comment.count FILTER none %]"
|
||||||
[% END %]>
|
[% END %]>
|
||||||
[%- comment_text FILTER quoteUrls(bug, comment) -%]
|
[%- comment_text FILTER markdown(bug, comment) -%]
|
||||||
</pre>
|
</pre>
|
||||||
[% Hook.process('a_comment-end', 'bug/comments.html.tmpl') %]
|
[% Hook.process('a_comment-end', 'bug/comments.html.tmpl') %]
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
on [% "$terms.bug $bug.id" FILTER bug_link(bug, { full_url => 1, user => to_user }) FILTER none %]
|
on [% "$terms.bug $bug.id" FILTER bug_link(bug, { full_url => 1, user => to_user }) FILTER none %]
|
||||||
from [% INCLUDE global/user.html.tmpl user = to_user, who = comment.author %]</b>
|
from [% INCLUDE global/user.html.tmpl user = to_user, who = comment.author %]</b>
|
||||||
[% END %]
|
[% END %]
|
||||||
<pre>[% comment.body_full({ wrap => 1 }) FILTER quoteUrls(bug, comment, to_user) %]</pre>
|
<pre>[% comment.body_full({ wrap => 1 }) FILTER markdown(bug, comment, to_user) %]</pre>
|
||||||
</div>
|
</div>
|
||||||
[% END %]
|
[% END %]
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
# [% foo.push() %]
|
# [% foo.push() %]
|
||||||
# TT loop variables - [% loop.count %]
|
# TT loop variables - [% loop.count %]
|
||||||
# Already-filtered stuff - [% wibble FILTER html %]
|
# Already-filtered stuff - [% wibble FILTER html %]
|
||||||
# where the filter is one of html|csv|js|quoteUrls|time|uri|xml|none
|
# where the filter is one of html|csv|js|quoteUrls|time|uri|xml|markdown|none
|
||||||
|
|
||||||
%::safe = (
|
%::safe = (
|
||||||
|
|
||||||
|
|||||||
@ -44,6 +44,7 @@
|
|||||||
"requestee_cc" => "Automatically add me to the CC list of $terms.bugs I am requested to review",
|
"requestee_cc" => "Automatically add me to the CC list of $terms.bugs I am requested to review",
|
||||||
"bugmail_new_prefix" => "Add 'New:' to subject line of email sent when a new $terms.bug is filed",
|
"bugmail_new_prefix" => "Add 'New:' to subject line of email sent when a new $terms.bug is filed",
|
||||||
"possible_duplicates" => "Display possible duplicates when reporting a new $terms.bug",
|
"possible_duplicates" => "Display possible duplicates when reporting a new $terms.bug",
|
||||||
|
"use_markdown" => "Enable Markdown support for $terms.comments"
|
||||||
}
|
}
|
||||||
%]
|
%]
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
<pre class="bz_comment_text">
|
<pre class="bz_comment_text">
|
||||||
[%- cgi.param("text") FILTER quoteUrls FILTER html -%]
|
[%- cgi.param("text") FILTER markdown FILTER html -%]
|
||||||
</pre>
|
</pre>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
<pre class="bz_comment_text">
|
<pre class="bz_comment_text">
|
||||||
[%- cgi.param("text") FILTER quoteUrls -%]
|
[%- cgi.param("text") FILTER markdown -%]
|
||||||
</pre>
|
</pre>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@ -102,6 +102,7 @@ END
|
|||||||
feature_xmlrpc => 'XML-RPC Interface',
|
feature_xmlrpc => 'XML-RPC Interface',
|
||||||
feature_detect_charset => 'Automatic charset detection for text attachments',
|
feature_detect_charset => 'Automatic charset detection for text attachments',
|
||||||
feature_typesniffer => 'Sniff MIME type of attachments',
|
feature_typesniffer => 'Sniff MIME type of attachments',
|
||||||
|
feature_markdown => 'Markdown syntax support for comments',
|
||||||
|
|
||||||
file_remove => 'Removing ##name##...',
|
file_remove => 'Removing ##name##...',
|
||||||
file_rename => 'Renaming ##from## to ##to##...',
|
file_rename => 'Renaming ##from## to ##to##...',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user