Commit ec5caa57 authored by Koosha KM's avatar Koosha KM Committed by David Lawrence

Bug 330707: Add optional support for MarkDown

r=dkl,a=sgreen
parent 82346032
...@@ -397,6 +397,11 @@ sub logout_request { ...@@ -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, ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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( ...@@ -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 => { ...@@ -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'}; } ...@@ -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 { ...@@ -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. ...@@ -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. ...@@ -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; ...@@ -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__'; ...@@ -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 => { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 => ( ...@@ -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'],
); );
......
...@@ -807,6 +807,23 @@ sub create { ...@@ -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 { ...@@ -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, ...@@ -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() { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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)) { ...@@ -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')) { ...@@ -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 { ...@@ -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) { ...@@ -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 { ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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,7 +44,8 @@ ...@@ -44,7 +44,8 @@
"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"
}
%] %]
[% Hook.process('settings') %] [% Hook.process('settings') %]
...@@ -18,7 +18,7 @@ ...@@ -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 @@ ...@@ -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 ...@@ -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##...',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment