parent
7b8880151b
commit
319f33e924
@ -1,30 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
//= require jstoolbar/jstoolbar
|
||||
//= require jstoolbar/translations
|
@ -1,378 +0,0 @@ |
||||
/* ***** BEGIN LICENSE BLOCK ***** |
||||
* This file is part of DotClear. |
||||
* Copyright (c) 2005 Nicolas Martin & Olivier Meunier and contributors. All |
||||
* rights reserved. |
||||
* |
||||
* DotClear is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* DotClear is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with DotClear; if not, write to the Free Software |
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
||||
* |
||||
* ***** END LICENSE BLOCK ***** |
||||
*/ |
||||
|
||||
/* Modified by JP LANG for textile formatting */ |
||||
|
||||
function jsToolBar(textarea) { |
||||
if (!document.createElement) { return; } |
||||
|
||||
if (!textarea) { return; } |
||||
|
||||
if ((typeof(document["selection"]) == "undefined") |
||||
&& (typeof(textarea["setSelectionRange"]) == "undefined")) { |
||||
return; |
||||
} |
||||
|
||||
this.textarea = textarea; |
||||
|
||||
this.editor = document.createElement('div'); |
||||
this.editor.className = 'jstEditor'; |
||||
|
||||
this.textarea.parentNode.insertBefore(this.editor,this.textarea); |
||||
this.editor.appendChild(this.textarea); |
||||
|
||||
this.toolbar = document.createElement("div"); |
||||
this.toolbar.className = 'jstElements'; |
||||
this.editor.parentNode.insertBefore(this.toolbar,this.editor); |
||||
|
||||
// Dragable resizing (only for gecko)
|
||||
if (this.editor.addEventListener) |
||||
{ |
||||
this.handle = document.createElement('div'); |
||||
this.handle.className = 'jstHandle'; |
||||
var dragStart = this.resizeDragStart; |
||||
var This = this; |
||||
this.handle.addEventListener('mousedown',function(event) { dragStart.call(This,event); },false); |
||||
// fix memory leak in Firefox (bug #241518)
|
||||
window.addEventListener('unload',function() { |
||||
var del = This.handle.parentNode.removeChild(This.handle); |
||||
delete(This.handle); |
||||
},false); |
||||
|
||||
this.editor.parentNode.insertBefore(this.handle,this.editor.nextSibling); |
||||
} |
||||
|
||||
this.context = null; |
||||
this.toolNodes = {}; // lorsque la toolbar est dessinée , cet objet est garni
|
||||
// de raccourcis vers les éléments DOM correspondants aux outils.
|
||||
} |
||||
|
||||
function jsButton(title, fn, scope, className) { |
||||
if(typeof jsToolBar.strings == 'undefined') { |
||||
this.title = title || null; |
||||
} else { |
||||
this.title = jsToolBar.strings[title] || title || null; |
||||
} |
||||
this.fn = fn || function(){}; |
||||
this.scope = scope || null; |
||||
this.className = className || null; |
||||
} |
||||
jsButton.prototype.draw = function() { |
||||
if (!this.scope) return null; |
||||
|
||||
var button = document.createElement('button'); |
||||
button.setAttribute('type','button'); |
||||
button.setAttribute('aria-label', this.title); |
||||
if (this.className) button.className = this.className; |
||||
button.title = this.title; |
||||
|
||||
if (this.icon != undefined) { |
||||
button.style.backgroundImage = 'url('+this.icon+')'; |
||||
} |
||||
if (typeof(this.fn) == 'function') { |
||||
var This = this; |
||||
button.onclick = function() { try { This.fn.apply(This.scope, arguments) } catch (e) {} return false; }; |
||||
} |
||||
return button; |
||||
}; |
||||
|
||||
function jsSpace(id) { |
||||
this.id = id || null; |
||||
this.width = null; |
||||
} |
||||
jsSpace.prototype.draw = function() { |
||||
var span = document.createElement('span'); |
||||
if (this.id) span.id = this.id; |
||||
span.appendChild(document.createTextNode(String.fromCharCode(160))); |
||||
span.className = 'jstSpacer'; |
||||
if (this.width) span.style.marginRight = this.width+'px'; |
||||
|
||||
return span; |
||||
}; |
||||
|
||||
function jsCombo(title, options, scope, fn, className) { |
||||
this.title = title || null; |
||||
this.options = options || null; |
||||
this.scope = scope || null; |
||||
this.fn = fn || function(){}; |
||||
this.className = className || null; |
||||
} |
||||
jsCombo.prototype.draw = function() { |
||||
if (!this.scope || !this.options) return null; |
||||
|
||||
var select = document.createElement('select'); |
||||
if (this.className) select.className = className; |
||||
select.title = this.title; |
||||
|
||||
for (var o in this.options) { |
||||
//var opt = this.options[o];
|
||||
var option = document.createElement('option'); |
||||
option.value = o; |
||||
option.appendChild(document.createTextNode(this.options[o])); |
||||
select.appendChild(option); |
||||
} |
||||
|
||||
var This = this; |
||||
select.onchange = function() { |
||||
try { |
||||
This.fn.call(This.scope, this.value); |
||||
} catch (e) { alert(e); } |
||||
|
||||
return false; |
||||
}; |
||||
|
||||
return select; |
||||
}; |
||||
|
||||
|
||||
jsToolBar.prototype = { |
||||
base_url: '', |
||||
mode: 'wiki', |
||||
elements: {}, |
||||
help_link: '', |
||||
|
||||
getMode: function() { |
||||
return this.mode; |
||||
}, |
||||
|
||||
setMode: function(mode) { |
||||
this.mode = mode || 'wiki'; |
||||
}, |
||||
|
||||
switchMode: function(mode) { |
||||
mode = mode || 'wiki'; |
||||
this.draw(mode); |
||||
}, |
||||
|
||||
setHelpLink: function(link) { |
||||
this.help_link = link; |
||||
}, |
||||
|
||||
button: function(toolName) { |
||||
var tool = this.elements[toolName]; |
||||
if (typeof tool.fn[this.mode] != 'function') return null; |
||||
var b = new jsButton(tool.title, tool.fn[this.mode], this, 'icon-small jstb_'+toolName); |
||||
if (tool.icon != undefined) b.icon = tool.icon; |
||||
return b; |
||||
}, |
||||
space: function(toolName) { |
||||
var tool = new jsSpace(toolName); |
||||
if (this.elements[toolName].width !== undefined) |
||||
tool.width = this.elements[toolName].width; |
||||
return tool; |
||||
}, |
||||
combo: function(toolName) { |
||||
var tool = this.elements[toolName]; |
||||
var length = tool[this.mode].list.length; |
||||
|
||||
if (typeof tool[this.mode].fn != 'function' || length == 0) { |
||||
return null; |
||||
} else { |
||||
var options = {}; |
||||
for (var i=0; i < length; i++) { |
||||
var opt = tool[this.mode].list[i]; |
||||
options[opt] = tool.options[opt]; |
||||
} |
||||
return new jsCombo(tool.title, options, this, tool[this.mode].fn); |
||||
} |
||||
}, |
||||
draw: function(mode) { |
||||
this.setMode(mode); |
||||
|
||||
// Empty toolbar
|
||||
while (this.toolbar.hasChildNodes()) { |
||||
this.toolbar.removeChild(this.toolbar.firstChild) |
||||
} |
||||
this.toolNodes = {}; // vide les raccourcis DOM/**/
|
||||
|
||||
// Draw toolbar elements
|
||||
var b, tool, newTool; |
||||
|
||||
for (var i in this.elements) { |
||||
b = this.elements[i]; |
||||
|
||||
var disabled = |
||||
b.type == undefined || b.type == '' |
||||
|| (b.disabled != undefined && b.disabled) |
||||
|| (b.context != undefined && b.context != null && b.context != this.context); |
||||
|
||||
if (!disabled && typeof this[b.type] == 'function') { |
||||
tool = this[b.type](i); |
||||
if (tool) newTool = tool.draw(); |
||||
if (newTool) { |
||||
this.toolNodes[i] = newTool; //mémorise l'accès DOM pour usage éventuel ultérieur
|
||||
this.toolbar.appendChild(newTool); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (this.help_link !== '') { |
||||
this.toolbar.appendChild(this.help_link); |
||||
} |
||||
}, |
||||
|
||||
singleTag: function(stag,etag) { |
||||
stag = stag || null; |
||||
etag = etag || stag; |
||||
|
||||
if (!stag || !etag) { return; } |
||||
|
||||
this.encloseSelection(stag,etag); |
||||
}, |
||||
|
||||
encloseLineSelection: function (prefix, suffix, fn) { |
||||
this.textarea.focus(); |
||||
prefix = prefix || ''; |
||||
suffix = suffix || ''; |
||||
var start, end, sel, scrollPos, subst, res; |
||||
if (typeof(document["selection"]) != "undefined") { |
||||
// just makes it work in IE8 somehow
|
||||
var helperRange = document.selection.createRange(); |
||||
var bookmark = helperRange.getBookmark(); |
||||
var origParent = helperRange.parentElement(); |
||||
var moveStart = 0; |
||||
// we move the starting point of the selection to the last newline
|
||||
// and track the number of movements
|
||||
try { |
||||
while (helperRange.text[0] != "\n" && helperRange.text[0] != "\r") { |
||||
helperRange.moveStart("character", -1); |
||||
if (origParent != helperRange.parentElement()) { |
||||
throw "Outside of Textarea"; |
||||
} |
||||
moveStart -= 1; |
||||
} |
||||
moveStart += 1; |
||||
} catch(err) { |
||||
if (err != "Outside of Textarea") |
||||
throw err; |
||||
} |
||||
var range = document.selection.createRange(); |
||||
range.moveStart("character", moveStart); |
||||
if (range.text.match(/ $/)) |
||||
range.moveEnd("character", -1); |
||||
sel = range.text; |
||||
} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { |
||||
start = this.textarea.selectionStart; |
||||
end = this.textarea.selectionEnd; |
||||
scrollPos = this.textarea.scrollTop; |
||||
// go to the start of the line
|
||||
start = this.textarea.value.substring(0, start).replace(/[^\r\n]*$/g,'').length; |
||||
// go to the end of the line
|
||||
end = this.textarea.value.length - this.textarea.value.substring(end, this.textarea.value.length).replace(/^[^\r\n]*/, '').length; |
||||
sel = this.textarea.value.substring(start, end); |
||||
} |
||||
if (sel.match(/ $/)) { |
||||
sel = sel.substring(0, sel.length - 1); |
||||
suffix = suffix + " "; |
||||
} |
||||
if (typeof(fn) == 'function') { |
||||
res = (sel) ? fn.call(this, sel) : fn(''); |
||||
} else { |
||||
res = (sel) ? sel : ''; |
||||
} |
||||
subst = prefix + res + suffix; |
||||
if (typeof(document["selection"]) != "undefined") { |
||||
range.text = subst; |
||||
this.textarea.caretPos -= suffix.length; |
||||
} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { |
||||
var cursorPosition = start; |
||||
this.textarea.value = this.textarea.value.substring(0, start) + subst + this.textarea.value.substring(end); |
||||
if (sel) { |
||||
cursorPosition += subst.length; |
||||
} else { |
||||
cursorPosition += prefix.length + res.length; |
||||
} |
||||
this.textarea.setSelectionRange(cursorPosition, cursorPosition); |
||||
this.textarea.scrollTop = scrollPos; |
||||
} |
||||
}, |
||||
|
||||
encloseSelection: function (prefix, suffix, fn) { |
||||
this.textarea.focus(); |
||||
prefix = prefix || ''; |
||||
suffix = suffix || ''; |
||||
var start, end, sel, scrollPos, subst, res; |
||||
if (typeof(document["selection"]) != "undefined") { |
||||
sel = document.selection.createRange().text; |
||||
} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { |
||||
start = this.textarea.selectionStart; |
||||
end = this.textarea.selectionEnd; |
||||
scrollPos = this.textarea.scrollTop; |
||||
sel = this.textarea.value.substring(start, end); |
||||
} |
||||
if (sel.match(/ $/)) { |
||||
sel = sel.substring(0, sel.length - 1); |
||||
suffix = suffix + " "; |
||||
} |
||||
if (typeof(fn) == 'function') { |
||||
res = (sel) ? fn.call(this, sel) : fn(''); |
||||
} else { |
||||
res = (sel) ? sel : ''; |
||||
} |
||||
subst = prefix + res + suffix; |
||||
if (typeof(document["selection"]) != "undefined") { |
||||
var range = document.selection.createRange().text = subst; |
||||
this.textarea.caretPos -= suffix.length; |
||||
} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { |
||||
this.textarea.value = this.textarea.value.substring(0, start) + subst + this.textarea.value.substring(end); |
||||
if (sel) { |
||||
this.textarea.setSelectionRange(start + subst.length, start + subst.length); |
||||
} else { |
||||
this.textarea.setSelectionRange(start + prefix.length, start + prefix.length); |
||||
} |
||||
this.textarea.scrollTop = scrollPos; |
||||
} |
||||
}, |
||||
|
||||
stripBaseURL: function(url) { |
||||
if (this.base_url != '') { |
||||
var pos = url.indexOf(this.base_url); |
||||
if (pos == 0) { |
||||
url = url.substr(this.base_url.length); |
||||
} |
||||
} |
||||
|
||||
return url; |
||||
} |
||||
}; |
||||
|
||||
/** Resizer |
||||
-------------------------------------------------------- */ |
||||
jsToolBar.prototype.resizeSetStartH = function() { |
||||
this.dragStartH = this.textarea.offsetHeight + 0; |
||||
}; |
||||
jsToolBar.prototype.resizeDragStart = function(event) { |
||||
var This = this; |
||||
this.dragStartY = event.clientY; |
||||
this.resizeSetStartH(); |
||||
document.addEventListener('mousemove', this.dragMoveHdlr=function(event){This.resizeDragMove(event);}, false); |
||||
document.addEventListener('mouseup', this.dragStopHdlr=function(event){This.resizeDragStop(event);}, false); |
||||
}; |
||||
|
||||
jsToolBar.prototype.resizeDragMove = function(event) { |
||||
this.textarea.style.height = (this.dragStartH+event.clientY-this.dragStartY)+'px'; |
||||
}; |
||||
|
||||
jsToolBar.prototype.resizeDragStop = function(event) { |
||||
document.removeEventListener('mousemove', this.dragMoveHdlr, false); |
||||
document.removeEventListener('mouseup', this.dragStopHdlr, false); |
||||
}; |
@ -1,193 +0,0 @@ |
||||
/* ***** BEGIN LICENSE BLOCK ***** |
||||
* This file is part of DotClear. |
||||
* Copyright (c) 2005 Nicolas Martin & Olivier Meunier and contributors. All |
||||
* rights reserved. |
||||
* |
||||
* DotClear is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* DotClear is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with DotClear; if not, write to the Free Software |
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
||||
* |
||||
* ***** END LICENSE BLOCK ***** |
||||
*/ |
||||
|
||||
/* Modified by JP LANG for textile formatting */ |
||||
|
||||
// strong
|
||||
jsToolBar.prototype.elements.strong = { |
||||
type: 'button', |
||||
title: 'Strong', |
||||
fn: { |
||||
wiki: function() { this.singleTag('**') } |
||||
} |
||||
}; |
||||
|
||||
// em
|
||||
jsToolBar.prototype.elements.em = { |
||||
type: 'button', |
||||
title: 'Italic', |
||||
fn: { |
||||
wiki: function() { this.singleTag("_") } |
||||
} |
||||
}; |
||||
|
||||
// del
|
||||
jsToolBar.prototype.elements.del = { |
||||
type: 'button', |
||||
title: 'Deleted', |
||||
fn: { |
||||
wiki: function() { this.singleTag('~~') } |
||||
} |
||||
}; |
||||
|
||||
// code
|
||||
jsToolBar.prototype.elements.code = { |
||||
type: 'button', |
||||
title: 'Code', |
||||
fn: { |
||||
wiki: function() { this.singleTag('`') } |
||||
} |
||||
}; |
||||
|
||||
// spacer
|
||||
jsToolBar.prototype.elements.space1 = {type: 'space'}; |
||||
|
||||
// headings
|
||||
jsToolBar.prototype.elements.h1 = { |
||||
type: 'button', |
||||
title: 'Heading 1', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('# ', '',function(str) { |
||||
str = str.replace(/^#+/, ''); |
||||
return str; |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
jsToolBar.prototype.elements.h2 = { |
||||
type: 'button', |
||||
title: 'Heading 2', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('## ', '',function(str) { |
||||
str = str.replace(/^#+/, ''); |
||||
return str; |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
jsToolBar.prototype.elements.h3 = { |
||||
type: 'button', |
||||
title: 'Heading 3', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('### ', '',function(str) { |
||||
str = str.replace(/^#+/, ''); |
||||
return str; |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// spacer
|
||||
jsToolBar.prototype.elements.space2 = {type: 'space'}; |
||||
|
||||
// ul
|
||||
jsToolBar.prototype.elements.ul = { |
||||
type: 'button', |
||||
title: 'Unordered list', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('','',function(str) { |
||||
str = str.replace(/\r/g,''); |
||||
return str.replace(/(\n|^)[#-]?\s*/g,"$1- "); |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// ol
|
||||
jsToolBar.prototype.elements.ol = { |
||||
type: 'button', |
||||
title: 'Ordered list', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('','',function(str) { |
||||
str = str.replace(/\r/g,''); |
||||
return str.replace(/(\n|^)[*-]?\s*/g,"$11. "); |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// spacer
|
||||
jsToolBar.prototype.elements.space3 = {type: 'space'}; |
||||
|
||||
// bq
|
||||
jsToolBar.prototype.elements.bq = { |
||||
type: 'button', |
||||
title: 'Quote', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('','',function(str) { |
||||
str = str.replace(/\r/g,''); |
||||
return str.replace(/(\n|^) *([^\n]*)/g,"$1> $2"); |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// unbq
|
||||
jsToolBar.prototype.elements.unbq = { |
||||
type: 'button', |
||||
title: 'Unquote', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('','',function(str) { |
||||
str = str.replace(/\r/g,''); |
||||
return str.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2"); |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// pre
|
||||
jsToolBar.prototype.elements.code = { |
||||
type: 'button', |
||||
title: 'Code fence', |
||||
fn: { |
||||
wiki: function() { this.encloseLineSelection('```\n', '\n```') } |
||||
} |
||||
}; |
||||
|
||||
// spacer
|
||||
jsToolBar.prototype.elements.space4 = {type: 'space'}; |
||||
|
||||
// wiki page
|
||||
jsToolBar.prototype.elements.link = { |
||||
type: 'button', |
||||
title: 'Link', |
||||
fn: { |
||||
wiki: function() { this.encloseSelection("[", "]()") } |
||||
} |
||||
}; |
||||
// image
|
||||
jsToolBar.prototype.elements.img = { |
||||
type: 'button', |
||||
title: 'Image', |
||||
fn: { |
||||
wiki: function() { this.encloseSelection("![](", ")") } |
||||
} |
||||
}; |
||||
|
||||
|
@ -1,202 +0,0 @@ |
||||
/* ***** BEGIN LICENSE BLOCK ***** |
||||
* This file is part of DotClear. |
||||
* Copyright (c) 2005 Nicolas Martin & Olivier Meunier and contributors. All |
||||
* rights reserved. |
||||
* |
||||
* DotClear is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* DotClear is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with DotClear; if not, write to the Free Software |
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
||||
* |
||||
* ***** END LICENSE BLOCK ***** |
||||
*/ |
||||
|
||||
/* Modified by JP LANG for textile formatting */ |
||||
|
||||
// strong
|
||||
jsToolBar.prototype.elements.strong = { |
||||
type: 'button', |
||||
title: 'Strong', |
||||
fn: { |
||||
wiki: function() { this.singleTag('*') } |
||||
} |
||||
}; |
||||
|
||||
// em
|
||||
jsToolBar.prototype.elements.em = { |
||||
type: 'button', |
||||
title: 'Italic', |
||||
fn: { |
||||
wiki: function() { this.singleTag("_") } |
||||
} |
||||
}; |
||||
|
||||
// ins
|
||||
jsToolBar.prototype.elements.ins = { |
||||
type: 'button', |
||||
title: 'Underline', |
||||
fn: { |
||||
wiki: function() { this.singleTag('+') } |
||||
} |
||||
}; |
||||
|
||||
// del
|
||||
jsToolBar.prototype.elements.del = { |
||||
type: 'button', |
||||
title: 'Deleted', |
||||
fn: { |
||||
wiki: function() { this.singleTag('-') } |
||||
} |
||||
}; |
||||
|
||||
// code
|
||||
jsToolBar.prototype.elements.pre = { |
||||
type: 'button', |
||||
title: 'Code', |
||||
fn: { |
||||
wiki: function() { this.singleTag('@') } |
||||
} |
||||
}; |
||||
|
||||
// spacer
|
||||
jsToolBar.prototype.elements.space1 = {type: 'space'}; |
||||
|
||||
// headings
|
||||
jsToolBar.prototype.elements.h1 = { |
||||
type: 'button', |
||||
title: 'Heading 1', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('h1. ', '',function(str) { |
||||
str = str.replace(/^h\d+\.\s+/, ''); |
||||
return str; |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
jsToolBar.prototype.elements.h2 = { |
||||
type: 'button', |
||||
title: 'Heading 2', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('h2. ', '',function(str) { |
||||
str = str.replace(/^h\d+\.\s+/, ''); |
||||
return str; |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
jsToolBar.prototype.elements.h3 = { |
||||
type: 'button', |
||||
title: 'Heading 3', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('h3. ', '',function(str) { |
||||
str = str.replace(/^h\d+\.\s+/, ''); |
||||
return str; |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// spacer
|
||||
jsToolBar.prototype.elements.space2 = {type: 'space'}; |
||||
|
||||
// ul
|
||||
jsToolBar.prototype.elements.ul = { |
||||
type: 'button', |
||||
title: 'Unordered list', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('','',function(str) { |
||||
str = str.replace(/\r/g,''); |
||||
return str.replace(/(\n|^)[#-]?\s*/g,"$1* "); |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// ol
|
||||
jsToolBar.prototype.elements.ol = { |
||||
type: 'button', |
||||
title: 'Ordered list', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('','',function(str) { |
||||
str = str.replace(/\r/g,''); |
||||
return str.replace(/(\n|^)[*-]?\s*/g,"$1# "); |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// spacer
|
||||
jsToolBar.prototype.elements.space3 = {type: 'space'}; |
||||
|
||||
// bq
|
||||
jsToolBar.prototype.elements.bq = { |
||||
type: 'button', |
||||
title: 'Quote', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('','',function(str) { |
||||
str = str.replace(/\r/g,''); |
||||
return str.replace(/(\n|^) *([^\n]*)/g,"$1> $2"); |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// unbq
|
||||
jsToolBar.prototype.elements.unbq = { |
||||
type: 'button', |
||||
title: 'Unquote', |
||||
fn: { |
||||
wiki: function() { |
||||
this.encloseLineSelection('','',function(str) { |
||||
str = str.replace(/\r/g,''); |
||||
return str.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2"); |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// pre
|
||||
jsToolBar.prototype.elements.pre = { |
||||
type: 'button', |
||||
title: 'Preformatted text', |
||||
fn: { |
||||
wiki: function() { this.encloseLineSelection('<pre>\n', '\n</pre>') } |
||||
} |
||||
}; |
||||
|
||||
// spacer
|
||||
jsToolBar.prototype.elements.space4 = {type: 'space'}; |
||||
|
||||
// wiki page
|
||||
jsToolBar.prototype.elements.link = { |
||||
type: 'button', |
||||
title: 'Wiki link', |
||||
fn: { |
||||
wiki: function() { this.encloseSelection("[[", "]]") } |
||||
} |
||||
}; |
||||
// image
|
||||
jsToolBar.prototype.elements.img = { |
||||
type: 'button', |
||||
title: 'Image', |
||||
fn: { |
||||
wiki: function() { this.encloseSelection("!", "!") } |
||||
} |
||||
}; |
||||
|
||||
|
@ -1,20 +0,0 @@ |
||||
jQuery(function() { |
||||
jsToolBar.strings = { |
||||
'Strong': I18n.t('js.wiki_formatting.strong'), |
||||
'Italic': I18n.t('js.wiki_formatting.italic'), |
||||
'Underline': I18n.t('js.wiki_formatting.underline'), |
||||
'Deleted': I18n.t('js.wiki_formatting.deleted'), |
||||
'Code': I18n.t('js.wiki_formatting.code'), |
||||
'Heading 1': I18n.t('js.wiki_formatting.heading1'), |
||||
'Heading 2': I18n.t('js.wiki_formatting.heading2'), |
||||
'Heading 3': I18n.t('js.wiki_formatting.heading3'), |
||||
'Unordered list': I18n.t('js.wiki_formatting.unordered_list'), |
||||
'Ordered list': I18n.t('js.wiki_formatting.ordered_list'), |
||||
'Quote': I18n.t('js.wiki_formatting.quote'), |
||||
'Unquote': I18n.t('js.wiki_formatting.unquote'), |
||||
'Preformatted text': I18n.t('js.wiki_formatting.preformatted_text'), |
||||
'Wiki link': I18n.t('js.wiki_formatting.wiki_link'), |
||||
'Image': I18n.t('js.wiki_formatting.image') |
||||
} |
||||
}); |
||||
|
@ -1,154 +0,0 @@ |
||||
//-- copyright |
||||
// OpenProject is a project management system. |
||||
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
// |
||||
// This program is free software; you can redistribute it and/or |
||||
// modify it under the terms of the GNU General Public License version 3. |
||||
// |
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
// Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
// Copyright (C) 2010-2013 the ChiliProject Team |
||||
// |
||||
// This program is free software; you can redistribute it and/or |
||||
// modify it under the terms of the GNU General Public License |
||||
// as published by the Free Software Foundation; either version 2 |
||||
// of the License, or (at your option) any later version. |
||||
// |
||||
// This program is distributed in the hope that it will be useful, |
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
// GNU General Public License for more details. |
||||
// |
||||
// You should have received a copy of the GNU General Public License |
||||
// along with this program; if not, write to the Free Software |
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
// |
||||
// See docs/COPYRIGHT.rdoc for more details. |
||||
//++ |
||||
|
||||
$jstoolbar--icon-background: #fff |
||||
$jstoolbar--icon-hover-border: #ccc |
||||
$jstoolbar--icon-border: #fff |
||||
$jstoolbar--icon-active-border: #bbb |
||||
$jstoolbar--icon-active-background: #eee |
||||
|
||||
.jstEditor |
||||
padding-left: 0 |
||||
clear: both |
||||
|
||||
textarea, iframe |
||||
margin: 0 |
||||
|
||||
textarea |
||||
min-height: 200px |
||||
|
||||
textarea.-small |
||||
min-height: 50px |
||||
|
||||
.jstHandle |
||||
height: 10px |
||||
font-size: 0.1em |
||||
cursor: s-resize |
||||
|
||||
.jstElements |
||||
background: #fff |
||||
float: right |
||||
line-height: 1.6rem |
||||
margin-bottom: 0.25rem |
||||
text-align: right |
||||
|
||||
button |
||||
@include icon-common |
||||
padding: 0.25rem |
||||
margin-left: 0.125rem |
||||
height: 1.625rem |
||||
width: 1.625rem |
||||
max-width: 1.625rem |
||||
border: 1px solid $jstoolbar--icon-border |
||||
background-color: $jstoolbar--icon-background |
||||
color: $body-font-color |
||||
border-radius: 2px |
||||
overflow: hidden |
||||
display: inline-block |
||||
|
||||
&:hover |
||||
border-color: $jstoolbar--icon-hover-border |
||||
cursor: pointer |
||||
|
||||
&:focus |
||||
border-color: $jstoolbar--icon-hover-border |
||||
|
||||
&.-active |
||||
border-color: $jstoolbar--icon-hover-border |
||||
box-shadow: inset 0px 0px 3px $jstoolbar--icon-active-border |
||||
background: $jstoolbar--icon-active-background |
||||
|
||||
&:before |
||||
display: block |
||||
|
||||
span |
||||
display: none |
||||
|
||||
.jstb_help |
||||
font-size: 0.8125rem |
||||
padding: 0.35rem |
||||
line-height: 1rem |
||||
@extend .icon-help1 |
||||
|
||||
&:before |
||||
color: $body-font-color |
||||
padding: 0 |
||||
|
||||
span |
||||
display: inline |
||||
|
||||
|
||||
.jstSpacer |
||||
width: 0 |
||||
font-size: 1px |
||||
margin-right: 4px |
||||
|
||||
.jstb_strong |
||||
@extend .icon-bold |
||||
|
||||
.jstb_em |
||||
@extend .icon-italic |
||||
|
||||
.jstb_ins |
||||
@extend .icon-underline |
||||
|
||||
.jstb_del |
||||
@extend .icon-strike-through |
||||
|
||||
.jstb_code |
||||
@extend .icon-code-tag |
||||
|
||||
.jstb_h1 |
||||
@extend .icon-headline1 |
||||
|
||||
.jstb_h2 |
||||
@extend .icon-headline2 |
||||
|
||||
.jstb_h3 |
||||
@extend .icon-headline3 |
||||
|
||||
.jstb_ul |
||||
@extend .icon-unordered-list |
||||
|
||||
.jstb_ol |
||||
@extend .icon-ordered-list |
||||
|
||||
.jstb_bq |
||||
@extend .icon-paragraph-right |
||||
|
||||
.jstb_unbq |
||||
@extend .icon-paragraph-left |
||||
|
||||
.jstb_pre |
||||
@extend .icon-pre |
||||
|
||||
.jstb_link |
||||
@extend .icon-link |
||||
|
||||
.jstb_img |
||||
@extend .icon-image1 |
@ -1,235 +0,0 @@ |
||||
<%#-- copyright |
||||
OpenProject is a project management system. |
||||
Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
|
||||
This program is free software; you can redistribute it and/or |
||||
modify it under the terms of the GNU General Public License version 3. |
||||
|
||||
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
Copyright (C) 2010-2013 the ChiliProject Team |
||||
|
||||
This program is free software; you can redistribute it and/or |
||||
modify it under the terms of the GNU General Public License |
||||
as published by the Free Software Foundation; either version 2 |
||||
of the License, or (at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program; if not, write to the Free Software |
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
See docs/COPYRIGHT.rdoc for more details. |
||||
|
||||
++#%> |
||||
<% content_for :styles do %> |
||||
h1 { font-family: Verdana, sans-serif; font-size: 14px; text-align: center; color: #444; } |
||||
body { font-family: Verdana, sans-serif; font-size: 12px; color: #444; } |
||||
table th { padding-top: 1em; } |
||||
table td { vertical-align: top; background-color: #f5f5f5; height: 2em; vertical-align: middle;} |
||||
table td code { font-size: 1.2em; } |
||||
table td h1 { font-size: 1.8em; text-align: left; } |
||||
table td h2 { font-size: 1.4em; text-align: left; } |
||||
table td h3 { font-size: 1.2em; text-align: left; } |
||||
<% end %> |
||||
|
||||
<% html_title "Wiki Syntax Quick Reference" %> |
||||
<h1>Wiki Syntax Quick Reference</h1> |
||||
|
||||
<table width="100%"> |
||||
<tr> |
||||
<th colspan="3">Font Styles</th> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_strong.png', style: 'border: 1px solid #bbb', alt: 'Strong' %></th> |
||||
<td width="50%">*Strong*</td> |
||||
<td width="50%"><strong>Strong</strong></td> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_em.png', style: 'border: 1px solid #bbb', alt: 'Italic' %></th> |
||||
<td>_Italic_</td> |
||||
<td><em>Italic</em></td> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_ins.png', style: 'border: 1px solid #bbb', alt: 'Underline' %></th> |
||||
<td>+Underline+</td> |
||||
<td><ins>Underline</ins></td> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_del.png', style: 'border: 1px solid #bbb', alt: 'Deleted' %></th> |
||||
<td>-Deleted-</td> |
||||
<td><del>Deleted</del></td> |
||||
</tr> |
||||
<tr> |
||||
<th><div class="generic-table--empty-header"></div></th> |
||||
<td>??Quote??</td> |
||||
<td><cite>Quote</cite></td> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_code.png', style: 'border: 1px solid #bbb', alt: 'Inline Code' %></th> |
||||
<td>@Inline Code@</td> |
||||
<td><code>Inline Code</code></td> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_pre.png', style: 'border: 1px solid #bbb', alt: 'Preformatted text' %></th> |
||||
<td><pre><br /> |
||||
lines<br /> |
||||
of code<br /> |
||||
</pre></td> |
||||
<td> |
||||
<pre> |
||||
lines |
||||
of code |
||||
</pre> |
||||
</td> |
||||
</tr> |
||||
|
||||
<tr> |
||||
<th colspan="3">Lists</th> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_ul.png', style: 'border: 1px solid #bbb', alt: 'Unordered list' %></th> |
||||
<td>* Item 1<br /> |
||||
* Item 2</td> |
||||
<td> |
||||
<ul> |
||||
<li>Item 1</li> |
||||
<li>Item 2</li> |
||||
</ul> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_ol.png', style: 'border: 1px solid #bbb', alt: 'Ordered list' %></th> |
||||
<td># Item 1<br /> |
||||
# Item 2</td> |
||||
<td> |
||||
<ol> |
||||
<li>Item 1</li> |
||||
<li>Item 2</li> |
||||
</ol> |
||||
</td> |
||||
</tr> |
||||
|
||||
<tr> |
||||
<th colspan="3">Headings</th> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_h1.png', style: 'border: 1px solid #bbb', alt: 'Heading 1' %></th> |
||||
<td>h1. Title 1</td> |
||||
<td> |
||||
<h1>Title 1</h1> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_h2.png', style: 'border: 1px solid #bbb', alt: 'Heading 2' %></th> |
||||
<td>h2. Title 2</td> |
||||
<td> |
||||
<h2>Title 2</h2> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_h3.png', style: 'border: 1px solid #bbb', alt: 'Heading 3' %></th> |
||||
<td>h3. Title 3</td> |
||||
<td> |
||||
<h3>Title 3</h3> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<th colspan="3">Links</th> |
||||
</tr> |
||||
<tr> |
||||
<th><div class="generic-table--empty-header"></div></th> |
||||
<td>http://foo.bar</td> |
||||
<td><a href="#">http://foo.bar</a></td> |
||||
</tr> |
||||
<tr> |
||||
<th><div class="generic-table--empty-header"></div></th> |
||||
<td>"Foo":http://foo.bar</td> |
||||
<td><a href="#">Foo</a></td> |
||||
</tr> |
||||
|
||||
<tr> |
||||
<th colspan="3">OpenProject links</th> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_link.png', style: 'border: 1px solid #bbb', alt: 'Link to a Wiki page' %></th> |
||||
<td>[[Wiki page]]</td> |
||||
<td><a href="#">Wiki page</a></td> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_link.png', style: 'border: 1px solid #bbb', alt: 'Link to a Wiki page' %></th> |
||||
<td>[[Sandbox:Wiki page]]</td> |
||||
<td><a href="#">Wiki page</a> (On the Sandbox project)</td> |
||||
</tr> |
||||
<tr> |
||||
<th><div class="generic-table--empty-header"></div></th> |
||||
<td>WorkPackage #12</td> |
||||
<td>WorkPackage <a href="#">#12</a></td> |
||||
</tr> |
||||
<tr> |
||||
<th><div class="generic-table--empty-header"></div></th> |
||||
<td>WorkPackage ##12</td> |
||||
<td>WorkPackage <a href="#">Bug #12: WorkPackage Subject</a> 2012-06-06 – 2013-06-06</td> |
||||
</tr> |
||||
<tr> |
||||
<th><div class="generic-table--empty-header"></div></th> |
||||
<td>WorkPackage ###12</td> |
||||
<td>WorkPackage <a href="#">Bug #12: WorkPackage Subject</a> 2012-06-06 – 2013-06-06 |
||||
|
||||
<div style="padding-left: 10px; padding-top: 0.5em; padding-bottom: 0.5em; line-height: 1.5em;"> |
||||
<strong>Responsible:</strong> John Doe<br> |
||||
<strong>Assigned to:</strong> John Doe |
||||
</div> |
||||
<div style="padding-left: 10px;"> |
||||
I am the description of this work package. |
||||
</div> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<th><div class="generic-table--empty-header"></div></th> |
||||
<td>Link user#4 via ID<br><i>(Using links in work package descriptions or comments will trigger a "you were mentioned" email notification. Type '@' to let an autocompleter suggest you project members to link to.)</i></td> |
||||
<td>Link <a href="#" class="user-mention">John Doe</a> via ID</td> |
||||
</tr> |
||||
<tr> |
||||
<th><div class="generic-table--empty-header"></div></th> |
||||
<td>Link user:"johndoe" via login name <br><i>(Using links in work package descriptions or comments will trigger a "you were mentioned" email notification.)</i></td> |
||||
<td>Link <a href="#" class="user-mention">John Doe</a> via login name</td> |
||||
</tr> |
||||
<tr> |
||||
<th><div class="generic-table--empty-header"></div></th> |
||||
<td>Revision r43</td> |
||||
<td>Revision <a href="#">r43</a></td> |
||||
</tr> |
||||
<tr> |
||||
<th><div class="generic-table--empty-header"></div></th> |
||||
<td>commit:f30e13e43</td> |
||||
<td><a href="#">f30e13e4</a></td> |
||||
</tr> |
||||
<tr> |
||||
<th><div class="generic-table--empty-header"></div></th> |
||||
<td>source:some/file</td> |
||||
<td><a href="#">source:some/file</a></td> |
||||
</tr> |
||||
|
||||
<tr> |
||||
<th colspan="3">Inline images</th> |
||||
</tr> |
||||
<tr> |
||||
<th><%= image_tag 'jstoolbar/bt_img.png', style: 'border: 1px solid #bbb', alt: 'Image' %></th> |
||||
<td>!<em>image_url</em>!</td> |
||||
<td></td> |
||||
</tr> |
||||
<tr> |
||||
<th><div class="generic-table--empty-header"></div></th> |
||||
<td>!<em>attached_image</em>!</td> |
||||
<td></td> |
||||
</tr> |
||||
</table> |
||||
|
||||
<p> |
||||
<%= link_to_function 'More Information', "window.open('wiki_syntax_detailed', '', '')" %> |
||||
</p> |
@ -1,297 +0,0 @@ |
||||
<%#-- copyright |
||||
OpenProject is a project management system. |
||||
Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
|
||||
This program is free software; you can redistribute it and/or |
||||
modify it under the terms of the GNU General Public License version 3. |
||||
|
||||
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
Copyright (C) 2010-2013 the ChiliProject Team |
||||
|
||||
This program is free software; you can redistribute it and/or |
||||
modify it under the terms of the GNU General Public License |
||||
as published by the Free Software Foundation; either version 2 |
||||
of the License, or (at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program; if not, write to the Free Software |
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
See docs/COPYRIGHT.rdoc for more details. |
||||
|
||||
++#%> |
||||
<% content_for :styles do %> |
||||
body { font:80% Verdana,Tahoma,Arial,sans-serif; } |
||||
h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; } |
||||
pre, code { font-size:120%; } |
||||
pre code { font-size:100%; } |
||||
pre { |
||||
margin: 1em 1em 1em 1.6em; |
||||
padding: 2px; |
||||
background-color: #fafafa; |
||||
border: 1px solid #dadada; |
||||
width:95%; |
||||
overflow-x: auto; |
||||
} |
||||
a.new { color: #b73535; } |
||||
|
||||
.CodeRay .c { color:#666; } |
||||
|
||||
.CodeRay .cl { color:#B06; font-weight:bold } |
||||
.CodeRay .dl { color:black } |
||||
.CodeRay .fu { color:#06B; font-weight:bold } |
||||
|
||||
.CodeRay .il { background: #eee } |
||||
.CodeRay .il .idl { font-weight: bold; color: #888 } |
||||
|
||||
.CodeRay .iv { color:#33B } |
||||
.CodeRay .r { color:#080; font-weight:bold } |
||||
|
||||
.CodeRay .s { background-color:#fff0f0 } |
||||
.CodeRay .s .dl { color:#710 } |
||||
<% end %> |
||||
<% html_title "Wiki Formatting" %> |
||||
<h1><a name="1" class="wiki-page"></a>Wiki Formatting</h1> |
||||
<h2><a name="2" class="wiki-page"></a>Links</h2> |
||||
<h3><a name="3" class="wiki-page"></a>OpenProject links</h3> |
||||
<p>OpenProject allows hyperlinking between work packages, changesets and wiki pages from anywhere wiki formatting is used.</p> |
||||
<ul> |
||||
<li><strong>#124</strong> displays a link to a work package: <del><a href="#" class="work package" title="bulk edit doesn't change the category or fixed version properties (Closed)">#124</a></del> (link is striked-through if the work package is closed)</li> |
||||
<li><strong>##124</strong> displays a link to a work package with context information: <a href="#" class="work package status-1 priority-2 overdue created-by-me" title="Work package subject (New)">Bug #12: Work package subject</a> 2012-05-14 - 2012-05-23</li> |
||||
<li><strong>###124</strong> displays a link to a work package with context information and an excerpt (first 3 lines) of the description</li> |
||||
<li><strong>r758</strong> displays a link to a changeset: <a href="#" class="changeset" title="Search engine now only searches objects the user is allowed to view.">r758</a></li> |
||||
<li><strong>commit:c6f4d0fd</strong> displays a link to a changeset with a non-numeric hash: <a href="#" class="changeset">c6f4d0fd</a></li> |
||||
<li><strong>sandbox:r758</strong> displays a link to a changeset of another project: <a href="#" class="changeset" title="Search engine now only searches objects the user is allowed to view.">sandbox:r758</a></li> |
||||
<li><strong>sandbox:c6f4d0fd</strong> displays a link to a changeset with a non-numeric hash: <a href="#" class="changeset">sandbox:c6f4d0fd</a></li> |
||||
</ul> |
||||
<p>Wiki links:</p> |
||||
<ul> |
||||
<li><strong>[[Guide]]</strong> displays a link to the page named 'Guide': <a href="#" class="wiki-page">Guide</a></li> |
||||
<li><strong>[[Guide#further-reading]]</strong> takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: <a href="#" class="wiki-page">Guide</a></li> |
||||
<li><strong>[[Guide|User manual]]</strong> displays a link to the same page but with a different text: <a href="#" class="wiki-page">User manual</a></li> |
||||
</ul> |
||||
<p>You can also link to pages of an other project wiki:</p> |
||||
<ul> |
||||
<li><strong>[[sandbox:some page]]</strong> displays a link to the page named 'Some page' of the Sandbox wiki</li> |
||||
<li><strong>[[sandbox:]]</strong> displays a link to the Sandbox wiki main page</li> |
||||
</ul> |
||||
<p>Wiki links are displayed in red if the page doesn't exist yet, eg: <a href="#" class="wiki-page new">Nonexistent page</a>.</p> |
||||
<p>Links to other resources:</p> |
||||
<ul> |
||||
<li>Versions: |
||||
|
||||
<ul> |
||||
<li><strong>version#3</strong> (link to version with id 3)</li> |
||||
<li><strong>version:1.0.0</strong> (link to version named "1.0.0")</li> |
||||
<li><strong>version:"1.0 beta 2"</strong></li> |
||||
<li><strong>sandbox:version:1.0.0</strong> (link to version "1.0.0" in the project "sandbox")</li> |
||||
</ul> |
||||
</li> |
||||
</ul> |
||||
<ul> |
||||
<li>Attachments: |
||||
|
||||
<ul> |
||||
<li><strong>attachment:file.zip</strong> (link to the attachment of the current object named file.zip)</li> |
||||
<li>For now, attachments of the current object can be referenced only (if you're on a work package, it's possible to reference attachments of this work package only)</li> |
||||
</ul> |
||||
</li> |
||||
</ul> |
||||
<ul> |
||||
<li>Repository files: |
||||
|
||||
<ul> |
||||
<li><strong>source:some/file</strong> (link to the file located at /some/file in the project's repository)</li> |
||||
<li><strong>source:some/file@52</strong> (link to the file's revision 52)</li> |
||||
<li><strong>source:some/file#L120</strong> (link to line 120 of the file)</li> |
||||
<li><strong>source:some/file@52#L120</strong> (link to line 120 of the file's revision 52)</li> |
||||
<li><strong>source:"some file@52#L120"</strong> (use double quotes when the URL contains spaces</li> |
||||
<li><strong>export:some/file</strong> (force the download of the file)</li> |
||||
<li><strong>sandbox:source:some/file</strong> (link to the file located at /some/file in the repository of the project "sandbox")</li> |
||||
<li><strong>sandbox:export:some/file</strong> (force the download of the file)</li> |
||||
</ul> |
||||
</li> |
||||
</ul> |
||||
<ul> |
||||
<li>Forum messages: |
||||
|
||||
<ul> |
||||
<li><strong>message#1218</strong> (link to message with id 1218)</li> |
||||
</ul> |
||||
</li> |
||||
</ul> |
||||
<ul> |
||||
<li>Projects: |
||||
|
||||
<ul> |
||||
<li><strong>project#3</strong> (link to project with id 3)</li> |
||||
<li><strong>project:someproject</strong> (link to project named "someproject")</li> |
||||
</ul> |
||||
</li> |
||||
</ul> |
||||
<p>Escaping:</p> |
||||
<ul> |
||||
<li>You can prevent OpenProject links from being parsed by preceding them with an exclamation mark: !</li> |
||||
</ul> |
||||
<h3><a name="4" class="wiki-page"></a>External links</h3> |
||||
<p>HTTP URLs and email addresses are automatically turned into clickable links:</p> |
||||
<pre> |
||||
https://www.openproject.org, someone@foo.bar |
||||
</pre> |
||||
<p>displays: <a class="external" href="https://www.openproject.org">https://www.openproject.org</a>, <a href="mailto:someone@foo.bar" class="email">someone@foo.bar</a></p> |
||||
<p>If you want to display a specific text instead of the URL, you can use the standard textile syntax:</p> |
||||
<pre> |
||||
"OpenProject web site":https://www.openproject.org |
||||
</pre> |
||||
<p>displays: <a href="https://www.openproject.org" class="external">OpenProject web site</a></p> |
||||
<h2><a name="5" class="wiki-page"></a>Text formatting</h2> |
||||
<p>For things such as headlines, bold, tables, lists, OpenProject supports Textile syntax. See <a class="external" href="http://txstyle.org/">http://txstyle.org/</a> for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.</p> |
||||
<h3><a name="6" class="wiki-page"></a>Font style</h3> |
||||
<pre> |
||||
* *bold* |
||||
* _italic_ |
||||
* _*bold italic*_ |
||||
* +underline+ |
||||
* -strike-through- |
||||
</pre> |
||||
<p>Display:</p> |
||||
<ul> |
||||
<li><strong>bold</strong></li> |
||||
<li><em>italic</em></li> |
||||
<li><em>*bold italic*</em></li> |
||||
<li><ins>underline</ins></li> |
||||
<li><del>strike-through</del></li> |
||||
</ul> |
||||
<h3><a name="7" class="wiki-page"></a>Inline images</h3> |
||||
<ul> |
||||
<li><strong>!image_url!</strong> displays an image located at image_url (textile syntax)</li> |
||||
<li><strong>!>image_url!</strong> right floating image</li> |
||||
<li>If you have an image attached to your wiki page, it can be displayed inline using its filename: <strong>!attached_image.png!</strong></li> |
||||
</ul> |
||||
<h3><a name="8" class="wiki-page"></a>Headings</h3> |
||||
<p>Prefixing a line with <code>h1.</code>, <code>h2.</code> etc. will create a heading:</p> |
||||
<pre> |
||||
h1. Heading |
||||
h2. Subheading |
||||
h3. Subsubheading |
||||
</pre> |
||||
<p>OpenProject assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.</p> |
||||
<h3>Numbered Headings</h3> |
||||
<p> |
||||
You may also define numbered headings by prefixing lines with <code>h1#.</code>, <code>h2#.</code> etc. The heading numbers are then automatically managed by OpenProject for you. |
||||
</p> |
||||
<table> |
||||
<thead> |
||||
<tr> |
||||
<th> |
||||
Wiki format |
||||
</th> |
||||
<th style='padding-left: 3em'> |
||||
Resulting Text |
||||
</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<tr> |
||||
<td> |
||||
<pre> |
||||
h1#. Topic Foo |
||||
h2#. Sub-Topic Foo Bar |
||||
h1#. Topic Baz |
||||
</pre> |
||||
</td> |
||||
<td style='padding-left: 3em'> |
||||
<strong>1. Topic A</strong><br/> |
||||
1.2. Sub-Topic 1<br/> |
||||
<strong>2. Topic B</strong> |
||||
</td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
<h3><a name="9" class="wiki-page"></a>Paragraphs</h3> |
||||
<pre> |
||||
p>. right aligned |
||||
p=. centered |
||||
</pre> |
||||
<p style="text-align:center;">This is a centered paragraph.</p> |
||||
<h3><a name="10" class="wiki-page"></a>Blockquotes</h3> |
||||
<p>Start the paragraph with <strong>bq.</strong></p> |
||||
<pre> |
||||
bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern. |
||||
To go live, all you need to add is a database and a web server. |
||||
</pre> |
||||
<p>Display:</p> |
||||
<blockquote> |
||||
<p>Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.<br /> |
||||
To go live, all you need to add is a database and a web server.</p> |
||||
</blockquote> |
||||
<h3><a name="11" class="wiki-page"></a>Table of content</h3> |
||||
<pre> |
||||
{{toc}} => left aligned toc |
||||
{{>toc}} => right aligned toc |
||||
</pre> |
||||
<h3><a name="11" class="wiki-page"></a>Acronyms</h3> |
||||
<p>Display tooltip for acronym by entering tooltip in parantheses after upper case |
||||
acronym.</p> |
||||
<pre> |
||||
WHO(World Health Organisation) => Displays "World Health Organisation" as |
||||
tooltip of "WHO" |
||||
</pre> |
||||
<p> |
||||
<acronym title="World Health Organisation">WHO</acronym> |
||||
</p> |
||||
<h2><a name="12" class="wiki-page"></a>Macros</h2> |
||||
<p>OpenProject has the following builtin macros:</p> |
||||
<p> |
||||
<dl> |
||||
<dt><code>hello_world</code></dt> |
||||
<dd> |
||||
<p>Sample macro.</p> |
||||
</dd> |
||||
<dt><code>include</code></dt> |
||||
<dd> |
||||
<p>Include a wiki page. Example:</p> |
||||
<pre><code>{{include(Foo)}}</code></pre> |
||||
</dd> |
||||
<dt><code>child_pages</code></dt> |
||||
<dd> |
||||
<p>Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:</p> |
||||
<pre><code> |
||||
{{child_pages}} -- can be used from a wiki page only |
||||
{{child_pages(Foo)}} -- lists all children of page Foo |
||||
{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo |
||||
</code></pre> |
||||
</dd> |
||||
<dt><code>macro_list</code></dt> |
||||
<dd> |
||||
<p>Displays a list of all available macros, including description if available.</p> |
||||
</dd> |
||||
</dl> |
||||
</p> |
||||
<h2><a name="13" class="wiki-page"></a>Code highlighting</h2> |
||||
<p>Code highlighting relies on <a href="http://coderay.rubychan.de/" class="external">CodeRay</a>, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.</p> |
||||
<p>You can highlight code in your wiki page using this syntax:</p> |
||||
<pre> |
||||
<pre><code class="ruby"> |
||||
Place you code here. |
||||
</code></pre> |
||||
</pre> |
||||
<p>Example:</p> |
||||
<pre><code class="ruby CodeRay"><span class="no"> 1</span> <span class="c"># The Greeter class</span> |
||||
<span class="no"> 2</span> <span class="r">class</span> <span class="cl">Greeter</span> |
||||
<span class="no"> 3</span> <span class="r">def</span> <span class="fu">initialize</span>(name) |
||||
<span class="no"> 4</span> <span class="iv">@name</span> = name.capitalize |
||||
<span class="no"> 5</span> <span class="r">end</span> |
||||
<span class="no"> 6</span> |
||||
<span class="no"> 7</span> <span class="r">def</span> <span class="fu">salute</span> |
||||
<span class="no"> 8</span> puts <span class="s"><span class="dl">"</span><span class="k">Hello </span><span class="il"><span class="idl">#{</span><span class="iv">@name</span><span class="idl">}</span></span><span class="k">!</span><span class="dl">"</span></span> |
||||
<span class="no"> 9</span> <span class="r">end</span> |
||||
<span class="no"><strong>10</strong></span> <span class="r">end</span> |
||||
</code> |
||||
</pre> |
@ -1,131 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'redcloth3' |
||||
|
||||
module RedCloth3Patch |
||||
private |
||||
|
||||
def block_textile_prefix(text) |
||||
text.replace(prepend_number_to_heading(text)) |
||||
|
||||
super(text) |
||||
end |
||||
|
||||
HEADING = /^h(\d)\.(.*)$/ unless defined? HEADING |
||||
NUMBERED_HEADING = /^h(\d)#\.(.*)$/ unless defined? NUMBERED_HEADING |
||||
|
||||
def prepend_number_to_heading(text) |
||||
if text =~ NUMBERED_HEADING |
||||
level = $1.to_i |
||||
|
||||
number = get_next_number_or_start_new_numbering level |
||||
|
||||
new_text = "h#{level}. #{number}#{$2}" |
||||
elsif text =~ HEADING |
||||
reset_numbering |
||||
end |
||||
|
||||
new_text.nil? ? text : new_text |
||||
end |
||||
|
||||
def get_next_number_or_start_new_numbering(level) |
||||
begin |
||||
number = get_number_for_level level |
||||
rescue ArgumentError |
||||
reset_numbering |
||||
number = get_number_for_level level |
||||
end |
||||
|
||||
number |
||||
end |
||||
|
||||
def get_number_for_level(level) |
||||
@numbering_provider ||= Redcloth3::NumberingStack.new level |
||||
|
||||
@numbering_provider.get_next_numbering_for_level level |
||||
end |
||||
|
||||
def reset_numbering |
||||
@numbering_provider = nil |
||||
end |
||||
end |
||||
|
||||
RedCloth3.send(:prepend, RedCloth3Patch) |
||||
|
||||
module Redcloth3 |
||||
class NumberingStack |
||||
def initialize(level) |
||||
@stack = [] |
||||
@init_level = level ? level.to_i : 1 |
||||
end |
||||
|
||||
def get_next_numbering_for_level(level) |
||||
internal_level = map_external_to_internal_level level |
||||
|
||||
increase_numbering_for_level internal_level |
||||
|
||||
current_numbering |
||||
end |
||||
|
||||
private |
||||
|
||||
def increase_numbering_for_level(level) |
||||
if @stack[level].nil? |
||||
@stack[level] = 1 |
||||
fill_nil_levels_with_zero |
||||
else |
||||
@stack[level] += 1 |
||||
reset_higher_levels_than level |
||||
end |
||||
|
||||
@stack[level] |
||||
end |
||||
|
||||
def reset_higher_levels_than(level) |
||||
@stack = @stack.slice! 0, level + 1 |
||||
end |
||||
|
||||
def current_numbering |
||||
@stack.join('.') + '.' |
||||
end |
||||
|
||||
def map_external_to_internal_level(level) |
||||
if level.to_i < @init_level |
||||
raise ArgumentError, 'Current level lower than initial level' |
||||
end |
||||
level.to_i - @init_level |
||||
end |
||||
|
||||
def fill_nil_levels_with_zero |
||||
@stack.map! { |e| e.nil? ? 0 : e } |
||||
end |
||||
end |
||||
end |
@ -1,104 +0,0 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
Feature: Issue textile quickinfo links |
||||
|
||||
Background: |
||||
Given there is 1 project with the following: |
||||
| name | parent | |
||||
| identifier | parent | |
||||
And I am working in project "parent" |
||||
And there is a board "development discussion" for project "parent" |
||||
And there is a role "member" |
||||
And the role "member" may have the following rights: |
||||
| manage_boards | |
||||
| add_messages | |
||||
| edit_messages | |
||||
| edit_own_messages | |
||||
| delete_messages | |
||||
| delete_messages | |
||||
| delete_own_messages | |
||||
And there is 1 user with the following: |
||||
| login | bob | |
||||
And the user "bob" is a "member" in the project "parent" |
||||
And I am already logged in as "bob" |
||||
|
||||
Scenario: Adding a message to a forum |
||||
When I go to the boards page of the project called "parent" |
||||
And I follow "New message" |
||||
And I fill in "New relase" for "message_subject" |
||||
And I fill in "We have release a new version of our software." for "message_content" |
||||
When I click on the first button matching "Create" |
||||
Then I should see "New relase" within "#content" |
||||
Then I should see "We have release a new version of our software." within "#content" |
||||
|
||||
Scenario: Message's reply count is zero |
||||
Given the board "development discussion" has the following messages: |
||||
| message #1 | |
||||
When I go to the message page of message "message #1" |
||||
Then I should not see "Replies" |
||||
|
||||
Scenario: Message's reply count is two |
||||
Given the board "development discussion" has the following messages: |
||||
| message #1 | |
||||
And "message #1" has the following replies: |
||||
| reply #1 | |
||||
| reply #2 | |
||||
When I go to the message page of message "message #1" |
||||
Then I should see "Replies (2)" |
||||
|
||||
Scenario: Check field value after error message raise when title is empty |
||||
When I go to the boards page of the project called "parent" |
||||
And I follow "New message" |
||||
And I fill in "New relase FAQ" for "message_subject" |
||||
When I click on the first button matching "Create" |
||||
Then there should be an error message |
||||
Then the "message_subject" field should contain "New relase FAQ" |
||||
|
||||
Scenario: Check field value after error message raise when description is empty |
||||
When I go to the boards page of the project called "parent" |
||||
And I follow "New message" |
||||
And I fill in "Here you find the most frequently asked questions" for "message_content" |
||||
When I click on the first button matching "Create" |
||||
Then there should be an error message |
||||
Then the "message_content" field should contain "Here you find the most frequently asked questions" |
||||
|
||||
@javascript |
||||
Scenario: Sticky message on top of messages list |
||||
Given the board "development discussion" has the following messages: |
||||
| message #1 | |
||||
| message #2 | |
||||
| message #3 | |
||||
When I go to the boards page of the project called "parent" |
||||
And I follow "New message" |
||||
And I fill in "How to?" for "message_subject" |
||||
And I fill in "How to st-up project on local mashine." for "message_content" |
||||
And I check "Sticky" |
||||
When I click on the first button matching "Create" |
||||
And I go to the boards page of the project called "parent" |
||||
Then "How to?" should be the first row in table |
@ -1,73 +0,0 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
Feature: Adding and Editing Wiki Tabs |
||||
|
||||
Background: |
||||
Given there is 1 project with the following: |
||||
| Name | Wookies | |
||||
And the project "Wookies" uses the following modules: |
||||
| wiki | |
||||
And the project "Wookies" has 1 wiki page with the following: |
||||
| Title | wookietest | |
||||
|
||||
@javascript |
||||
Scenario: Adding simple wiki tab as admin |
||||
Given I am admin |
||||
And I am working in project "Wookies" |
||||
And I go to the wiki index page of the project called "Wookies" |
||||
|
||||
@javascript |
||||
Scenario: Editing of wiki pages as a member with proper rights |
||||
Given there is 1 user with the following: |
||||
| login | chewbacca| |
||||
And I am logged in as "chewbacca" |
||||
And there is a role "humanoid" |
||||
And the role "humanoid" may have the following rights: |
||||
| view_wiki_pages | |
||||
| edit_wiki_pages | |
||||
And the user "chewbacca" is a "humanoid" in the project "Wookies" |
||||
When I go to the wiki page "wookietest" for the project called "Wookies" |
||||
And I click "Edit" |
||||
And I fill in "content_text" with "testing wookie" |
||||
And I click "Save" |
||||
Then I should see "testing wookie" within "#content" |
||||
And I click "Edit" |
||||
|
||||
|
||||
@javascript |
||||
Scenario: Overview and see the history of a wiki page |
||||
Given I am already admin |
||||
Given the wiki page "wookietest" of the project "Wookies" has 3 versions |
||||
And I go to the wiki page "wookietest" for the project called "Wookies" |
||||
And I follow "More" within "#content" |
||||
When I click "History" |
||||
Then I should see "History" within "#content" |
||||
When I press "View differences" |
||||
Then I should see "Version 1/4" |
||||
Then I should see "Version 2/4" |
@ -1,71 +0,0 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
Feature: Creating a wiki child page |
||||
|
||||
Background: |
||||
Given there are no wiki menu items |
||||
And there is 1 user with the following: |
||||
| login | bob | |
||||
And there is a role "member" |
||||
And the role "member" may have the following rights: |
||||
| view_wiki_pages | |
||||
| edit_wiki_pages | |
||||
And there is 1 project with the following: |
||||
| name | project1 | |
||||
| identifier | project1 | |
||||
| name | project1 | |
||||
And the user "bob" is a "member" in the project "project1" |
||||
And I am already logged in as "bob" |
||||
|
||||
@javascript |
||||
Scenario: A user with proper rights can add a child wiki page |
||||
Given the project "project1" has 1 wiki page with the following: |
||||
| title | Wikiparentpage | |
||||
Given I go to the wiki index page of the project called "project1" |
||||
And I click "Wikiparentpage" |
||||
And I click "Wiki page" |
||||
And I fill in "content_page_title" with "Todd's wiki" |
||||
And I fill in "content_text" with "Great content" |
||||
And I press "Save" |
||||
When I go to the wiki index page of the project called "project1" |
||||
Then I should see "Todd's wiki" within "#content" |
||||
|
||||
@javascript |
||||
Scenario: Creating a wiki child page the title of which contains special characters |
||||
Given the project "project1" has 1 wiki page with the following: |
||||
| title | ParentWikiPage | |
||||
And the project "project1" has 1 wiki menu item with the following: |
||||
| title | ParentWikiPage | |
||||
| new_wiki_page | true | |
||||
When I go to the wiki page "ParentWikiPage" of the project called "project1" |
||||
And I click "Wiki page" |
||||
And I fill in "content_page_title" with "Child Page !@#{$%^&*()_},./<>?;':" |
||||
And I fill in "content_text" with "Great content" |
||||
And I click "Save" |
||||
Then I should see "Successful creation." |
@ -1,52 +0,0 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
Feature: Activating and deactivating wiki menu as admin |
||||
|
||||
Background: |
||||
|
||||
Given I am admin |
||||
And there is 1 project with the following: |
||||
|
||||
| Name | Wookie | |
||||
|
||||
@javascript |
||||
Scenario: Activation of wiki module via aproject settings as admin |
||||
When I go to the settings page of the project called "Wookie" |
||||
And I click on "tab-modules" |
||||
And I check "Wiki" |
||||
And I press "Save" |
||||
Then I should see "Wiki" within "#menu-sidebar" |
||||
|
||||
@javascript |
||||
Scenario: Deactivation of wiki module via project settings |
||||
When I go to the settings page of the project called "Wookie" |
||||
And I click on "tab-modules" |
||||
And I uncheck "Wiki" |
||||
And I press "Save" |
||||
And I should not see "Wiki" within "#menu-sidebar" |
@ -1,66 +0,0 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
Feature: Viewing the wiki new child page |
||||
|
||||
Background: |
||||
Given there are no wiki menu items |
||||
And there is 1 user with the following: |
||||
| login | bob | |
||||
And there is a role "member" |
||||
And the role "member" may have the following rights: |
||||
| view_wiki_pages | |
||||
| edit_wiki_pages | |
||||
And there is 1 project with the following: |
||||
| name | project1 | |
||||
| identifier | project1 | |
||||
And the user "bob" is a "member" in the project "project1" |
||||
And I am already logged in as "bob" |
||||
|
||||
Scenario: Visiting the wiki new child page with a parent page that has the new child page option enabled on it's menu item should show the page and select the toc menu entry within the wiki menu item |
||||
Given the project "project1" has 1 wiki page with the following: |
||||
| title | ParentWikiPage | |
||||
And the project "project1" has 1 wiki menu item with the following: |
||||
| title | ParentWikiPage | |
||||
| new_wiki_page | true | |
||||
When I go to the wiki page "ParentWikiPage" of the project called "project1" |
||||
Then I should see "Wiki page" within "#content" |
||||
|
||||
Scenario: Visiting the wiki new child page with a related page that has the new child page option disabled on it's menu item should show the page and select no menu item |
||||
Given the project "project1" has 1 wiki page with the following: |
||||
| title | ParentWikiPage | |
||||
And the project "project1" has 1 wiki menu item with the following: |
||||
| title | ParentWikiPage | |
||||
When I go to the wiki new child page below the "ParentWikiPage" page of the project called "project1" |
||||
Then I should see "Wiki page" within "#content" |
||||
And there should be no child menu item selected |
||||
|
||||
Scenario: Visiting the wiki new child page with an invalid parent page |
||||
When I go to the wiki new child page below the "InvalidPage" page of the project called "project1" |
||||
Then I should see "404" within "#content" |
||||
Then I should see "The page you were trying to access doesn't exist or has been removed." |
@ -1,42 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {WikiToolbar} from "core-app/globals/augmenting/wiki-toolbar"; |
||||
|
||||
(function($:JQueryStatic) { |
||||
|
||||
// Identify all uses
|
||||
$(function () { |
||||
|
||||
// Wrap all static wiki-toolbars (rendered from backend)
|
||||
$('.wiki-toolbar') |
||||
.each((i:number, el:HTMLElement) => { |
||||
new WikiToolbar(I18n, el); |
||||
}); |
||||
}); |
||||
}(jQuery)); |
@ -1,91 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {GlobalI18n} from "core-app/modules/common/i18n/i18n.service"; |
||||
|
||||
export class WikiToolbar { |
||||
public isPreview = false; |
||||
|
||||
constructor(protected I18n:GlobalI18n, |
||||
protected element:HTMLElement, |
||||
public previewCallback?:(preview:boolean) => void) { |
||||
|
||||
this.onInit(); |
||||
} |
||||
|
||||
onInit() { |
||||
const element = jQuery(this.element); |
||||
const help_link_title = this.I18n.t('js.inplace.link_formatting_help'); |
||||
|
||||
const button = document.createElement('button'); |
||||
button.classList.add('jstb_help', 'formatting-help-link-button'); |
||||
button.setAttribute('type', 'button'); |
||||
button.setAttribute('aria-label', help_link_title); |
||||
button.setAttribute('title', help_link_title); |
||||
|
||||
const PREVIEW_ENABLE_TEXT = this.I18n.t('js.inplace.btn_preview_enable'); |
||||
const PREVIEW_DISABLE_TEXT = this.I18n.t('js.inplace.btn_preview_disable'); |
||||
const PREVIEW_BUTTON_CLASS = 'jstb_preview'; |
||||
let previewButtonAttributes:any = { |
||||
'class': PREVIEW_BUTTON_CLASS + ' icon-preview icon-small', |
||||
'type': 'button', |
||||
'title': PREVIEW_ENABLE_TEXT, |
||||
'aria-label': PREVIEW_ENABLE_TEXT, |
||||
'text': '' |
||||
}; |
||||
|
||||
let textarea = this.element; |
||||
if (!element.is('textarea')) { |
||||
textarea = this.element.querySelector('textarea') as any; |
||||
} |
||||
|
||||
const wikiToolbar = new (window as any).jsToolBar(textarea); |
||||
wikiToolbar.setHelpLink(button); |
||||
wikiToolbar.draw(); |
||||
|
||||
previewButtonAttributes.click = () => { |
||||
this.isPreview = !this.isPreview; |
||||
!!this.previewCallback && this.previewCallback(this.isPreview); |
||||
|
||||
const title = this.isPreview ? PREVIEW_DISABLE_TEXT : PREVIEW_ENABLE_TEXT; |
||||
const toggledClasses = 'icon-preview icon-ticket-edit -active'; |
||||
|
||||
element.closest('.textarea-wrapper') |
||||
.find('.' + PREVIEW_BUTTON_CLASS).attr('title', title) |
||||
.attr('aria-label', title) |
||||
.toggleClass(toggledClasses); |
||||
}; |
||||
|
||||
if (!!this.previewCallback) { |
||||
element |
||||
.closest('.textarea-wrapper') |
||||
.find('.jstb_help') |
||||
.after(jQuery('<button>', previewButtonAttributes)); |
||||
} |
||||
} |
||||
} |
@ -1,59 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {PathHelperService} from './../path-helper/path-helper.service'; |
||||
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; |
||||
import {HalLink} from 'core-app/modules/hal/hal-link/hal-link'; |
||||
import {WorkPackageNotificationService} from "core-components/wp-edit/wp-notification.service"; |
||||
import {Inject, Injectable} from "@angular/core"; |
||||
import {HttpClient} from "@angular/common/http"; |
||||
|
||||
@Injectable() |
||||
export class TextileService { |
||||
constructor(private readonly http:HttpClient, |
||||
private readonly wpNotificationsService:WorkPackageNotificationService, |
||||
private readonly PathHelper:PathHelperService) { |
||||
} |
||||
|
||||
public renderWithWorkPackageContext(workPackage:WorkPackageResource, text:string) { |
||||
return this.render(workPackage.previewMarkup, text); |
||||
} |
||||
|
||||
public render(link:HalLink, text:string) { |
||||
return this.http |
||||
.request( |
||||
link.method, |
||||
link.href!, { |
||||
body: text, |
||||
responseType: 'text', |
||||
headers: {'Content-Type': 'text/plain; charset=UTF-8'} |
||||
}) |
||||
.toPromise() |
||||
.catch((error:any) => this.wpNotificationsService.handleRawError(error)); |
||||
} |
||||
} |
@ -1,59 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {AfterViewInit, Directive, ElementRef, EventEmitter, OnDestroy, Output} from "@angular/core"; |
||||
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
||||
import {WikiToolbar} from "core-app/globals/augmenting/wiki-toolbar"; |
||||
|
||||
@Directive({ |
||||
selector: '[op-wiki-toolbar]' |
||||
}) |
||||
export class WikiToolbarDirective implements AfterViewInit, OnDestroy { |
||||
@Output() onPreviewToggle = new EventEmitter<undefined>(); |
||||
public instance:WikiToolbar; |
||||
|
||||
constructor(readonly I18n:I18nService, |
||||
readonly elementRef:ElementRef) { |
||||
|
||||
} |
||||
|
||||
ngAfterViewInit() { |
||||
this.instance = new WikiToolbar( |
||||
this.I18n, |
||||
this.elementRef.nativeElement, |
||||
() => { |
||||
this.onPreviewToggle.emit(); |
||||
}); |
||||
} |
||||
|
||||
|
||||
|
||||
ngOnDestroy() { |
||||
// nothing to do
|
||||
} |
||||
} |
@ -1,80 +0,0 @@ |
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
// ++
|
||||
|
||||
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from "@angular/core"; |
||||
import {EditFieldComponent} from "core-app/modules/fields/edit/edit-field.component"; |
||||
import {FormattableEditField} from "core-app/modules/fields/edit/field-types/formattable-edit-field"; |
||||
import {ExpressionService} from "../../../../../../common/expression.service"; |
||||
|
||||
@Component({ |
||||
template: ` |
||||
<div class="textarea-wrapper" |
||||
op-wiki-toolbar |
||||
(onPreviewToggle)="field.togglePreview()" |
||||
[ngClass]="{'-preview': field.isPreview}"> |
||||
<textarea |
||||
style="min-height: 114px" |
||||
#textAreaField |
||||
class="focus-input wp-inline-edit--field inplace-edit--textarea -animated" |
||||
name="value" |
||||
[hidden]="field.isPreview" |
||||
[disabled]="field.isBusy || field.inFlight" |
||||
[required]="field.required" |
||||
[(ngModel)]="field.rawValue" |
||||
[id]="handler.htmlId"> |
||||
</textarea> |
||||
<div class="inplace-edit--preview" |
||||
*ngIf="field.isPreview && !field.isBusy"> |
||||
<span [innerHTML]="unEscapedPreviewHtml"></span> |
||||
</div> |
||||
<div *ngIf="field.isFormattable && !field.isPreview" |
||||
class="wp-edit-field-attachment-label"> |
||||
<span [textContent]="field.text.attachmentLabel"></span> |
||||
</div> |
||||
<edit-field-controls *ngIf="!handler.inEditMode" |
||||
[fieldController]="handler" |
||||
(onSave)="handler.handleUserSubmit()" |
||||
(onCancel)="handler.handleUserCancel()" |
||||
[saveTitle]="field.text.save" |
||||
[cancelTitle]="field.text.cancel"> |
||||
</edit-field-controls> |
||||
</div> |
||||
|
||||
` |
||||
}) |
||||
export class FormattableTextareaEditFieldComponent extends EditFieldComponent implements AfterViewInit { |
||||
public field:FormattableEditField; |
||||
@ViewChild('textAreaField') public textAreaField:ElementRef; |
||||
|
||||
ngAfterViewInit() { |
||||
this.textAreaField.nativeElement.focus(); |
||||
} |
||||
|
||||
public get unEscapedPreviewHtml() { |
||||
return ExpressionService.unescape(this.field.previewHtml || ''); |
||||
} |
||||
} |
@ -0,0 +1,66 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject::TextFormatting |
||||
module Formats |
||||
class << self |
||||
attr_reader :plain, :rich |
||||
|
||||
%i(plain rich).each do |flavor| |
||||
define_method("#{flavor}_format") do |
||||
send(flavor).format |
||||
end |
||||
|
||||
define_method("#{flavor}_formatter") do |
||||
send(flavor).formatter |
||||
end |
||||
|
||||
define_method("#{flavor}_helper") do |
||||
send(flavor).helper |
||||
end |
||||
|
||||
define_method("register_#{flavor}!") do |klass| |
||||
instance_variable_set("@#{flavor}", klass) |
||||
end |
||||
end |
||||
|
||||
def supported?(name) |
||||
[plain, rich].map(&:format).include?(name.to_sym) |
||||
end |
||||
|
||||
def plain?(name) |
||||
name && plain.format == name.to_sym |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
OpenProject::TextFormatting::Formats.register_plain! ::OpenProject::TextFormatting::Formats::Plain::Format |
||||
OpenProject::TextFormatting::Formats.register_rich! ::OpenProject::TextFormatting::Formats::Markdown::Format |
@ -0,0 +1,43 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject::TextFormatting::Formats::Markdown |
||||
class Format < OpenProject::TextFormatting::Formats::BaseFormat |
||||
class << self |
||||
def format |
||||
:markdown |
||||
end |
||||
|
||||
def priority |
||||
5 |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,67 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject::TextFormatting::Formats::Markdown |
||||
class Formatter < OpenProject::TextFormatting::Formats::BaseFormatter |
||||
attr_reader :context, |
||||
:pipeline |
||||
|
||||
def initialize(context) |
||||
@context = context |
||||
@pipeline = ::HTML::Pipeline.new(located_filters, context) |
||||
end |
||||
|
||||
def to_html(text) |
||||
result = pipeline.call(text, context) |
||||
output = result[:output].to_s |
||||
|
||||
output.html_safe |
||||
end |
||||
|
||||
def to_document(text) |
||||
pipeline.to_document text, context |
||||
end |
||||
|
||||
def filters |
||||
[ |
||||
:markdown, |
||||
:sanitization, |
||||
HTML::Pipeline::TableOfContentsFilter, |
||||
:macro, |
||||
:pattern_matcher, |
||||
:autolink |
||||
] |
||||
end |
||||
|
||||
def self.format |
||||
:markdown |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,43 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject::TextFormatting::Formats::Plain |
||||
class Format < OpenProject::TextFormatting::Formats::BaseFormat |
||||
class << self |
||||
def format |
||||
:plain |
||||
end |
||||
|
||||
def priority |
||||
100 |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,88 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject::TextFormatting |
||||
module Formatters |
||||
class << self |
||||
|
||||
def registered |
||||
unless defined? @formatters |
||||
register_default! |
||||
end |
||||
|
||||
@formatters |
||||
end |
||||
|
||||
def registered?(key) |
||||
registered.key? key.to_sym |
||||
end |
||||
|
||||
def register(namespace:) |
||||
# Force lookup to avoid const errors later on. |
||||
key = namespace.to_sym |
||||
modulename = namespace.to_s.classify |
||||
|
||||
raise ArgumentError, "format name '#{name}' is already taken" if registered?(key) |
||||
|
||||
begin |
||||
formatter = "OpenProject::TextFormatting::Formatters::#{modulename}::Formatter".constantize |
||||
helper = "OpenProject::TextFormatting::Formatters::#{modulename}::Helper".constantize |
||||
registered[key] = { formatter: formatter, helper: helper } |
||||
rescue NameError => e |
||||
Rails.logger.error "Failed to register wiki formatting #{namespace}: #{e}" |
||||
Rails.logger.debug { e.backtrace } |
||||
end |
||||
end |
||||
|
||||
def formatter_for(name) |
||||
entry = registered.fetch(name.to_sym) { registered[:plain] } |
||||
entry[:formatter] |
||||
end |
||||
|
||||
def helper_for(name) |
||||
entry = registered.fetch(name.to_sym) { registered[:plain] } |
||||
entry[:helper] |
||||
end |
||||
|
||||
def format_names |
||||
registered.keys.map |
||||
end |
||||
|
||||
private |
||||
|
||||
def register_default! |
||||
@formatters = {} |
||||
register namespace: :plain |
||||
register namespace: :markdown |
||||
register namespace: :textile |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,515 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject::TextFormatting::Formatters |
||||
module Textile |
||||
class Formatter < OpenProject::TextFormatting::Formatters::Base |
||||
|
||||
include Redmine::WikiFormatting::Macros::Definitions |
||||
include ActionView::Helpers::SanitizeHelper |
||||
include Redmine::I18n |
||||
# used for the work package quick links |
||||
include WorkPackagesHelper |
||||
include ApplicationHelper |
||||
# Used for escaping helper 'h()' |
||||
include ERB::Util |
||||
# Rails helper |
||||
include ActionView::Context |
||||
include ActionView::Helpers::TagHelper |
||||
include ActionView::Helpers::UrlHelper |
||||
include ActionView::Helpers::TextHelper |
||||
# For route path helpers |
||||
include OpenProject::ObjectLinking |
||||
include OpenProject::StaticRouting::UrlHelpers |
||||
# Truncation |
||||
include OpenProject::TextFormatting::Truncation |
||||
|
||||
def controller; end |
||||
|
||||
def to_html(text) |
||||
edit = !!options[:edit] |
||||
# don't return html in edit mode when textile or text formatting is enabled |
||||
return text if edit |
||||
|
||||
object = options[:object] |
||||
project = options[:project] |
||||
only_path = options.delete(:only_path) != false |
||||
|
||||
# offer 'plain' as readable version for 'no formatting' to callers |
||||
format = options.delete(:format) { :textile } |
||||
text = RedclothWrapper.new(text).to_html |
||||
|
||||
# TODO: transform modifications into WikiFormatting Helper, or at least ask the helper if he wants his stuff to be modified |
||||
@parsed_headings = [] |
||||
text = parse_non_pre_blocks(text) { |text| |
||||
[:execute_macros, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings, :parse_relative_urls].each do |method_name| |
||||
send method_name, text, project, object, options[:attribute], only_path, options |
||||
end |
||||
} |
||||
|
||||
if @parsed_headings.any? |
||||
replace_toc(text, @parsed_headings, options) |
||||
end |
||||
|
||||
escape_non_macros(text) |
||||
text.html_safe |
||||
end |
||||
|
||||
## |
||||
# Escape double curly braces after macro expansion. |
||||
# This will avoid arbitrary angular expressions to be evaluated in |
||||
# formatted text marked html_safe. |
||||
def escape_non_macros(text) |
||||
text.gsub!(/\{\{(?! \$root\.DOUBLE_LEFT_CURLY_BRACE)/, '{{ $root.DOUBLE_LEFT_CURLY_BRACE }}') |
||||
end |
||||
|
||||
def parse_non_pre_blocks(text) |
||||
s = StringScanner.new(text) |
||||
tags = [] |
||||
parsed = '' |
||||
while !s.eos? |
||||
s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) |
||||
text = s[1] |
||||
full_tag = s[2] |
||||
closing = s[3] |
||||
tag = s[4] |
||||
if tags.empty? |
||||
yield text |
||||
end |
||||
parsed << text |
||||
if tag |
||||
if closing |
||||
if tags.last == tag.downcase |
||||
tags.pop |
||||
end |
||||
else |
||||
tags << tag.downcase |
||||
end |
||||
parsed << full_tag |
||||
end |
||||
end |
||||
# Close any non closing tags |
||||
while tag = tags.pop |
||||
parsed << "</#{tag}>" |
||||
end |
||||
parsed |
||||
end |
||||
|
||||
|
||||
MACROS_RE = / |
||||
(!)? # escaping |
||||
( |
||||
\{\{ # opening tag |
||||
([\w]+) # macro name |
||||
(\(([^\}]*)\))? # optional arguments |
||||
\}\} # closing tag |
||||
) |
||||
/x unless const_defined?(:MACROS_RE) |
||||
|
||||
# Macros substitution |
||||
def execute_macros(text, project, obj, _attr, _only_path, options) |
||||
return if !!options[:edit] |
||||
text.gsub!(MACROS_RE) do |
||||
esc = $1 |
||||
all = $2 |
||||
macro = $3 |
||||
args = ($5 || '').split(',').each(&:strip!) |
||||
if esc.nil? |
||||
begin |
||||
exec_macro(macro, obj, args, view: self, project: project) |
||||
rescue => e |
||||
"<span class=\"flash error macro-unavailable permanent\">\ |
||||
#{::I18n.t(:macro_execution_error, macro_name: macro)} (#{e})\ |
||||
</span>".squish |
||||
rescue NotImplementedError |
||||
"<span class=\"flash error macro-unavailable permanent\">\ |
||||
#{::I18n.t(:macro_unavailable, macro_name: macro)}\ |
||||
</span>".squish |
||||
end || all |
||||
else |
||||
all |
||||
end |
||||
end |
||||
end |
||||
|
||||
RELATIVE_LINK_RE = %r{ |
||||
<a |
||||
(?: |
||||
(\shref= |
||||
(?: # the href and link |
||||
(?:'(\/[^>]+?)')| |
||||
(?:"(\/[^>]+?)") |
||||
) |
||||
)| |
||||
[^>] |
||||
)* |
||||
> |
||||
[^<]*?<\/a> # content and closing link tag. |
||||
}x unless const_defined?(:RELATIVE_LINK_RE) |
||||
|
||||
def parse_relative_urls(text, _project, _obj, _attr, only_path, _options) |
||||
return if only_path |
||||
text.gsub!(RELATIVE_LINK_RE) do |m| |
||||
href = $1 |
||||
relative_url = $2 || $3 |
||||
next m unless href.present? |
||||
request = options[:request] |
||||
if request.present? |
||||
# we have a request! |
||||
protocol = request.protocol |
||||
host_with_port = request.host_with_port |
||||
elsif @controller |
||||
# use the same methods as url_for in the Mailer |
||||
url_opts = @controller.class.default_url_options |
||||
next m unless url_opts && url_opts[:protocol] && url_opts[:host] |
||||
protocol = "#{url_opts[:protocol]}://" |
||||
host_with_port = url_opts[:host] |
||||
else |
||||
next m |
||||
end |
||||
m.sub href, " href=\"#{protocol}#{host_with_port}#{relative_url}\"" |
||||
end |
||||
end |
||||
|
||||
def parse_inline_attachments(text, _project, obj, _attr, only_path, options) |
||||
# when using an image link, try to use an attachment, if possible |
||||
if options[:attachments] || (obj && obj.respond_to?(:attachments)) |
||||
attachments = nil |
||||
text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| |
||||
filename = $1.downcase |
||||
ext = $2 |
||||
alt = $3 |
||||
alttext = $4 |
||||
attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_at).reverse |
||||
# search for the picture in attachments |
||||
if found = attachments.detect { |att| att.filename.downcase == filename } |
||||
image_url = url_for only_path: only_path, controller: '/attachments', action: 'download', id: found |
||||
desc = found.description.to_s.gsub('"', '') |
||||
if !desc.blank? && alttext.blank? |
||||
alt = " title=\"#{desc}\" alt=\"#{desc}\"" |
||||
end |
||||
"src=\"#{image_url}\"#{alt}" |
||||
else |
||||
m |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
# Wiki links |
||||
# |
||||
# Examples: |
||||
# [[mypage]] |
||||
# [[mypage|mytext]] |
||||
# wiki links can refer other project wikis, using project name or identifier: |
||||
# [[project:]] -> wiki starting page |
||||
# [[project:|mytext]] |
||||
# [[project:mypage]] |
||||
# [[project:mypage|mytext]] |
||||
def parse_wiki_links(text, project, _obj, _attr, only_path, options) |
||||
text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |_m| |
||||
link_project = project |
||||
esc = $1 |
||||
all = $2 |
||||
page = $3 |
||||
title = $5 |
||||
if esc.nil? |
||||
if page =~ /\A([^\:]+)\:(.*)\z/ |
||||
link_project = Project.find_by(identifier: $1) || Project.find_by(name: $1) |
||||
page = $2 |
||||
title ||= $1 if page.blank? |
||||
end |
||||
|
||||
if link_project && link_project.wiki |
||||
# extract anchor |
||||
anchor = nil |
||||
if page =~ /\A(.+?)\#(.+)\z/ |
||||
page = $1 |
||||
anchor = $2 |
||||
end |
||||
# Unescape the escaped entities from textile |
||||
page = CGI.unescapeHTML(page) |
||||
# check if page exists |
||||
wiki_page = link_project.wiki.find_page(page) |
||||
default_wiki_title = wiki_page.nil? ? page : wiki_page.title |
||||
wiki_title = title || default_wiki_title |
||||
url = case options[:wiki_links] |
||||
when :local; "#{title}.html" |
||||
when :anchor; "##{title}" # used for single-file wiki export |
||||
else |
||||
wiki_page_id = wiki_page.nil? ? page.to_url : wiki_page.slug |
||||
url_for(only_path: only_path, |
||||
controller: '/wiki', |
||||
action: 'show', |
||||
project_id: link_project, |
||||
id: wiki_page_id, |
||||
title: wiki_page.nil? ? wiki_title.strip : nil, |
||||
anchor: anchor) |
||||
end |
||||
link_to(h(wiki_title), url, class: ('wiki-page' + (wiki_page ? '' : ' new'))) |
||||
else |
||||
# project or wiki doesn't exist |
||||
all |
||||
end |
||||
else |
||||
all |
||||
end |
||||
end |
||||
end |
||||
|
||||
# Redmine links |
||||
# |
||||
# Examples: |
||||
# Issues: |
||||
# #52 -> Link to issue #52 |
||||
# Changesets: |
||||
# r52 -> Link to revision 52 |
||||
# commit:a85130f -> Link to scmid starting with a85130f |
||||
# Documents: |
||||
# document#17 -> Link to document with id 17 |
||||
# document:Greetings -> Link to the document with title "Greetings" |
||||
# document:"Some document" -> Link to the document with title "Some document" |
||||
# Versions: |
||||
# version#3 -> Link to version with id 3 |
||||
# version:1.0.0 -> Link to version named "1.0.0" |
||||
# version:"1.0 beta 2" -> Link to version named "1.0 beta 2" |
||||
# Attachments: |
||||
# attachment:file.zip -> Link to the attachment of the current object named file.zip |
||||
# Source files: |
||||
# source:some/file -> Link to the file located at /some/file in the project's repository |
||||
# source:some/file@52 -> Link to the file's revision 52 |
||||
# source:some/file#L120 -> Link to line 120 of the file |
||||
# source:some/file@52#L120 -> Link to line 120 of the file's revision 52 |
||||
# export:some/file -> Force the download of the file |
||||
# Forum messages: |
||||
# message#1218 -> Link to message with id 1218 |
||||
# |
||||
# Links can refer other objects from other projects, using project identifier: |
||||
# identifier:r52 |
||||
# identifier:document:"Some document" |
||||
# identifier:version:1.0.0 |
||||
# identifier:source:some/file |
||||
def parse_redmine_links(text, project, obj, attr, only_path, options) |
||||
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|version|commit|source|export|message|project|user|group)?((#+|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |_m| |
||||
leading = $1 |
||||
esc = $2 |
||||
project_prefix = $3 |
||||
project_identifier = $4 |
||||
prefix = $5 |
||||
sep = $7 || $9 |
||||
identifier = $8 || $10 |
||||
link = nil |
||||
if project_identifier |
||||
project = Project.visible.find_by(identifier: project_identifier) |
||||
end |
||||
if esc.nil? |
||||
if prefix.nil? && sep == 'r' |
||||
# project.changesets.visible raises an SQL error because of a double join on repositories |
||||
if project && project.repository && (changeset = Changeset.visible.find_by(repository_id: project.repository.id, revision: identifier)) |
||||
link = link_to(h("#{project_prefix}r#{identifier}"), { only_path: only_path, controller: '/repositories', action: 'revision', project_id: project, rev: changeset.revision }, |
||||
class: 'changeset', |
||||
title: truncate_single_line(changeset.comments, length: 100)) |
||||
end |
||||
elsif sep == '#' |
||||
oid = identifier.to_i |
||||
case prefix |
||||
when nil |
||||
if work_package = WorkPackage.visible |
||||
.includes(:status) |
||||
.references(:statuses) |
||||
.find_by(id: oid) |
||||
link = link_to("##{oid}", |
||||
work_package_path_or_url(id: oid, only_path: only_path), |
||||
class: work_package_css_classes(work_package), |
||||
title: "#{truncate(work_package.subject, length: 100)} (#{work_package.status.try(:name)})") |
||||
end |
||||
when 'version' |
||||
if version = Version.visible.find_by(id: oid) |
||||
link = link_to h(version.name), { only_path: only_path, controller: '/versions', action: 'show', id: version }, |
||||
class: 'version' |
||||
end |
||||
when 'message' |
||||
if message = Message.visible.includes(:parent).find_by(id: oid) |
||||
link = link_to_message(message, { only_path: only_path }, class: 'message') |
||||
end |
||||
when 'project' |
||||
if p = Project.visible.find_by(id: oid) |
||||
link = link_to_project(p, { only_path: only_path }, class: 'project') |
||||
end |
||||
when 'user' |
||||
if user = User.in_visible_project.find_by(id: oid) |
||||
link = link_to_user(user, class: 'user-mention') |
||||
end |
||||
when 'group' |
||||
if group = Group.find_by(id: oid) |
||||
link = content_tag(:span, group.name, class: 'user-mention') |
||||
end |
||||
end |
||||
elsif sep == '##' |
||||
oid = identifier.to_i |
||||
if work_package = WorkPackage.visible |
||||
.includes(:status) |
||||
.references(:statuses) |
||||
.find_by(id: oid) |
||||
link = work_package_quick_info(work_package, only_path: only_path) |
||||
end |
||||
elsif sep == '###' |
||||
oid = identifier.to_i |
||||
work_package = WorkPackage.visible |
||||
.includes(:status) |
||||
.references(:statuses) |
||||
.find_by(id: oid) |
||||
if work_package && obj && !options[:no_nesting] && !(attr == :description && obj.id == work_package.id) |
||||
link = work_package_quick_info_with_description(work_package, only_path: only_path) |
||||
end |
||||
elsif sep == ':' |
||||
# removes the double quotes if any |
||||
name = identifier.gsub(%r{\A"(.*)"\z}, '\\1') |
||||
case prefix |
||||
when 'version' |
||||
if project && version = project.versions.visible.find_by(name: name) |
||||
link = link_to h(version.name), { only_path: only_path, controller: '/versions', action: 'show', id: version }, |
||||
class: 'version' |
||||
end |
||||
when 'commit' |
||||
if project && project.repository && (changeset = Changeset.visible.where(['repository_id = ? AND scmid LIKE ?', project.repository.id, "#{name}%"]).first) |
||||
link = link_to h("#{project_prefix}#{name}"), { only_path: only_path, controller: '/repositories', action: 'revision', project_id: project, rev: changeset.identifier }, |
||||
class: 'changeset', |
||||
title: truncate_single_line(changeset.comments, length: 100) |
||||
end |
||||
when 'source', 'export' |
||||
if project && project.repository && User.current.allowed_to?(:browse_repository, project) |
||||
name =~ %r{\A[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?\z} |
||||
path = $1 |
||||
rev = $3 |
||||
anchor = $5 |
||||
link = link_to h("#{project_prefix}#{prefix}:#{name}"), { controller: '/repositories', action: 'entry', project_id: project, |
||||
repo_path: path.to_s, |
||||
rev: rev, |
||||
anchor: anchor, |
||||
format: (prefix == 'export' ? 'raw' : nil) }, |
||||
class: (prefix == 'export' ? 'source download' : 'source') |
||||
end |
||||
when 'attachment' |
||||
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) |
||||
if attachments && attachment = attachments.detect { |a| a.filename == name } |
||||
link = link_to h(attachment.filename), { only_path: only_path, controller: '/attachments', action: 'download', id: attachment }, |
||||
class: 'attachment' |
||||
end |
||||
when 'project' |
||||
p = Project |
||||
.visible |
||||
.where(['projects.identifier = :s OR LOWER(projects.name) = :s', |
||||
{ s: name.downcase }]) |
||||
.first |
||||
if p |
||||
link = link_to_project(p, { only_path: only_path }, class: 'project') |
||||
end |
||||
when 'user' |
||||
if user = User.in_visible_project.find_by(login: name) |
||||
link = link_to_user(user, class: 'user-mention') |
||||
end |
||||
end |
||||
end |
||||
end |
||||
leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}") |
||||
end |
||||
end |
||||
|
||||
HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE) |
||||
|
||||
# Headings and TOC |
||||
# Adds ids and links to headings unless options[:headings] is set to false |
||||
def parse_headings(text, _project, _obj, _attr, _only_path, options) |
||||
return if options[:headings] == false |
||||
|
||||
text.gsub!(HEADING_RE) do |
||||
level = $1.to_i |
||||
attrs = $2 |
||||
content = $3 |
||||
item = strip_tags(content).strip |
||||
tocitem = strip_tags(content.gsub(/<br \/>/, ' ')) |
||||
anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') |
||||
@parsed_headings << [level, anchor, tocitem] |
||||
url = full_url(anchor, options[:request]) |
||||
"<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"#{url}\" class=\"wiki-anchor\">¶</a></h#{level}>" |
||||
end |
||||
end |
||||
|
||||
TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) |
||||
|
||||
# Renders the TOC with given headings |
||||
def replace_toc(text, headings, options) |
||||
text.gsub!(TOC_RE) do |
||||
if headings.empty? |
||||
'' |
||||
else |
||||
div_class = 'toc' |
||||
div_class << ' right' if $1 == '>' |
||||
div_class << ' left' if $1 == '<' |
||||
out = "<fieldset class='form--fieldset -collapsible'>" |
||||
out << "<legend class='form--fieldset-legend' title='#{l(:description_toc_toggle)}'>" |
||||
out << "<a href='#'>#{l(:label_table_of_contents)}</a></legend><div>" |
||||
out << "<ul class=\"#{div_class}\"><li>" |
||||
root = headings.map(&:first).min |
||||
current = root |
||||
started = false |
||||
headings.each do |level, anchor, item| |
||||
if level > current |
||||
out << '<ul><li>' * (level - current) |
||||
elsif level < current |
||||
out << "</li></ul>\n" * (current - level) + '</li><li>' |
||||
elsif started |
||||
out << '</li><li>' |
||||
end |
||||
url = full_url(anchor, options[:request]) |
||||
out << "<a href=\"#{url}\">#{item}</a>" |
||||
current = level |
||||
started = true |
||||
end |
||||
out << '</li></ul>' * (current - root) |
||||
out << '</li></ul>' |
||||
out << '</div></fieldset>' |
||||
end |
||||
end |
||||
end |
||||
|
||||
# |
||||
# displays the current url plus an optional anchor |
||||
# |
||||
def full_url(anchor_name = '', request) |
||||
return "##{anchor_name}" if request.nil? |
||||
url_for pagination_params_whitelist(request).merge(anchor: anchor_name, only_path: true) |
||||
rescue ActionController::UrlGenerationError |
||||
# In a context outside params, we don't know what the relative anchor url is |
||||
"##{anchor_name}" |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,72 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject::TextFormatting::Formatters |
||||
module Textile |
||||
class Helper |
||||
|
||||
attr_reader :view_context |
||||
|
||||
def initialize(view_context) |
||||
@view_context = view_context |
||||
end |
||||
|
||||
def text_formatting_js_includes |
||||
view_context.javascript_include_tag 'jstoolbar/textile.js' |
||||
end |
||||
|
||||
def text_formatting_has_preview? |
||||
true |
||||
end |
||||
|
||||
def wikitoolbar_for(field_id) |
||||
help_button = view_context.content_tag( |
||||
:button, |
||||
'', |
||||
type: 'button', |
||||
class: 'jstb_help formatting-help-link-button', |
||||
:'aria-label' => ::I18n.t('js.inplace.link_formatting_help'), |
||||
title: ::I18n.t('js.inplace.link_formatting_help') |
||||
) |
||||
|
||||
|
||||
view_context.content_for(:additional_js_dom_ready) do |
||||
%( |
||||
var wikiToolbar = new jsToolBar(document.getElementById('#{field_id}')); |
||||
|
||||
wikiToolbar.setHelpLink(jQuery('#{view_context.escape_javascript help_button}')[0]); |
||||
wikiToolbar.draw(); |
||||
).html_safe |
||||
end |
||||
|
||||
''.html_safe |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,143 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'redcloth3' |
||||
|
||||
module OpenProject::TextFormatting::Formatters |
||||
module Textile |
||||
class RedclothWrapper < RedCloth3 |
||||
include ERB::Util |
||||
include ActionView::Helpers::TagHelper |
||||
|
||||
# auto_link rule after textile rules so that it doesn't break !image_url! tags |
||||
RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto] |
||||
|
||||
def initialize(*args) |
||||
super |
||||
|
||||
self.hard_breaks = true |
||||
self.no_span_caps = true |
||||
self.filter_styles = true |
||||
end |
||||
|
||||
def to_html(*_rules) |
||||
@toc = [] |
||||
super(*RULES).to_s |
||||
end |
||||
|
||||
private |
||||
|
||||
# Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet. |
||||
# <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a> |
||||
def hard_break(text) |
||||
text.gsub!(/(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, '\\1<br />') if hard_breaks |
||||
end |
||||
|
||||
# Patch to add code highlighting support to RedCloth |
||||
def smooth_offtags(text) |
||||
unless @pre_list.empty? |
||||
## replace <pre> content |
||||
text.gsub!(/<redpre#(\d+)>/) do |
||||
content = @pre_list[$1.to_i] |
||||
if content.match(/<code\s+class="(\w+)">\s?(.+)/m) |
||||
content = "<code class=\"#{$1} CodeRay\">" + |
||||
Redmine::SyntaxHighlighting.highlight_by_language($2, $1) |
||||
end |
||||
content |
||||
end |
||||
end |
||||
end |
||||
|
||||
|
||||
def auto_link_regexp |
||||
@auto_link_regexp ||= begin |
||||
%r{ |
||||
( # leading text |
||||
<\w+.*?>| # leading HTML tag, or |
||||
[^=<>!:'"/]| # leading punctuation, or |
||||
\{\{\w+\(| # inside a macro? |
||||
^ # beginning of line |
||||
) |
||||
( |
||||
(?:https?://)| # protocol spec, or |
||||
(?:s?ftps?://)| |
||||
(?:www\.) # www.* |
||||
) |
||||
( |
||||
(\S+?) # url |
||||
(\/)? # slash |
||||
) |
||||
((?:>)?|[^\w\=\/;\(\)]*?) # post |
||||
(?=<|\s|$) |
||||
}x |
||||
end |
||||
end |
||||
|
||||
# Turns all urls into clickable links (code from Rails). |
||||
def inline_auto_link(text) |
||||
text.gsub!(auto_link_regexp) do |
||||
all = $& |
||||
leading = $1 |
||||
proto = $2 |
||||
url = $3 |
||||
post = $6 |
||||
if url.nil? || leading =~ /<a\s/i || leading =~ /![<>=]?/ || leading =~ /\{\{\w+\(/ |
||||
# don't replace URLs that are already linked |
||||
# and URLs prefixed with ! !> !< != (textile images) |
||||
all |
||||
else |
||||
# Idea below : an URL with unbalanced parethesis and |
||||
# ending by ')' is put into external parenthesis |
||||
if url[-1] == ?) and ((url.count('(') - url.count(')')) < 0) |
||||
url = url[0..-2] # discard closing parenth from url |
||||
post = ')' + post # add closing parenth to post |
||||
end |
||||
tag = content_tag('a', |
||||
proto + url, |
||||
href: "#{proto == 'www.' ? 'http://www.' : proto}#{url}", |
||||
class: 'external icon-context icon-copy') |
||||
%(#{leading}#{tag}#{post}) |
||||
end |
||||
end |
||||
end |
||||
|
||||
# Turns all email addresses into clickable links (code from Rails). |
||||
def inline_auto_mailto(text) |
||||
text.gsub!(/((?<!user:")\b[\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do |
||||
mail = $1 |
||||
if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/) |
||||
mail |
||||
else |
||||
content_tag('a', mail, href: "mailto:#{mail}", class: 'email') |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,81 +0,0 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject |
||||
module WikiFormatting |
||||
module Macros |
||||
module Default |
||||
Redmine::WikiFormatting::Macros.register do |
||||
# Builtin macros |
||||
|
||||
desc 'Sample macro.' |
||||
|
||||
macro :hello_world do |obj, args| |
||||
"Hello world! Object: #{obj.class.name}, " + (args.empty? ? 'Called with no argument.' : "Arguments: #{args.join(', ')}") |
||||
end |
||||
end |
||||
|
||||
Redmine::WikiFormatting::Macros.register do |
||||
desc 'Displays a list of all available macros, including description if available.' |
||||
macro :macro_list do |_obj, _args| |
||||
out = '' |
||||
available_macros = Redmine::WikiFormatting::Macros.available_macros |
||||
|
||||
available_macros.keys.map(&:to_s).sort.each do |macro| |
||||
out << content_tag('dt', content_tag('code', macro)) |
||||
out << content_tag('dd', format_text(available_macros[macro.to_sym])) |
||||
end |
||||
content_tag('dl', out.html_safe) |
||||
end |
||||
end |
||||
|
||||
Redmine::WikiFormatting::Macros.register do |
||||
desc "Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:\n\n" + |
||||
" !{{child_pages}} -- can be used from a wiki page only\n" + |
||||
" !{{child_pages(Foo)}} -- lists all children of page Foo\n" + |
||||
' !{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo' |
||||
|
||||
macro :child_pages do |obj, args| |
||||
args, options = extract_macro_options(args, :parent) |
||||
page = nil |
||||
if args.size > 0 |
||||
page = Wiki.find_page(args.first.to_s, project: @project) |
||||
elsif obj.is_a?(WikiContent) |
||||
page = obj.page |
||||
else |
||||
raise 'With no argument, this macro can be called from wiki pages only.' |
||||
end |
||||
raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project) |
||||
pages = ([page] + page.descendants).group_by(&:parent_id) |
||||
render_page_hierarchy(pages, options[:parent] ? page.parent_id : page.id) |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,66 +0,0 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject |
||||
module WikiFormatting |
||||
module Macros |
||||
module WorkPackageButton |
||||
Redmine::WikiFormatting::Macros.register do |
||||
desc 'Inserts a link or button to the create form of a work package' |
||||
macro :create_work_package_link do |_obj, args, options| |
||||
project = @project || options[:project] |
||||
if project.nil? |
||||
raise I18n.t('macros.create_work_package_link.errors.no_project_context') |
||||
end |
||||
|
||||
type_name = args.shift |
||||
class_name = args.shift == 'button' ? 'button' : nil |
||||
if type_name.present? |
||||
type = project.types.find_by(name: type_name) |
||||
if type.nil? |
||||
raise I18n.t( |
||||
'macros.create_work_package_link.errors.invalid_type', |
||||
type: type_name, |
||||
project: project.name |
||||
) |
||||
end |
||||
|
||||
link_to I18n.t('macros.create_work_package_link.link_name_type', type_name: type_name), |
||||
new_project_work_packages_path(project_id: project.identifier, type: type.id), |
||||
class: class_name |
||||
else |
||||
link_to I18n.t('macros.create_work_package_link.link_name'), |
||||
new_project_work_packages_path(project_id: project.identifier), |
||||
class: class_name |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
File diff suppressed because it is too large
Load Diff
@ -1,100 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module Redmine |
||||
module WikiFormatting |
||||
module Macros |
||||
module Definitions |
||||
def exec_macro(name, obj, args, options = {}) |
||||
method_name = "macro_#{name}" |
||||
if respond_to?(method_name) |
||||
if method(method_name).arity == 2 |
||||
send(method_name, obj, args) |
||||
else |
||||
send(method_name, obj, args, options) |
||||
end |
||||
end |
||||
end |
||||
|
||||
def extract_macro_options(args, *keys) |
||||
options = {} |
||||
while args.last.to_s.strip =~ %r{^(.+)\=(.+)$} && keys.include?($1.downcase.to_sym) |
||||
options[$1.downcase.to_sym] = $2 |
||||
args.pop |
||||
end |
||||
[args, options] |
||||
end |
||||
end |
||||
|
||||
@@available_macros = {} |
||||
|
||||
class << self |
||||
# Called with a block to define additional macros. |
||||
# Macro blocks accept 2 arguments: |
||||
# * obj: the object that is rendered |
||||
# * args: macro arguments |
||||
# |
||||
# Plugins can use this method to define new macros: |
||||
# |
||||
# Redmine::WikiFormatting::Macros.register do |
||||
# desc "This is my macro" |
||||
# macro :my_macro do |obj, args| |
||||
# "My macro output" |
||||
# end |
||||
# end |
||||
def register(&block) |
||||
class_eval(&block) if block_given? |
||||
end |
||||
|
||||
def available_macros |
||||
@@available_macros |
||||
end |
||||
|
||||
private |
||||
|
||||
# Defines a new macro with the given name and block. |
||||
def macro(name, &block) |
||||
name = name.to_sym if name.is_a?(String) |
||||
@@available_macros[name] = @@desc || '' |
||||
@@desc = nil |
||||
raise 'Can not create a macro without a block!' unless block_given? |
||||
Definitions.send :define_method, "macro_#{name}".downcase, &block |
||||
end |
||||
|
||||
# Sets description for the next macro to be defined |
||||
def desc(txt) |
||||
@@desc = txt |
||||
end |
||||
|
||||
include OpenProject::WikiFormatting::Macros::Default |
||||
include OpenProject::WikiFormatting::Macros::WorkPackageButton |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,46 +0,0 @@ |
||||
|
||||
# Textile to Markdown converter |
||||
# Based on redmine_convert_textile_to_markown |
||||
# https://github.com/Ecodev/redmine_convert_textile_to_markown |
||||
# |
||||
# Original license: |
||||
# Copyright (c) 2016 |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to deal |
||||
# in the Software without restriction, including without limitation the rights |
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
# copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
# SOFTWARE. |
||||
|
||||
namespace :markdown do |
||||
task :convert_from_textile => :environment do |
||||
|
||||
warning = <<~EOS |
||||
**WARNING** |
||||
THIS IS NOT REVERSIBLE. |
||||
Ensure you have backed up your installation before running this task. |
||||
|
||||
This rake task will modify EVERY formattable textile field in your database. |
||||
It uses pandoc to convert each textile field to GFM-Markdown. |
||||
EOS |
||||
|
||||
printf "#{warning}\nPress 'y' to continue: " |
||||
prompt = STDIN.gets.chomp |
||||
exit(1) unless prompt == 'y' |
||||
|
||||
converter = OpenProject::TextFormatting::Formatters::Markdown::TextileConverter.new |
||||
converter.run! |
||||
end |
||||
end |
@ -0,0 +1,82 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe 'sticky messages', type: :feature do |
||||
let(:board) { FactoryBot.create(:board) } |
||||
|
||||
let!(:message1) do |
||||
FactoryBot.create :message, board: board, created_on: Time.now - 1.minute do |message| |
||||
Message.where(id: message.id).update_all(updated_on: Time.now - 1.minute) |
||||
end |
||||
end |
||||
let!(:message2) do |
||||
FactoryBot.create :message, board: board, created_on: Time.now - 2.minute do |message| |
||||
Message.where(id: message.id).update_all(updated_on: Time.now - 2.minute) |
||||
end |
||||
end |
||||
let!(:message3) do |
||||
FactoryBot.create :message, board: board, created_on: Time.now - 3.minute do |message| |
||||
Message.where(id: message.id).update_all(updated_on: Time.now - 3.minute) |
||||
end |
||||
end |
||||
|
||||
let(:user) do |
||||
FactoryBot.create :user, |
||||
member_in_project: board.project, |
||||
member_through_role: role |
||||
end |
||||
let(:role) { FactoryBot.create(:role, permissions: [:edit_messages]) } |
||||
|
||||
before do |
||||
login_as user |
||||
visit project_boards_path(board.project) |
||||
end |
||||
|
||||
def expect_order_of_messages(*order) |
||||
order.each_with_index do |message, index| |
||||
expect(page).to have_selector("table tbody tr:nth-of-type(#{index + 1})", text: message.subject) |
||||
end |
||||
end |
||||
|
||||
scenario 'sticky messages are on top' do |
||||
expect_order_of_messages(message1, message2, message3) |
||||
|
||||
click_link(message2.subject) |
||||
|
||||
click_link('Edit') |
||||
|
||||
check('Sticky') |
||||
click_button('Save') |
||||
|
||||
visit project_boards_path(board.project) |
||||
|
||||
expect_order_of_messages(message2, message1, message3) |
||||
end |
||||
end |
@ -0,0 +1,103 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe 'wiki pages', type: :feature, js: true do |
||||
let(:project) do |
||||
FactoryBot.create(:project, enabled_module_names: [:news]) |
||||
end |
||||
let(:user) do |
||||
FactoryBot.create :user, |
||||
member_in_project: project, |
||||
member_through_role: role |
||||
end |
||||
let(:role) do |
||||
FactoryBot.create(:role, |
||||
permissions: %i[view_wiki_pages |
||||
edit_wiki_pages |
||||
view_wiki_edits |
||||
select_project_modules |
||||
edit_project]) |
||||
end |
||||
let(:content_first_version) do |
||||
'The new content, first version' |
||||
end |
||||
let(:content_second_version) do |
||||
'The new content, second version' |
||||
end |
||||
|
||||
before do |
||||
login_as user |
||||
end |
||||
|
||||
scenario 'adding, editing and history' do |
||||
visit settings_project_path(project, tab: 'modules') |
||||
|
||||
expect(page).to have_no_selector('.menu-sidebar .main-item-wrapper', text: 'Wiki') |
||||
|
||||
within '#content' do |
||||
check 'Wiki' |
||||
|
||||
click_button 'Save' |
||||
end |
||||
|
||||
expect(page).to have_selector('#menu-sidebar .main-item-wrapper', text: 'Wiki') |
||||
|
||||
# creating by accessing the page |
||||
visit project_wiki_path(project, 'new page') |
||||
|
||||
find('.ck-content').set(content_first_version) |
||||
|
||||
click_button 'Save' |
||||
|
||||
expect(page).to have_selector('.title-container', text: 'New page') |
||||
expect(page).to have_selector('.wiki-content', text: content_first_version) |
||||
|
||||
within '.toolbar-items' do |
||||
click_on "Edit" |
||||
end |
||||
|
||||
find('.ck-content').set(content_second_version) |
||||
|
||||
click_button 'Save' |
||||
expect(page).to have_selector('.wiki-content', text: content_second_version) |
||||
|
||||
within '.toolbar-items' do |
||||
click_on 'More' |
||||
click_on 'History' |
||||
end |
||||
|
||||
click_on 'View differences' |
||||
|
||||
within '.text-diff' do |
||||
expect(page).to have_selector('ins.diffmod', text: 'second') |
||||
expect(page).to have_selector('del.diffmod', text: 'first') |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,78 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe 'wiki child pages', type: :feature, js: true do |
||||
let(:project) do |
||||
FactoryBot.create(:project) |
||||
end |
||||
let(:user) do |
||||
FactoryBot.create :user, |
||||
member_in_project: project, |
||||
member_through_role: role |
||||
end |
||||
let(:role) do |
||||
FactoryBot.create(:role, |
||||
permissions: %i[view_wiki_pages edit_wiki_pages]) |
||||
end |
||||
let(:parent_page) do |
||||
FactoryBot.create(:wiki_page_with_content, |
||||
wiki: project.wiki) |
||||
end |
||||
let(:child_page_name) { 'The child page !@#{$%^&*()_},./<>?;\':' } |
||||
|
||||
before do |
||||
login_as user |
||||
end |
||||
|
||||
scenario 'adding a childpage' do |
||||
visit project_wiki_path(project, parent_page.title) |
||||
|
||||
click_on 'Wiki page' |
||||
|
||||
fill_in 'content_page_title', with: child_page_name |
||||
|
||||
find('.ck-content').set('The child page\'s content') |
||||
|
||||
click_button 'Save' |
||||
|
||||
# hierarchy displayed in the breadcrumb |
||||
expect(page).to have_selector('#breadcrumb .breadcrumb', |
||||
text: "#{parent_page.title}\n#{child_page_name}") |
||||
|
||||
# hierarchy displayed in the sidebar |
||||
expect(page).to have_selector('.pages-hierarchy', |
||||
text: "#{parent_page.title}\n#{child_page_name}") |
||||
|
||||
# on toc page |
||||
visit index_project_wiki_index_path(project) |
||||
|
||||
expect(page).to have_content(child_page_name) |
||||
end |
||||
end |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue