Render descriptions and comments as Markdown

When displaying comments and descriptions for projects or stories,
render the content as Markdown before displaying it. This is a
simple way to support rich text descriptions and comments, and
there is no special editor as yet.

Any code in the supplied Markdown (indented by 4 spaces) will have
its syntax highlighted. The `highlightjs` module is used for syntax
highlighting and the `marked` module is used for parsing the
Markdown.

Also, stop eslint from raising an error when it thinks something is
undefined, and raise a warning instead. This is because the use of
`hljs` and `marked` was confusing the linter into thinking they
weren't defined.

Change-Id: I7896fd686a39e27f8068ee6db6747b2b5ab0ccfc
This commit is contained in:
Adam Coldrick 2015-09-11 16:59:55 +00:00
parent ec0f5651cd
commit cdf7944065
12 changed files with 67 additions and 23 deletions

View File

@ -49,6 +49,7 @@
"no-trailing-spaces": 2, "no-trailing-spaces": 2,
"camelcase": 0, "camelcase": 0,
"no-extra-boolean-cast": 0, "no-extra-boolean-cast": 0,
"no-undef": 1,
// Stylistic // Stylistic
"indent": [2, 4], "indent": [2, 4],

View File

@ -149,7 +149,8 @@ module.exports = function (grunt) {
dir.theme + '/custom/', dir.theme + '/custom/',
dir.theme + '/storyboard/', dir.theme + '/storyboard/',
dir.bower + '/bootstrap/less/', dir.bower + '/bootstrap/less/',
dir.bower + '/font-awesome/less/' dir.bower + '/font-awesome/less/',
dir.bower + '/highlightjs/styles/'
]; ];
}, },
cleancss: true, cleancss: true,

View File

@ -13,7 +13,9 @@
"angular-elastic": "2.4.2", "angular-elastic": "2.4.2",
"angular-moment": "0.9.0", "angular-moment": "0.9.0",
"angular-cache": "3.2.5", "angular-cache": "3.2.5",
"angularjs-viewhead": "0.0.1" "angularjs-viewhead": "0.0.1",
"marked": "0.3.4",
"highlightjs": "8.4"
}, },
"devDependencies": { "devDependencies": {
"angular-mocks": "1.3.13", "angular-mocks": "1.3.13",

View File

@ -46,9 +46,9 @@
</small> </small>
</h1> </h1>
<p> <p>
<span ng-show="project.description" <insert-markdown ng-if="project.description && !isLoading"
class="honor-carriage-return">{{project.description}} content="project.description">
</span> </insert-markdown>
<em ng-hide="project.description" class="text-muted"> <em ng-hide="project.description" class="text-muted">
No description provided No description provided
</em> </em>

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2015 Codethink Limited.
*
* Licensed under the Apache License, Version 2.0 (the 'License'); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* Service for rendering text as markdown.
*/
angular.module('sb.services')
.directive('insertMarkdown', function($sanitize) {
'use strict';
return {
restrict: 'E',
scope: {
content: '='
},
link: function(scope, elem) {
scope.$watch('content', function(newVal) {
elem.html('<div>' + $sanitize(marked(newVal)) + '</div>');
}, true);
}
};
});

View File

@ -4,9 +4,9 @@
<span time-moment eventdate="event.created_at" class="pull-right"></span> <span time-moment eventdate="event.created_at" class="pull-right"></span>
</p> </p>
<p ng-show="event.comment.content" <insert-markdown ng-show="event.comment.content"
class="honor-carriage-return">{{event.comment.content}} content="event.comment.content">
</p> </insert-markdown>
<p><em ng-hide="event.comment.content" <p><em ng-hide="event.comment.content"
class="text-muted"> class="text-muted">

View File

@ -38,7 +38,6 @@
<!-- Template for the header and description --> <!-- Template for the header and description -->
<script type="text/ng-template" id="/inline/story_detail.html"> <script type="text/ng-template" id="/inline/story_detail.html">
<h1> <h1>
<span ng-show="story.title" view-title> <span ng-show="story.title" view-title>
{{story.title}} {{story.title}}
</span> </span>
@ -52,11 +51,6 @@
</a> </a>
<subscribe resource="story" <subscribe resource="story"
resource-id="story.id"></subscribe> resource-id="story.id"></subscribe>
<button type="button"
class="btn btn-link"
ng-click="remove()" permission="is_superuser">
Remove this story
</button>
</small> </small>
</h1> </h1>
<p><strong>Author:</strong> <p><strong>Author:</strong>
@ -78,9 +72,9 @@
</em> </em>
</p> </p>
<p> <p>
<span ng-show="story.description" <insert-markdown ng-show="story.description"
class="honor-carriage-return">{{story.description}} content="story.description">
</span> </insert-markdown>
<em ng-hide="story.description" class="text-muted"> <em ng-hide="story.description" class="text-muted">
No description provided No description provided
</em> </em>

View File

@ -24,8 +24,9 @@
</a> </a>
</strong> </strong>
<br/> <br/>
<small class="text-muted honor-carriage-return" <small class="text-muted"
ng-show="expandRow">{{story.description}} ng-show="expandRow">
<insert-markdown content="story.description"></insert-markdown>
</small> </small>
</p> </p>
</td> </td>

View File

@ -27,7 +27,7 @@ angular.module('storyboard',
'sb.auth', 'sb.story', 'sb.profile', 'sb.notification', 'sb.search', 'sb.auth', 'sb.story', 'sb.profile', 'sb.notification', 'sb.search',
'sb.admin', 'sb.subscription', 'sb.project_group', 'ui.router', 'sb.admin', 'sb.subscription', 'sb.project_group', 'ui.router',
'ui.bootstrap', 'monospaced.elastic', 'angularMoment', 'ui.bootstrap', 'monospaced.elastic', 'angularMoment',
'angular-data.DSCacheFactory', 'viewhead']) 'angular-data.DSCacheFactory', 'viewhead', 'ngSanitize'])
.constant('angularMomentConfig', { .constant('angularMomentConfig', {
preprocess: 'utc', preprocess: 'utc',
timezone: 'UTC' timezone: 'UTC'
@ -62,6 +62,13 @@ angular.module('storyboard',
preferences: PreferenceResolver.resolvePreferences preferences: PreferenceResolver.resolvePreferences
} }
}); });
// Set up syntax highlighting for the markdown parser
marked.setOptions({
highlight: function (code) {
return hljs.highlightAuto(code).value;
}
});
}) })
.run(function ($log, $rootScope, $document) { .run(function ($log, $rootScope, $document) {
'use strict'; 'use strict';

View File

@ -39,6 +39,8 @@
<script src="moment/moment.js"></script> <script src="moment/moment.js"></script>
<script src="angular-moment/angular-moment.js"></script> <script src="angular-moment/angular-moment.js"></script>
<script src="angularjs-viewhead/angularjs-viewhead.js"></script> <script src="angularjs-viewhead/angularjs-viewhead.js"></script>
<script src="marked/marked.min.js"></script>
<script src="highlightjs/highlight.pack.js"></script>
<!-- endbuild --> <!-- endbuild -->
<link rel="stylesheet" href="styles/main.css"> <link rel="stylesheet" href="styles/main.css">

View File

@ -36,7 +36,7 @@
padding: @table-condensed-cell-padding; padding: @table-condensed-cell-padding;
} }
> p { > insert-markdown > div, > p {
padding: @table-cell-padding; padding: @table-cell-padding;
padding-bottom: 0px; padding-bottom: 0px;
} }

View File

@ -24,6 +24,7 @@
@import './bootstrap.less'; @import './bootstrap.less';
@import './base/bootstrap/navbar.less'; @import './base/bootstrap/navbar.less';
@import './font-awesome.less'; @import './font-awesome.less';
@import (less) './default.css';
// Theme // Theme
@import './theme.less'; @import './theme.less';