Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
bugzilla
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
etersoft
bugzilla
Commits
ec5caa57
Commit
ec5caa57
authored
Aug 28, 2014
by
Koosha KM
Committed by
David Lawrence
Aug 28, 2014
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Bug 330707: Add optional support for MarkDown
r=dkl,a=sgreen
parent
82346032
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
624 additions
and
15 deletions
+624
-15
Bugzilla.pm
Bugzilla.pm
+9
-0
Bug.pm
Bugzilla/Bug.pm
+6
-1
Comment.pm
Bugzilla/Comment.pm
+18
-0
Constants.pm
Bugzilla/Constants.pm
+6
-0
Schema.pm
Bugzilla/DB/Schema.pm
+2
-1
Install.pm
Bugzilla/Install.pm
+2
-0
DB.pm
Bugzilla/Install/DB.pm
+4
-0
Requirements.pm
Bugzilla/Install/Requirements.pm
+9
-0
Markdown.pm
Bugzilla/Markdown.pm
+493
-0
Template.pm
Bugzilla/Template.pm
+17
-0
Bug.pm
Bugzilla/WebService/Bug.pm
+3
-1
using.rst
docs/en/rst/using.rst
+23
-0
field.js
js/field.js
+6
-2
post_bug.cgi
post_bug.cgi
+1
-0
process_bug.cgi
process_bug.cgi
+6
-2
global.css
skins/standard/global.css
+1
-1
004template.t
t/004template.t
+1
-0
008filter.t
t/008filter.t
+1
-1
comment.html.tmpl
template/en/default/bug/comment.html.tmpl
+8
-0
comments.html.tmpl
template/en/default/bug/comments.html.tmpl
+1
-1
bugmail.html.tmpl
template/en/default/email/bugmail.html.tmpl
+1
-1
filterexceptions.pl
template/en/default/filterexceptions.pl
+1
-1
setting-descs.none.tmpl
template/en/default/global/setting-descs.none.tmpl
+2
-1
linked.html.tmpl
template/en/default/pages/linked.html.tmpl
+2
-2
strings.txt.pl
template/en/default/setup/strings.txt.pl
+1
-0
No files found.
Bugzilla.pm
View file @
ec5caa57
...
...
@@ -397,6 +397,11 @@ sub logout_request {
# 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
{
require
Bugzilla::
JobQueue
;
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
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>
Returns a L<Bugzilla::JobQueue> that you can use for queueing jobs.
...
...
Bugzilla/Bug.pm
View file @
ec5caa57
...
...
@@ -691,6 +691,8 @@ sub create {
unless
defined
$params
->
{
rep_platform
};
# Make sure a comment is always defined.
$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
);
$params
=
$class
->
run_create_validators
(
$params
);
...
...
@@ -704,6 +706,7 @@ sub create {
my
$blocked
=
delete
$params
->
{
blocked
};
my
$keywords
=
delete
$params
->
{
keywords
};
my
$creation_comment
=
delete
$params
->
{
comment
};
my
$is_markdown
=
delete
$params
->
{
is_markdown
};
my
$see_also
=
delete
$params
->
{
see_also
};
# 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
$creation_comment
->
{
'bug_id'
}
=
$bug
->
id
;
$creation_comment
->
{
'is_markdown'
}
=
$is_markdown
;
# Insert the comment. We always insert a comment on bug creation,
# 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.
$self
->
add_comment
(
$params
->
{
'comment'
}
->
{
'body'
},
{
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
})
{
...
...
Bugzilla/Comment.pm
View file @
ec5caa57
...
...
@@ -43,6 +43,7 @@ use constant DB_COLUMNS => qw(
already_wrapped
type
extra_data
is_markdown
)
;
use
constant
UPDATE_COLUMNS
=>
qw(
...
...
@@ -65,6 +66,7 @@ use constant VALIDATORS => {
work_time
=>
\&
_check_work_time
,
thetext
=>
\&
_check_thetext
,
isprivate
=>
\&
_check_isprivate
,
is_markdown
=>
\&
Bugzilla::Object::
check_boolean
,
extra_data
=>
\&
_check_extra_data
,
type
=>
\&
_check_type
,
};
...
...
@@ -177,6 +179,7 @@ sub body { return $_[0]->{'thetext'}; }
sub
bug_id
{
return
$_
[
0
]
->
{
'bug_id'
};
}
sub
creation_ts
{
return
$_
[
0
]
->
{
'bug_when'
};
}
sub
is_private
{
return
$_
[
0
]
->
{
'isprivate'
};
}
sub
is_markdown
{
return
$_
[
0
]
->
{
'is_markdown'
};
}
sub
work_time
{
# Work time is returned as a string (see bug 607909)
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_type
{
$_
[
0
]
->
set
(
'type'
,
$_
[
1
]);
}
sub
set_extra_data
{
$_
[
0
]
->
set
(
'extra_data'
,
$_
[
1
]);
}
sub
set_is_markdown
{
$_
[
0
]
->
set
(
'is_markdown'
,
$_
[
1
]);
}
sub
add_tag
{
my
(
$self
,
$tag
)
=
@_
;
...
...
@@ -522,6 +526,10 @@ C<string> Time spent as related to this comment.
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>
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
=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>
=over
...
...
Bugzilla/Constants.pm
View file @
ec5caa57
...
...
@@ -191,6 +191,8 @@ use Memoize;
AUDIT_REMOVE
MOST_FREQUENT_THRESHOLD
MARKDOWN_TAB_WIDTH
)
;
@
Bugzilla::Constants::
EXPORT_OK
=
qw(contenttypes)
;
...
...
@@ -628,6 +630,10 @@ use constant AUDIT_REMOVE => '__remove__';
# on the "Most frequently reported bugs" page.
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
{
# Force memoize() to re-compute data per project, to avoid
# sharing the same data across different installations.
...
...
Bugzilla/DB/Schema.pm
View file @
ec5caa57
...
...
@@ -410,7 +410,8 @@ use constant ABSTRACT_SCHEMA => {
DEFAULT
=>
'FALSE'
},
type
=>
{
TYPE
=>
'INT2'
,
NOTNULL
=>
1
,
DEFAULT
=>
'0'
},
extra_data
=>
{
TYPE
=>
'varchar(255)'
}
extra_data
=>
{
TYPE
=>
'varchar(255)'
},
is_markdown
=>
{
TYPE
=>
'BOOLEAN'
,
NOTNULL
=>
1
,
DEFAULT
=>
'FALSE'
}
],
INDEXES
=>
[
longdescs_bug_id_idx
=>
[
qw(bug_id work_time)
],
...
...
Bugzilla/Install.pm
View file @
ec5caa57
...
...
@@ -90,6 +90,8 @@ sub SETTINGS {
bugmail_new_prefix
=>
{
options
=>
[
'on'
,
'off'
],
default
=>
'on'
},
# 2013-07-26 joshi_sunil@in.com -- Bug 669535
possible_duplicates
=>
{
options
=>
[
'on'
,
'off'
],
default
=>
'on'
},
# 2014-05-24 koosha.khajeh@gmail.com -- Bug 1014164
use_markdown
=>
{
options
=>
[
'on'
,
'off'
],
default
=>
'on'
},
}
};
...
...
Bugzilla/Install/DB.pm
View file @
ec5caa57
...
...
@@ -726,6 +726,10 @@ sub update_table_definitions {
# 2014-08-11 sgreen@redhat.com - Bug 1012506
_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 #
################################################################
...
...
Bugzilla/Install/Requirements.pm
View file @
ec5caa57
...
...
@@ -405,6 +405,14 @@ sub OPTIONAL_MODULES {
version
=>
'0'
,
feature
=>
[
'memcached'
],
},
# Markdown
{
package
=>
'Text-Markdown'
,
module
=>
'Text::Markdown'
,
version
=>
'1.0.26'
,
feature
=>
[
'markdown'
],
}
);
my
$extra_modules
=
_get_extension_requirements
(
'OPTIONAL_MODULES'
);
...
...
@@ -428,6 +436,7 @@ use constant FEATURE_FILES => (
'Bugzilla/JobQueue/*'
,
'jobqueue.pl'
],
patch_viewer
=>
[
'Bugzilla/Attachment/PatchReader.pm'
],
updates
=>
[
'Bugzilla/Update.pm'
],
markdown
=>
[
'Bugzilla/Markdown.pm'
],
memcached
=>
[
'Bugzilla/Memcache.pm'
],
);
...
...
Bugzilla/Markdown.pm
0 → 100644
View file @
ec5caa57
# 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
]
*
(?:
&
lt
;)?
<
a
\
s
+
href
=
"(.+?)"
>\
2
</
a
>
(?:
&
gt
;)?
# url = \$2
[
\
t
]
*
\
n
?
# maybe one newline
[
\
t
]
*
(?:
(?
<=\
s
)
# lookbehind for whitespace
(?:
&
quot
;
|\
()
(
.+
?)
# title = \$3
(?:
&
quot
;
|\
))
[
\
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
Bugzilla/Template.pm
View file @
ec5caa57
...
...
@@ -807,6 +807,23 @@ sub create {
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
{
my
(
$context
,
$bug
,
$options
)
=
@_
;
return
sub
{
...
...
Bugzilla/WebService/Bug.pm
View file @
ec5caa57
...
...
@@ -331,7 +331,9 @@ sub render_comment {
Bugzilla
->
switch_to_shadow_db
();
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
$template
=
Bugzilla
->
template
;
$template
->
process
(
...
...
docs/en/rst/using.rst
View file @
ec5caa57
...
...
@@ -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
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:
Server-Side Comment Wrapping
...
...
js/field.js
View file @
ec5caa57
...
...
@@ -979,11 +979,13 @@ function initDirtyFieldTracking() {
*/
var
last_comment_text
=
''
;
var
last_markdown_cb_value
=
null
;
function
show_comment_preview
(
bug_id
)
{
var
Dom
=
YAHOO
.
util
.
Dom
;
var
comment
=
document
.
getElementById
(
'comment'
);
var
preview
=
document
.
getElementById
(
'comment_preview'
);
var
markdown_cb
=
document
.
getElementById
(
'use_markdown'
);
if
(
!
comment
||
!
preview
)
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'
);
if
(
last_comment_text
==
comment
.
value
)
if
(
last_comment_text
==
comment
.
value
&&
last_markdown_cb_value
==
markdown_cb
.
checked
)
return
;
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
.
removeClass
(
'comment_preview_text'
,
'bz_default_hidden'
);
last_comment_text
=
comment
.
value
;
last_markdown_cb_value
=
markdown_cb
.
checked
;
}
},
failure
:
function
(
res
)
{
...
...
@@ -1039,7 +1042,8 @@ function show_comment_preview(bug_id) {
params
:
{
Bugzilla_api_token
:
BUGZILLA
.
api_token
,
id
:
bug_id
,
text
:
comment
.
value
text
:
comment
.
value
,
markdown
:
(
markdown_cb
!=
null
)
&&
markdown_cb
.
checked
?
1
:
0
}
})
);
...
...
post_bug.cgi
View file @
ec5caa57
...
...
@@ -118,6 +118,7 @@ foreach my $field (qw(cc groups)) {
$bug_params
{
$field
}
=
[
$cgi
->
param
(
$field
)];
}
$bug_params
{
'comment'
}
=
$comment
;
$bug_params
{
'is_markdown'
}
=
$cgi
->
param
(
'use_markdown'
);
my
@multi_selects
=
grep
{
$_
->
type
==
FIELD_TYPE_MULTI_SELECT
&&
$_
->
enter_bug
}
Bugzilla
->
active_custom_fields
;
...
...
process_bug.cgi
View file @
ec5caa57
...
...
@@ -233,9 +233,13 @@ if (should_set('keywords')) {
$set_all_fields
{
keywords
}
->
{
$action
}
=
$cgi
->
param
(
'keywords'
);
}
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
}
=
{
body
=>
scalar
$cgi
->
param
(
'comment'
),
is_private
=>
scalar
$cgi
->
param
(
'comment_is_private'
),
body
=>
scalar
$cgi
->
param
(
'comment'
),
is_private
=>
scalar
$cgi
->
param
(
'comment_is_private'
),
is_markdown
=>
$is_markdown
,
};
}
if
(
should_set
(
'see_also'
))
{
...
...
skins/standard/global.css
View file @
ec5caa57
...
...
@@ -728,7 +728,7 @@ input.required, select.required, span.required_explanation {
}
#comment
{
margin
:
0
px
0px
1em
0px
;
margin
:
0
;
}
/*******************/
...
...
t/004template.t
View file @
ec5caa57
...
...
@@ -88,6 +88,7 @@ foreach my $include_path (@include_paths) {
wrap_comment => sub { return $_ },
none => sub { return $_ } ,
ics => [ sub { return sub { return $_; } }, 1] ,
markdown => sub { return $_ } ,
},
}
);
...
...
t/008filter.t
View file @
ec5caa57
...
...
@@ -212,7 +212,7 @@ sub directive_ok {
return 1 if $directive =~ /FILTER\ (html|csv|js|base64|css_class_quote|ics|
quoteUrls|time|uri|xml|lower|html_light|
obsolete|inactive|closed|unitconvert|
txt|html_linebreak|none)\b/x;
txt|html_linebreak|
markdown|
none)\b/x;
return 0;
}
...
...
template/en/default/bug/comment.html.tmpl
View file @
ec5caa57
...
...
@@ -35,3 +35,11 @@
<pre id="comment_preview_text" class="bz_comment_text"></pre>
</div>
[% 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 %]
template/en/default/bug/comments.html.tmpl
View file @
ec5caa57
...
...
@@ -229,7 +229,7 @@
[% IF mode == "edit" || comment.collapsed %]
id="comment_text_[% comment.count FILTER none %]"
[% END %]>
[%- comment_text FILTER
quoteUrls
(bug, comment) -%]
[%- comment_text FILTER
markdown
(bug, comment) -%]
</pre>
[% Hook.process('a_comment-end', 'bug/comments.html.tmpl') %]
</div>
...
...
template/en/default/email/bugmail.html.tmpl
View file @
ec5caa57
...
...
@@ -25,7 +25,7 @@
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>
[% 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>
[% END %]
</p>
...
...
template/en/default/filterexceptions.pl
View file @
ec5caa57
...
...
@@ -20,7 +20,7 @@
# [% foo.push() %]
# TT loop variables - [% loop.count %]
# 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
=
(
...
...
template/en/default/global/setting-descs.none.tmpl
View file @
ec5caa57
...
...
@@ -44,7 +44,8 @@
"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",
"possible_duplicates" => "Display possible duplicates when reporting a new $terms.bug",
}
"use_markdown" => "Enable Markdown support for $terms.comments"
}
%]
[% Hook.process('settings') %]
template/en/default/pages/linked.html.tmpl
View file @
ec5caa57
...
...
@@ -18,7 +18,7 @@
<p>
<pre class="bz_comment_text">
[%- cgi.param("text") FILTER
quoteUrls
FILTER html -%]
[%- cgi.param("text") FILTER
markdown
FILTER html -%]
</pre>
</p>
...
...
@@ -33,7 +33,7 @@
<p>
<pre class="bz_comment_text">
[%- cgi.param("text") FILTER
quoteUrls
-%]
[%- cgi.param("text") FILTER
markdown
-%]
</pre>
</p>
...
...
template/en/default/setup/strings.txt.pl
View file @
ec5caa57
...
...
@@ -102,6 +102,7 @@ END
feature_xmlrpc
=>
'XML-RPC Interface'
,
feature_detect_charset
=>
'Automatic charset detection for text attachments'
,
feature_typesniffer
=>
'Sniff MIME type of attachments'
,
feature_markdown
=>
'Markdown syntax support for comments'
,
file_remove
=>
'Removing ##name##...'
,
file_rename
=>
'Renaming ##from## to ##to##...'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment