Added ProseMirror rich text editor.

This commit is contained in:
buddh4 2018-02-08 14:28:55 +01:00
parent 01b0b2c42d
commit 130c4aa6de
2757 changed files with 5464 additions and 971 deletions

View File

@ -51,6 +51,7 @@
"bower-asset/imagesloaded": "*",
"bower-asset/jquery-timeentry": "^2.0",
"bower-asset/caret.js": "0.2.2",
"npm-asset/humhub-prosemirror-richtext": "1.0.2",
"npm-asset/at.js": "^1.5.1",
"yiisoft/yii2-queue": "^2.0",
"yiisoft/yii2-redis": "^2.0",

753
composer.lock generated

File diff suppressed because it is too large Load Diff

1830
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -45,19 +45,6 @@ class AppAsset extends AssetBundle
*/
public $jsOptions = ['position' => \yii\web\View::POS_HEAD];
/**
* @inheritdoc
*/
public $js = [
'js/blueimp-gallery.min.js',
'js/jquery.highlight.min.js',
'js/desktop-notify-min.js',
'js/desktop-notify-config.js',
'js/jquery.nicescroll.min.js',
'resources/file/fileuploader.js',
'resources/user/userpicker.js',
];
/**
* @inheritdoc
*/
@ -78,6 +65,7 @@ class AppAsset extends AssetBundle
'humhub\assets\AtJsAsset',
'humhub\assets\AnimateCssAsset',
'humhub\assets\CoreApiAsset',
'humhub\modules\content\assets\ProseMirrorRichTextAsset',
'humhub\modules\user\assets\UserAsset',
'humhub\modules\live\assets\LiveAsset',
'humhub\modules\notification\assets\NotificationAsset',
@ -97,6 +85,19 @@ class AppAsset extends AssetBundle
'humhub\assets\SocketIoAsset',
];
/**
* @inheritdoc
*/
public $js = [
'js/blueimp-gallery.min.js',
'js/jquery.highlight.min.js',
'js/desktop-notify-min.js',
'js/desktop-notify-config.js',
'js/jquery.nicescroll.min.js',
'resources/file/fileuploader.js',
'resources/user/userpicker.js',
];
/**
* @inheritdoc
*/

View File

@ -69,7 +69,7 @@ class CoreApiAsset extends AssetBundle
'js/humhub/humhub.ui.navigation.js',
'js/humhub/humhub.ui.gallery.js',
'js/humhub/humhub.ui.picker.js',
'js/humhub/humhub.ui.richtext.js',
'js/humhub/humhub.oembed.js',
'js/humhub/humhub.ui.markdown.js',
'js/humhub/humhub.media.Jplayer.js',
// Note this should stay at last for other click event listeners beeing able to prevent pjax handling (e.g gallery)

View File

@ -0,0 +1,30 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\assets;
use yii\web\AssetBundle;
class ProsemirrorEditorAsset extends AssetBundle
{
public $publishOptions = [
'forceCopy' => false
];
/**
* @inheritdoc
*/
//public $sourcePath = '/codebase/humhub/humhub-prosemirror/dist/';
public $sourcePath = '@npm/humhub-prosemirror-richtext/dist/';
/**
* @inheritdoc
*/
public $js = ['humhub-editor.js'];
}

View File

@ -8,13 +8,17 @@
namespace humhub\components;
use humhub\libs\Helpers;
use humhub\modules\content\models\Content;
use Yii;
use yii\helpers\Html;
use humhub\modules\user\models\User;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\space\models\Space;
use humhub\modules\content\interfaces\ContentOwner;
use humhub\widgets\RichText;
use humhub\modules\content\widgets\richtext\RichText;
use yii\helpers\Json;
use yii\helpers\Url;
/**
* This class represents a social Activity triggered within the network.
@ -234,7 +238,7 @@ abstract class SocialActivity extends \yii\base\Object implements rendering\View
// Create absolute URL, for E-Mails
if (substr($url, 0, 4) !== 'http') {
$url = \yii\helpers\Url::to($url, true);
$url = Url::to($url, true);
}
return $url;
@ -263,7 +267,7 @@ abstract class SocialActivity extends \yii\base\Object implements rendering\View
*/
public function json()
{
return \yii\helpers\Json::encode($this->asArray());
return Json::encode($this->asArray());
}
/**
@ -299,23 +303,17 @@ abstract class SocialActivity extends \yii\base\Object implements rendering\View
* If no $content is provided the contentInfo of $source is returned.
*
* @param Content $content
* @return string
* @return string|null
*/
public function getContentInfo(ContentOwner $content = null, $withContentName = true)
{
if (!$this->hasContent() && !$content) {
return;
} else if (!$content) {
$content = $this->source;
$truncatedDescription = $this->getContentPreview($content, 60);
if(empty($truncatedDescription)) {
return null;
}
$truncatedDescription = RichText::widget([
'text' => $content->getContentDescription(),
'minimal' => true,
'maxLength' => 60
]);
$trimmed = \humhub\libs\Helpers::trimText($truncatedDescription, 60);
$trimmed = Helpers::trimText($truncatedDescription, 60);
return ($withContentName) ? Html::encode($content->getContentName()). ' "' . $trimmed . '"' : $trimmed;
@ -326,12 +324,12 @@ abstract class SocialActivity extends \yii\base\Object implements rendering\View
* notification source.
*
* @param ContentOwner $content
* @return type
* @return string|null
*/
public function getContentName(ContentOwner $content = null)
{
if (!$this->hasContent() && !$content) {
return;
return null;
} else if (!$content) {
$content = $this->source;
}
@ -346,21 +344,17 @@ abstract class SocialActivity extends \yii\base\Object implements rendering\View
* If no $content is provided the contentPreview of $source is returned.
*
* @param Content $content
* @return string
* @return string|null
*/
public function getContentPreview(ContentOwner $content = null, $maxLength = 25)
{
if (!$this->hasContent() && !$content) {
return;
return null;
} else if (!$content) {
$content = $this->source;
}
return RichText::widget([
'text' => $content->getContentDescription(),
'minimal' => true,
'maxLength' => $maxLength
]);
return RichText::preview($content->getContentDescription(), $maxLength);
}
/**

View File

@ -12,7 +12,7 @@ Yii::setAlias('@config', '@app/config');
$config = [
'name' => 'HumHub',
'version' => '1.2.3',
'version' => '1.3.0-beta.1',
'basePath' => dirname(__DIR__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR,
'bootstrap' => ['log', 'humhub\components\bootstrap\ModuleAutoLoader', 'queue'],
'sourceLanguage' => 'en',
@ -231,6 +231,9 @@ $config = [
'tour' => [
'acceptableNames' => ['interface', 'administration', 'profile', 'spaces']
],
'richText' => [
'class' => 'humhub\modules\content\widgets\richtext\ProsemirrorRichText',
],
'enablePjax' => true,
]
];

View File

@ -0,0 +1,48 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\controllers;
use humhub\components\Controller;
use humhub\models\UrlOembed;
use Yii;
/**
* @since 1.3
*/
class OembedController extends Controller
{
/**
* @inheritdoc
*/
public function getAccessRules()
{
return [['login']];
}
/**
* Fetches oembed content for the posted urls.
*
* @return \yii\web\Response
*/
public function actionIndex()
{
$urls = Yii::$app->request->post('urls', []);
$result = [];
foreach ($urls as $url) {
$oembed = UrlOembed::GetOEmbed($url);
if($oembed) {
$result[$url] = $oembed;
}
}
return $this->asJson($result);
}
}

View File

@ -19,3 +19,18 @@ HumHub Change Log - v1.3-dev Branch
- Enh: Moved SyncUsers (LDAP) and session table cleanup handling into ActiveJob
- Enh: Added Push live module driver using Redis and Node.JS
- Enh: Added tooltip option to space Image widget.
- Enh: Added `humhub.client.json` javascript util for directly receiving json instead of a Response object.
- Enh: Added `humhub.file.Upload.run()` for triggering the upload of the Upload widget.
- Chg: Moved `humhub\widgets\RichText` to `humhub\modules\content\widgets\richtext\RichText`
- Chg: Moved `humhub\widgets\RichTextField` to `humhub\modules\content\widgets\richtext\RichTextField`
- Enh: Added rich text abstraction by means of configuration parameter `richText`
- Enh: Added `humhub\modules\content\widgets\richtext\ProsemirrorRichText` as default rich text.
- Enh: Added `humhub.oembed` js module for loading oembed content
- Enh: Added `RichText::preview()` helper for minimal rich text output
- Enh: Added `RichText::output()` helper for rendering the richtext
- Enh: Added `RichText::postProcess()` for post-processing rich text content (mentionings/oembed etc.)
- Enh: Added `content` module setting `Module::$maxOembeds` for setting the maximim amount of oembeds in a richtext.
- Chg: Deprecate `humhub\modules\user\models\Mentioning::parse()` and in favor of `humhub\modules\content\widgets\richtext\RichText::postProcess()`
- Enh: Added `humhub.user.getLocale()` javascript helper for checking the user locale on client side
- Enh: Added `humhub\widgets\InputWidget::getValue()` for determining the field value
- Enh: Added `humhub.client.json` for directly receiving the json result instead of a response wrapper object

View File

@ -32,4 +32,5 @@ Bundled Software / Libaries
* Bootstrap Markdown - http://www.codingdrama.com/bootstrap-markdown/
* JShrink - https://github.com/tedious/JShrink
* CSSCompressor - http://www.yiiframework.com/extension/minifyclientscript/
* Sample avatars designed by http://www.freepik.com
* Sample avatars designed by http://www.freepik.com
* twemoji - https://github.com/twitter/twemoji

View File

@ -3,3 +3,28 @@
## Space & Profile Layouts
The sidebars are now moved into own files `_sidebar.php` view files.
## New Richtext
#### Added wrapper div `comment-create-input-group` to
- `/protected/humhub/modules/comment/views/comment/edit.php`
- `/protected/humhub/modules/comment/widgets/views/form.php`
#### Added wrapper div `post-richtext-input-group` to
- `/protected/humhub/modules/post/views/post/edit.php`
#### Minor changes in the following less files:
- `static/less/comment.less`
- `static/less/file.less`
- `static/less/mentioning.less`
> Note: Those changes will be included by rebuilding your theme, as long as you did not exclude those files from your theme build.
#### Added `static/less/richtext.less` file.
This file contains the style of the new wysiwyg rich text editor and will be included by a theme rebuild.
As with other less files this file can be excluded from your theme build by less variable `@prev-richtext`.

View File

@ -9,6 +9,9 @@
namespace humhub\models;
use Yii;
use yii\base\InvalidParamException;
use yii\helpers\Html;
use yii\helpers\Json;
/**
* This is the model class for table "url_oembed".
@ -114,11 +117,11 @@ class UrlOembed extends \yii\db\ActiveRecord
$jsonOut = UrlOembed::fetchUrl($urlOembed->getProviderUrl());
if ($jsonOut != "" && $jsonOut != "Unauthorized") {
try {
$data = \yii\helpers\Json::decode($jsonOut);
$data = Json::decode($jsonOut);
if (isset($data['html']) && isset($data['type']) && ($data['type'] === "video" || $data['type'] === 'rich' || $data['type'] === 'photo')) {
$html = "<div data-guid='".uniqid('oembed-')."' data-richtext-feature class='oembed_snippet' data-url='" . \yii\helpers\Html::encode($url) . "'>" . $data['html'] . "</div>";
$html = "<div data-guid='".uniqid('oembed-')."' data-richtext-feature class='oembed_snippet' data-url='" . Html::encode($url) . "'>" . $data['html'] . "</div>";
}
} catch (\yii\base\InvalidParamException $ex) {
} catch (InvalidParamException $ex) {
Yii::warning($ex->getMessage());
}
}
@ -209,7 +212,7 @@ class UrlOembed extends \yii\db\ActiveRecord
{
$providers = Yii::$app->settings->get('oembedProviders');
if ($providers != "") {
return \yii\helpers\Json::decode($providers);
return Json::decode($providers);
}
return [];
@ -222,7 +225,7 @@ class UrlOembed extends \yii\db\ActiveRecord
*/
public static function setProviders($providers)
{
Yii::$app->settings->set('oembedProviders', \yii\helpers\Json::encode($providers));
Yii::$app->settings->set('oembedProviders', Json::encode($providers));
}
}

View File

@ -7,6 +7,7 @@
*/
/* @var $this humhub\components\View */
/* @var $streamUrl string */
\humhub\modules\activity\assets\ActivityAsset::register($this);

View File

@ -1,11 +1,15 @@
<?php
use humhub\modules\content\widgets\richtext\RichText;
use yii\helpers\Html;
/* @var $originator \humhub\modules\user\models\User */
/* @var $source \humhub\modules\comment\models\Comment */
echo Yii::t('CommentModule.views_activities_CommentCreated', "%displayName% wrote a new comment ", [
'%displayName%' => '<strong>' . Html::encode($originator->displayName) . '</strong>'
]);
?>
<br>
<em>"<?= \humhub\widgets\RichText::widget(['text' => $source->message, 'minimal' => true]); ?>"</em>
<em>"<?= RichText::preview($source->message); ?>"</em>

View File

@ -1,11 +1,15 @@
<?php
use humhub\modules\content\widgets\richtext\RichText;
use yii\helpers\Html;
/* @var $originator \humhub\modules\user\models\User */
/* @var $source \humhub\modules\comment\models\Comment */
echo strip_tags(Yii::t('CommentModule.views_activities_CommentCreated', "%displayName% wrote a new comment ", [
'%displayName%' => Html::encode($originator->displayName)
]));
?>
"<?= strip_tags(\humhub\widgets\RichText::widget(['text' => $source->message, 'minimal' => true])); ?>"
"<?= strip_tags(RichText::preview($source->message)); ?>"

View File

@ -1,10 +1,14 @@
<?php
use yii\helpers\Html;
use humhub\modules\content\widgets\richtext\RichText;
/* @var $originator \humhub\modules\user\models\User */
/* @var $source \humhub\modules\comment\models\Comment */
echo Yii::t('CommentModule.views_activities_CommentCreated', "%displayName% wrote a new comment ", [
'%displayName%' => '<strong>' . Html::encode($originator->displayName) . '</strong>'
]);
echo ' "' . \humhub\widgets\RichText::widget(['text' => $source->message, 'minimal' => true, 'maxLength' => 100]) . '"';
echo ' "' . RichText::preview($source->message, 100) . '"';
?>

View File

@ -8,6 +8,9 @@
namespace humhub\modules\comment\models;
use humhub\modules\content\widgets\richtext\RichText;
use humhub\modules\search\interfaces\Searchable;
use humhub\modules\space\models\Space;
use humhub\modules\user\models\User;
use Yii;
use humhub\modules\post\models\Post;
@ -112,14 +115,15 @@ class Comment extends ContentAddonActiveRecord implements ContentOwner
*/
public function afterSave($insert, $changedAttributes)
{
// flush the cache
$this->flushCache();
// Creating activity
NewComment::instance()->about($this)->save();
// Handle mentioned users
// Execute before NewCommentNotification to avoid double notification when mentioned.
$mentionedUsers = \humhub\modules\user\models\Mentioning::parse($this, $this->message);
$processResult = RichText::postProcess($this->message, $this);
$mentionedUsers = (isset($processResult['mentioning'])) ? $processResult['mentioning'] : [];
if ($insert) {
$followers = $this->getCommentedRecord()->getFollowers(null, true);
@ -176,7 +180,7 @@ class Comment extends ContentAddonActiveRecord implements ContentOwner
*/
protected function updateContentSearch()
{
if ($this->getCommentedRecord() instanceof \humhub\modules\search\interfaces\Searchable) {
if ($this->getCommentedRecord() instanceof Searchable) {
Yii::$app->search->update($this->getCommentedRecord());
}
}
@ -272,7 +276,7 @@ class Comment extends ContentAddonActiveRecord implements ContentOwner
return true;
}
if ($this->content->container instanceof \humhub\modules\space\models\Space && $this->content->container->isAdmin($userId)) {
if ($this->content->container instanceof Space && $this->content->container->isAdmin($userId)) {
return true;
}

View File

@ -15,10 +15,11 @@ humhub.module('comment', function (module, require, $) {
Form.prototype.submit = function (evt) {
var that = this;
client.submit(evt, {dataType: 'html'}).then(function (response) {
var richText = that.getRichtext();
that.addComment(response.html);
that.getInput().val('').trigger('autosize.resize');
that.getRichtext().$.addClass('atwho-placeholder').focus();
that.getRichtext().$.trigger('clear');
richText.$.trigger('clear');
//richText.focus();
that.getUpload().reset();
}).catch(function (err) {
module.log.error(err, true);
@ -26,7 +27,7 @@ humhub.module('comment', function (module, require, $) {
};
Form.prototype.getRichtext = function () {
return Widget.instance(this.$.find('div.humhub-ui-richtext'));
return Widget.closest(this.$.find('div.humhub-ui-richtext'));
};
Form.prototype.addComment = function (html) {

View File

@ -1,58 +1,54 @@
<?php
use humhub\compat\CActiveForm;
use humhub\modules\file\widgets\FilePreview;
use humhub\modules\file\widgets\UploadButton;
use humhub\widgets\Button;
use yii\bootstrap\ActiveForm;
use yii\helpers\Html;
use yii\helpers\Url;
use humhub\modules\content\widgets\richtext\RichTextField;
/* @var $contentModel string */
/* @var $contentId integer */
/* @var $comment \humhub\modules\comment\models\Comment */
$submitUrl = Url::to(['/comment/comment/edit', 'id' => $comment->id, 'contentModel' => $comment->object_model, 'contentId' => $comment->object_id]);
?>
<div class="content_edit input-container" id="comment_edit_<?= $comment->id; ?>">
<?php $form = CActiveForm::begin(); ?>
<?= Html::hiddenInput('contentModel', $contentModel); ?>
<?= Html::hiddenInput('contentId', $contentId); ?>
<?php $form = ActiveForm::begin(); ?>
<?= Html::hiddenInput('contentModel', $contentModel); ?>
<?= Html::hiddenInput('contentId', $contentId); ?>
<?=
humhub\widgets\RichtextField::widget([
'id' => 'comment_input_'.$comment->id,
'placeholder' => Yii::t('CommentModule.views_edit', 'Edit your comment...'),
'model' => $comment,
'attribute' => 'message'
]);
?>
<div class="comment-create-input-group">
<?= $form->field($comment, 'message')->widget(RichTextField::class, [
'id' => 'comment_input_'.$comment->id,
'placeholder' => Yii::t('CommentModule.views_edit', 'Edit your comment...')
]) ?>
<div class="comment-buttons">
<div class="comment-buttons">
<?=
\humhub\modules\file\widgets\UploadButton::widget([
'id' => 'comment_upload_' . $comment->id,
<?= UploadButton::widget([
'id' => 'comment_upload_' . $comment->id,
'model' => $comment,
'dropZone' => '#comment_'.$comment->id,
'preview' => '#comment_upload_preview_'.$comment->id,
'progress' => '#comment_upload_progress_'.$comment->id,
'max' => Yii::$app->getModule('content')->maxAttachedFiles
]); ?>
<?= Button::defaultType(Yii::t('base', 'Save'))->cssClass('btn-comment-submit')->action('editSubmit', $submitUrl)->submit()->sm() ?>
</div>
</div>
<div id="comment_upload_progress_<?= $comment->id ?>" style="display:none; margin:10px 0;"></div>
<?= FilePreview::widget([
'id' => 'comment_upload_preview_'.$comment->id,
'options' => ['style' => 'margin-top:10px'],
'model' => $comment,
'dropZone' => '#comment_'.$comment->id,
'preview' => '#comment_upload_preview_'.$comment->id,
'progress' => '#comment_upload_progress_'.$comment->id,
'max' => Yii::$app->getModule('content')->maxAttachedFiles
]);
?>
<a href="#" class="btn btn-default btn-sm btn-comment-submit"
data-action-click="editSubmit"
data-action-url="<?= Url::to(['/comment/comment/edit', 'id' => $comment->id, 'contentModel' => $comment->object_model, 'contentId' => $comment->object_id]) ?>"
data-action-submit
data-ui-loader>
<?= Yii::t('CommentModule.views_edit', 'Save') ?>
</a>
</div>
<div id="comment_upload_progress_<?= $comment->id ?>" style="display:none; margin:10px 0;"></div>
<?=
\humhub\modules\file\widgets\FilePreview::widget([
'id' => 'comment_upload_preview_'.$comment->id,
'options' => ['style' => 'margin-top:10px'],
'model' => $comment,
'edit' => true
]);
?>
<?php CActiveForm::end(); ?>
'edit' => true
]); ?>
<?php ActiveForm::end(); ?>
</div>

View File

@ -1,7 +1,17 @@
<?php
use humhub\widgets\Button;
use yii\helpers\Html;
use yii\helpers\Url;
use humhub\modules\content\widgets\richtext\RichTextField;
use humhub\modules\file\widgets\UploadButton;
use humhub\modules\file\widgets\FilePreview;
/* @var $modelName string */
/* @var $modelId integer */
$submitUrl = Url::to(['/comment/comment/post']);
?>
<div id="comment_create_form_<?= $id; ?>" class="comment_create" data-ui-widget="comment.Form">
@ -10,43 +20,36 @@ use yii\helpers\Url;
<?= Html::hiddenInput('contentModel', $modelName); ?>
<?= Html::hiddenInput('contentId', $modelId); ?>
<?=
humhub\widgets\RichtextField::widget([
'id' => 'newCommentForm_' . $id,
'placeholder' => Yii::t('CommentModule.widgets_views_form', 'Write a new comment...'),
'name' => 'message'
]);
?>
<div class="comment-create-input-group">
<?= RichTextField::widget([
'id' => 'newCommentForm_' . $id,
'placeholder' => Yii::t('CommentModule.widgets_views_form', 'Write a new comment...'),
'name' => 'message'
]); ?>
<div class="comment-buttons">
<?=
humhub\modules\file\widgets\UploadButton::widget([
'id' => 'comment_create_upload_' . $id,
'progress' => '#comment_create_upload_progress_' . $id,
'preview' => '#comment_create_upload_preview_' . $id,
'dropZone' => '#comment_create_form_'.$id,
'max' => Yii::$app->getModule('content')->maxAttachedFiles
]);
?>
<div class="comment-buttons">
<?= UploadButton::widget([
'id' => 'comment_create_upload_' . $id,
'progress' => '#comment_create_upload_progress_' . $id,
'preview' => '#comment_create_upload_preview_' . $id,
'dropZone' => '#comment_create_form_'.$id,
'max' => Yii::$app->getModule('content')->maxAttachedFiles
]); ?>
<a href="#" class="btn btn-sm btn-default btn-comment-submit pull-left"
data-action-click="submit"
data-action-url="<?= Url::to(['/comment/comment/post']) ?>"
data-ui-loader>
<?= Yii::t('CommentModule.widgets_views_form', 'Send') ?>
</a>
<?= Button::defaultType(Yii::t('CommentModule.widgets_views_form', 'Send'))
->cssClass('btn-comment-submit')
->action('submit', $submitUrl)->submit()->sm() ?>
</div>
</div>
<?= Html::endForm(); ?>
<div id="comment_create_upload_progress_<?= $id ?>" style="display:none;margin:10px 0px;"></div>
<?=
\humhub\modules\file\widgets\FilePreview::widget([
<?= FilePreview::widget([
'id' => 'comment_create_upload_preview_' . $id,
'options' => ['style' => 'margin-top:10px'],
'edit' => true
]);
?>
]); ?>
</div>

View File

@ -2,10 +2,19 @@
use humhub\libs\Html;
use humhub\widgets\TimeAgo;
use humhub\widgets\RichText;
use humhub\modules\content\widgets\richtext\RichText;
use humhub\modules\user\widgets\Image as UserImage;
use humhub\modules\file\widgets\ShowFiles;
use humhub\modules\like\widgets\LikeLink;
/* @var $deleteUrl string */
/* @var $editUrl string */
/* @var $loadUrl string */
/* @var $user \humhub\modules\user\models\User */
/* @var $canWrite bool */
/* @var $canDelete bool */
/* @var $createdAt string */
/* @var $updatedAt string */
?>
<div class="media" id="comment_<?= $comment->id; ?>"
@ -56,7 +65,7 @@ use humhub\modules\like\widgets\LikeLink;
<!-- class comment_edit_content required since v1.2 -->
<div class="content comment_edit_content" id="comment_editarea_<?= $comment->id; ?>">
<div id="comment-message-<?= $comment->id; ?>" class="comment-message" data-ui-markdown data-ui-show-more data-read-more-text="<?= Yii::t('CommentModule.widgets_views_showComment', 'Read full comment...') ?>">
<?= RichText::widget(['text' => $comment->message, 'record' => $comment, 'markdown' => true]); ?>
<?= RichText::output($comment->message); ?>
</div>
<?= ShowFiles::widget(['object' => $comment]); ?>
</div>

View File

@ -53,6 +53,12 @@ class Module extends \humhub\components\Module
*/
public $maxAttachedFiles = 50;
/**
* @since 1.3
* @var integer Maximum allowed number of oembeds in richtexts
*/
public $maxOembeds = 5;
/**
* @inheritdoc
*/

View File

@ -2,6 +2,8 @@
use yii\helpers\Html;
/** @var $originator \humhub\modules\user\models\User */
echo Yii::t('ContentModule.activities_views_created', '{displayName} created a new {contentTitle}.', [
'{displayName}' => '<strong>' . Html::encode($originator->displayName) . '</strong>',
'{contentTitle}' => $this->context->getContentInfo($source)

View File

@ -1,11 +1,15 @@
<?php
use humhub\modules\content\widgets\richtext\RichText;
use yii\helpers\Html;
/* @var $originator \humhub\modules\user\models\User */
/* @var $source \humhub\modules\content\interfaces\ContentOwner */
echo Yii::t('ContentModule.activities_views_created', '{displayName} created a new {contentTitle}.', array(
'{displayName}' => '<strong>' . Html::encode($originator->displayName) . '</strong>',
'{contentTitle}' => '<strong>' . Html::encode($source->getContentName()) . '</strong>'
));
?>
<br />
<em>"<?php echo \humhub\widgets\RichText::widget(['text' => $source->getContentDescription(), 'minimal' => true]); ?>"</em>
<em>"<?= RichText::preview($source->getContentDescription()); ?>"</em>

View File

@ -1,11 +1,15 @@
<?php
use humhub\modules\content\widgets\richtext\RichText;
use yii\helpers\Html;
echo strip_tags(Yii::t('ContentModule.activities_views_created', '{displayName} created a new {contentTitle}.', array(
/* @var $originator \humhub\modules\user\models\User */
/* @var $source \humhub\modules\content\interfaces\ContentOwner */
echo strip_tags(Yii::t('ContentModule.activities_views_created', '{displayName} created a new {contentTitle}.', [
'{displayName}' => Html::encode($originator->displayName),
'{contentTitle}' => html_entity_decode(Html::encode($source->getContentName()))
)));
]));
?>
"<?php echo strip_tags(\humhub\widgets\RichText::widget(['text' => $source->getContentDescription(), 'minimal' => true])); ?>"
"<?= strip_tags(RichText::preview($source->getContentDescription())); ?>"

View File

@ -0,0 +1,41 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\assets;
use yii\web\AssetBundle;
/**
* Asset for core content resources.
*
* @since 1.3
* @author buddha
*/
class LegacyRichTextAsset extends AssetBundle
{
/**
* @inheritdoc
*/
public $sourcePath = '@content/resources';
/**
* @inheritdoc
*/
public $js = [
'js/humhub.ui.richtext.legacy.js'
];
/**
* @inheritdoc
*/
public $depends = [
'humhub\assets\CoreApiAsset'
];
}

View File

@ -0,0 +1,42 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\assets;
use humhub\assets\ProsemirrorEditorAsset;
use yii\web\AssetBundle;
/**
* Asset for core content resources.
*
* @since 1.3
* @author buddha
*/
class ProseMirrorRichTextAsset extends AssetBundle
{
/**
* @inheritdoc
*/
public $sourcePath = '@content/resources';
/**
* @inheritdoc
*/
public $js = [
'js/humhub.ui.richtext.prosemirror.js'
];
/**
* @inheritdoc
*/
public $depends = [
ProsemirrorEditorAsset::class
];
}

View File

@ -8,6 +8,7 @@
namespace humhub\modules\content\components;
use humhub\modules\content\models\Content;
use Yii;
use yii\base\Exception;
use humhub\components\ActiveRecord;

View File

@ -426,7 +426,7 @@ humhub.module('ui.richtext', function(module, require, $) {
// set plugin settings for showing results
this.setting.highlightFirst = true;
this.setting.displayTpl = '<li class="${cssClass}" data-value="@${name}">${image} ${name}</li>';
$.getJSON(that.options.mentioningUrl, {keyword: query}, function(data, test) {
$.getJSON(that.options.mentioningUrl, {keyword: query}, function(data) {
if(!data.length) {
that.emptyMentionings.push(query);

View File

@ -0,0 +1,146 @@
/*
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
humhub.module('ui.richtext.prosemirror', function(module, require, $) {
var object = require('util').object;
var client = require('client');
var Widget = require('ui.widget').Widget;
var MarkdownEditor = prosemirror.MarkdownEditor;
var MentionProvider = prosemirror.MentionProvider;
var RichTextEditor = function(node, options) {
Widget.call(this, node, options);
};
object.inherits(RichTextEditor, Widget);
RichTextEditor.component = 'humhub-ui-richtexteditor';
RichTextEditor.prototype.getDefaultOptions = function() {
return {
attributes: {
'class': 'atwho-input form-control humhub-ui-richtext',
'data-ui-markdown': true,
},
mention: {
provider: new HumHubMentionProvider(module.config.mention)
},
emoji: module.config.emoji,
oembed: module.config.oembed,
translate: function(key) {
return module.text(key);
}
};
};
RichTextEditor.prototype.init = function() {
if(this.options.placeholder) {
this.options.placeholder = {
text: this.options.placeholder,
'class' : 'placeholder atwho-placeholder'
};
}
if(this.options.disabled) {
setTimeout($.proxy(this.disable, this), 50);
}
this.editor = new MarkdownEditor(this.$, this.options);
$content = this.$.find('[data-ui-richtext]').text();
this.editor.init($content);
if(this.options.focus) {
this.editor.view.focus();
}
var that = this;
this.$.on('focusout', function() {
that.getInput().val(that.editor.serialize());
}).on('clear', function() {
that.editor.clear();
}).on('focus', function() {
that.focus();
});
};
RichTextEditor.prototype.focus = function(tooltip) {
this.editor.view.focus();
this.editor.view.focus();
};
RichTextEditor.prototype.disable = function(tooltip) {
tooltip = tooltip || this.options.disabledText;
debugger;
$(this.editor.view.dom).removeAttr('contenteditable').attr({
disabled: 'disabled',
title: tooltip,
}).tooltip({
placement: 'bottom'
});
};
RichTextEditor.prototype.getInput = function() {
return $('#'+this.$.attr('id')+'_input');
};
var RichText = function(node, options) {
Widget.call(this, node, options);
};
object.inherits(RichText, Widget);
RichText.component = 'humhub-ui-richtext';
RichText.prototype.init = function() {
// If in edit mode we do not actually render, we just hold the content
if(!this.options.edit) {
this.editor = new MarkdownEditor(this.$, this.options);
this.$.html(this.editor.render());
}
};
HumHubMentionProvider = function(options) {
MentionProvider.call(this, options);
};
object.inherits(HumHubMentionProvider, MentionProvider);
HumHubMentionProvider.prototype.find = function(query, node) {
if(this.xhr) {
this.xhr.abort();
}
var that = this;
var $editor = Widget.closest(node);
return new Promise(function(resolve, reject) {
client.get($editor.options.mentioningUrl, {
data: {keyword: query},
beforeSend: function(jqXHR) {
that.xhr = jqXHR;
}
}).then(function(response) {
resolve(response.data);
}).catch(function(err) {
reject(reject)
});
});
};
module.export({
initOnPjaxLoad: true,
unload: function(pjax) {
$('.humhub-richtext-provider').remove();
},
RichTextEditor: RichTextEditor,
RichText: RichText,
api: prosemirror
});
});

View File

@ -0,0 +1,244 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\widgets\richtext;
use Yii;
use humhub\components\Event;
use humhub\modules\content\components\ContentActiveRecord;
use humhub\modules\content\components\ContentAddonActiveRecord;
use humhub\widgets\JsWidget;
use yii\base\InvalidParamException;
/**
* AbstractRichText serves as the base class for rich text implementations.
*
* A rich text Widget is used for rendering the output of a rich text and usually is related to an [[AbstractRichTextEditor]] implementation
* defined by [[editorClass]] and an [[AbstractRichTextProcessor]] implementation for post-processing rich texts defined by [[processorClass]].
*
* In order for some features as the _Mentionings_ to work a rich text should only be used for [[ContentActiveRecord]] or [[ContentAddonActiveRecord]] models.
*
* A rich text can implement the following core features:
*
* - [[preset]] defines a preset of plugins and settings
* - [[include]] and **exclude** specific plugins
* - [[pluginOptions]] configure specific plugins
* - [[minimal]] rendering mode used e.g. for activities and mails, should truncate the input by means of the [[maxLenght]] option
* - [[edit]] rendering mode used for rendering the rich text in a format interpretable by the editor
*
* Common rich text plugins are _Mentionings_, _Oembed_ and text formatting features.
*
* It's up to the implementation which features or plugins are supported.
*
* After saving the related record of a rich text the [[postProcess()]] function should be called manually in order to
* parse the rich text e.g. for _Mentionings_ or other features etc.
* This usually happens in the [[ContentActiveRecord::afterSave()]] function of the record.
*
* Furthermore the [[output()]] function can be used as convenience function to render a given text.
*
* ```php
* $richText = RichText::output($text, $options) ?>
* ```
*
* > Note: Subclasses provided by third party modules should ideally be compatible with the default implementation in
* order to be able to switch the RichText implementation without loosing the semantic.
*
* @author Julian Harrer <julian.harrer@humhub.com>
* @since 1.3
*/
abstract class AbstractRichText extends JsWidget
{
/**
* @event Event an event raised after the post-process phase of the rich text.
*/
const EVENT_POST_PROCESS = 'postProcess';
/**
* @event \humhub\modules\search\events\ParameterEvent with parameter 'output'
*/
const EVENT_BEFORE_OUTPUT = 'beforeOutput';
/**
* @var string defines a preset of rich text features and settings
* @see AbstractRichTextEditor::$preset
*/
public $preset;
/**
* @inheritdoc
*/
public $init = true;
/**
* @var string text to display
*/
public $text = "";
/**
* @var boolean
*/
public $encode = true;
/**
* @var boolean enables the edit rendering mode
*/
public $edit = false;
/**
* @var boolean enables the minimal rendering mode used for example for previews, this mode should take the
* [[maxLenght]] setting into account for truncating the preview content.
*/
public $minimal = false;
/**
* @var int setting used to truncate the rich text content, usually related to [[minimal]] mode and used for previews
*/
public $maxLength = 0;
/**
* @var boolean defines if this rich text is also used as client side markdown text.
* @deprecated since 1.3
*/
public $markdown = false;
/**
* @var array Can be used to explicitly include specific plugins in addition to the set of defaults (preset)
*/
public $include = [];
/**
* @var array Can be used to exclude specific plugins from the set of defaults (preset)
*/
public $exclude = [];
/**
* @var array rich text plugin settings. Note that changes of those settings may require an additional preset.
*/
public $pluginOptions = [];
/**
* @var string [[AbstractRichTextEditor]] subclass, used for rendering the editor widget.
* @see editorWidget
*/
protected static $editorClass;
/**
* @var string [[AbstractRichTextProcessor]] subclass, used for post-processing the rich text content
* @see postProcess
*/
protected static $processorClass;
/**
* @inheritdoc
*/
public function init() {
parent::init();
if(!static::$editorClass || ! static::$processorClass) {
throw new InvalidParamException('No editor or processor class set for rich text '.static::class);
}
}
/**
* @inheritdoc
*/
public function getData()
{
return [
'exclude' => $this->exclude,
'include' => $this->include,
'plugin-options' => $this->pluginOptions,
'preset' => $this->preset,
'edit' => $this->edit,
'ui-richtext' => true
];
}
/**
* Used for the post-processing of the rich text, normally called within [[ContentActiveRecord::afterSave()]]
* of the related [[ContentActiveRecord]].
*
* @param $text string RichText content
* @param ContentActiveRecord|ContentAddonActiveRecord $record
*/
public static function postProcess($text, $record)
{
$processorResult = static::getProcessor($text,$record)->process();
Event::trigger(static::class, static::EVENT_POST_PROCESS, new Event(['data' => ['processorClass' => static::$processorClass, 'text' => $text, 'record' => $record]]));
return $processorResult;
}
/**
* @return string renders the related [[AbstractRichTextEditor]] widget of this rich text implementation
*/
public static function editorWidget($config = [])
{
return call_user_func(static::getEditorClass().'::widget', $config);
}
/**
* @param $text string rich text content to be processed
* @param $record ContentActiveRecord|ContentAddonActiveRecord related model holding the rich text
* @return AbstractRichTextProcessor the related post-processor
*/
public static function getProcessor($text, $record)
{
return Yii::createObject([
'class' => static::getProcessorClass(),
'text' => $text,
'record' => $record]);
}
/**
* @return string
*/
public static function getProcessorClass()
{
return static::$processorClass;
}
/**
* @return string
*/
public static function getEditorClass()
{
return static::$editorClass;
}
/**
* Renders the given text by means of the given config.
*
* This acts as convenience method for [[widget()]].
*
* @param $text string rich text content to be rendered
* @param array $config rich text widget options
* @return string render result
*/
public static function output($text, $config = [])
{
$config['text'] = $text;
return static::widget($config);
}
/**
* Renders the given text in minimal render mode truncated by the `maxLength` parameter.
*
* This acts as convenience method for [[widget()]].
*
* @param $text string rich text content to be rendered
* @param $maxLength int max length of the preview
* @param array $config rich text widget options
* @return string render result
*/
public static function preview($text, $maxLength = 0)
{
$config['maxLength'] = $maxLength;
$config['minimal'] = true;
return static::output($text, $config);
}
}

View File

@ -0,0 +1,254 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\widgets\richtext;
use humhub\widgets\InputWidget;
use Yii;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\helpers\Url;
/**
* Abstract class for RichTextEditor implementations.
*
* Most RichTextEditor fields will use some kind of contenteditable element in combination with an underlying input field.
*
* A RichTextEditor feature set may vary between implementations, and is ideally configurable for a single instance through
* plugin settings like:
*
* - `$preset`: select a preset as 'markdown', 'normal', 'full' or a custom preset provided by an other module
* - `$plugins`: set plugin options
* - `§includes`: include some additional plugins
* - `$exclude`: exclude some specific plugins
*
* Some common plugin extensions are
* - placeholder
* - mention
* - oembed
* - emoji
*
* This abstract class provides direct settings of some core plugins as `$placeholder` and `$mentionUrl`.
* Other plugin settings can be configured by means of the `$plugins` array.
*
* To render the RichtText output for a given plain text use the static `output()` function, which internally will determine the configured
* `RichText` class to transform the text into the output format required by the RichText e.g. Markdown or directly HTML.
*
* > Note: the `output()` function by default is also used in editor edit mode with the `edit` flag set to true.
* > Note: Some Richtext implementation may not support all mentioned features and plugins.
*
* @author Julian Harrer <julian.harrer@humhub.com>
* @since 1.3
*/
class AbstractRichTextEditor extends InputWidget
{
/**
* @var string richtext feature preset e.g: 'markdown', 'normal', 'full'
*/
public $preset;
/**
* Can be used to overwrite the default placeholder.
*
* @var string
*/
public $placeholder;
/**
* The url used for the default @ metioning.
* If there is no $searchUrl is given, the $searchRoute will be used instead.
*
* @var string
*/
public $mentioningUrl;
/**
* Route used for the default @ mentioning. This will only be used if
* not $searchUrl is given.
*
* @var string
*/
protected $mentioningRoute = "/search/search/mentioning";
/**
* RichText plugin supported for this instance.
* By default all features will be included.
*
* @var array
*/
public $include = [];
/**
* RichText plugins not supported in this instance.
* This can also be used do exclude specific plugins if not supported by the RichText implementation.
*
* @var array
*/
public $exclude = [];
/**
* Additional pluginoptions
* @var array
*/
public $pluginOptions = [];
/**
* If set to true the picker will be focused automatically.
*
* @var boolean
*/
public $focus = false;
/**
* Disables the input field.
* @var boolean
*/
public $disabled = false;
/**
* Will be used as user feedback, why this richtext is disabled.
*
* @var string
*/
public $disabledText = false;
/**
* @inheritdoc
*/
public $init = true;
/**
* @inheritdoc
*/
public $visible = true;
/**
* @var boolean defines if the default label should be rendered.
*/
public $label = false;
/**
* @var [] renderer class definition
*/
public static $renderer;
/**
* @inhertidoc
*/
public function run()
{
$inputOptions = $this->getInputAttributes();
if ($this->form != null) {
$input = $this->form->field($this->model, $this->attribute)->textarea($inputOptions)->label(false);
$richText = Html::tag('div', $this->editOutput($this->getValue()), $this->getOptions());
$richText = $this->getLabel() . $richText;
} else if ($this->model != null) {
$input = Html::activeTextarea($this->model, $this->attribute, $inputOptions);
$richText = Html::tag('div', $this->editOutput($this->getValue()), $this->getOptions());
$richText = $this->getLabel() . $richText;
} else {
$input = Html::textarea(((!$this->name) ? 'richtext' : $this->name), $this->value, $inputOptions);
$richText = Html::tag('div', $this->editOutput($this->getValue()), $this->getOptions());
$richText = $this->getLabel() . $richText;
}
return $input . $richText . $this->prepend();
}
/**
* This method can be overwritten in order to prepend content after the actual rich text content.
* @return string
*/
public function prepend() {
return '';
}
/**
* @return array attributes added to the hidden textarea input of the richtext
*/
public function getInputAttributes()
{
return [
'id' => $this->getId(true) . '_input',
'style' => 'display:none;',
'title' => $this->placeholder
];
}
/**
* @return bool|string returns the html label used for rendering
*/
public function getLabel()
{
if(!$this->label) {
return "";
}
if ($this->label === true && $this->model != null) {
return Html::activeLabel($this->model, $this->attribute, ['class' => 'control-label']);
} else {
return $this->label;
}
}
/**
* Returns the content formatted for editing by means of the configured [[renderer]].
*
* This function will call [[RichText::output()]] with given richtext settings and `edit = true`.
*
* @param $content
* @param array $params
* @return string
*/
protected function editOutput($content)
{
$params = [
'edit' => true,
'exclude' => $this->exclude,
'include' => $this->include,
'pluginOptions' => $this->pluginOptions,
'preset' => $this->preset,
];
$config = ArrayHelper::merge(static::$renderer, $params);
unset($config['class']);
return call_user_func(static::$renderer['class'].'::output', $content, $config);
}
/**
* @inheritdoc
*/
public function getData()
{
$result = [
'exclude' => $this->exclude,
'include' => $this->include,
'mentioning-url' => $this->getMentioningUrl(),
'placeholder' => $this->placeholder,
'plugin-options' => $this->pluginOptions,
'preset' => $this->preset,
'focus' => $this->focus
];
if ($this->disabled) {
$result['disabled'] = true;
$result['disabled-text'] = $this->disabledText;
}
return $result;
}
/**
* @return string returns the url used by the mention plugin
*/
public function getMentioningUrl()
{
return ($this->mentioningUrl) ? $this->mentioningUrl : Url::to([$this->mentioningRoute]);
}
}

View File

@ -0,0 +1,73 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\widgets\richtext;
use humhub\modules\content\components\ContentActiveRecord;
use humhub\modules\content\components\ContentAddonActiveRecord;
use yii\base\Object;
/**
* Rich text processors are used to post-process the rich text after saving the related content record.
*
* By default this includes the parsing of mentionings and oembed links, a rich text implementation may provide further
* post-processing steps.
*
* @author Julian Harrer <julian.harrer@humhub.com>
* @since 1.3
*/
abstract class AbstractRichTextProcessor extends Object
{
/**
* @var string RichText content
*/
public $text;
/**
* @var ContentAddonActiveRecord|ContentActiveRecord $record
*/
public $record;
/**
* Executes post process logic
*/
public function process() {
$result = [];
$this->parseOembed();
if($this->record instanceof ContentAddonActiveRecord || $this->record instanceof ContentAddonActiveRecord) {
$result['mentioning'] = $this->parseMentioning();
}
return $result;
}
/**
* This function is called while processing the Richtext content and will parse the given text for urls and preloads the oembed result.
* Richtext subclasses have to provide their own parsing logic.
*
* @param $text string richtext content
*/
public abstract function parseOembed();
/**
* This function is called while processing the richtext content and is responsible for parsing and creating mentionings.
* Richtext implementations have to provide their own parsing logic.
*
* e.g:
*
* ```php
* $guids = someParserLogic();
* return Mentioning::mention($guids, $record);
* ```
* @param $message
* @return array list of successfully mentioned users
*/
public abstract function parseMentioning();
}

View File

@ -1,66 +1,35 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2016 HumHub GmbH & Co. KG
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\widgets;
namespace humhub\modules\content\widgets\richtext;
use humhub\libs\ParameterEvent;
use humhub\models\UrlOembed;
use humhub\modules\space\models\Space;
use humhub\modules\user\models\Mentioning;
use humhub\modules\user\models\User;
use Yii;
use yii\helpers\Html;
use humhub\models\UrlOembed;
use humhub\libs\ParameterEvent;
/**
* RichText dis
* Legacy rich text implementation.
*
* @author luke
* @deprecated since 1.3 this is the old rich text implementation which won't be maintained in the future.
*/
class RichText extends JsWidget
class HumHubRichText extends AbstractRichText
{
public static $editorClass = HumHubRichTextEditor::class;
public static $processorClass = HumHubRichTextProcessor::class;
/**
* @var string text to display
* @inheritdoc
*/
public $text = "";
/**
* @var boolean
*/
public $encode = true;
/**
* @var boolean
*/
public $minimal = false;
/**
* @var boolean edit mode
*/
public $edit = false;
/**
* @var \humhub\components\ActiveRecord this richtext belongs to
*/
public $record = null;
/**
* @var int
*/
public $maxLength = 0;
/**
* @var boolean defines if this richtext is also used as clientside markdown text.
*/
public $markdown = false;
/**
* @event \humhub\modules\search\events\ParameterEvent with parameter 'output'
*/
const EVENT_BEFORE_OUTPUT = 'beforeOutput';
public function run()
{
if ($this->encode) {
@ -68,38 +37,7 @@ class RichText extends JsWidget
}
if (!$this->minimal && !$this->edit) {
$maxOembedCount = 3; // Maximum OEmbeds
$oembedCount = 0; // OEmbeds used
$that = $this;
$pattern= <<<REGEXP
/(?(R) # in case of recursion match parentheses
\(((?>[^\s()]+)|(?R))*\)
| # else match a link with title
(https?|ftp):\/\/(([^\s()]+)|(?R))+(?<![\.,:;\'"!\?\s])
)/x
REGEXP;
$this->text = preg_replace_callback($pattern, function ($match) use (&$oembedCount, &$maxOembedCount, &$that) {
// Try use oembed
if ($maxOembedCount > $oembedCount) {
$oembed = UrlOembed::GetOEmbed($match[0]);
if ($oembed) {
$oembedCount++;
return $oembed;
}
}
$options = strpos($match[0], Yii::$app->settings->get('baseUrl')) === 0 ? [] : ['target' => '_blank', 'rel' => "noopener noreferrer"];
// The markdown parser will parse the links by itself
return ($this->markdown) ? $match[0] : Html::a($match[0], Html::decode($match[0]), $options);
}, $this->text);
// mark emails
$this->text = preg_replace_callback('/[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,3})/', function ($match) {
return Html::mailto($match[0]);
}, $this->text);
$this->text = self::translateOembed($this->text, $this->markdown);
}
// get user and space details from guids
@ -130,6 +68,48 @@ REGEXP;
return trim($output);
}
/**
* Parses for links and checks if they are embedable by oembed.
*
* @param $text
* @param $markdown
* @return mixed
*/
public static function translateOembed($text, $markdown)
{
$maxOembedCount = 3; // Maximum OEmbeds
$oembedCount = 0; // OEmbeds used
$pattern= <<<REGEXP
/(?(R) # in case of recursion match parentheses
\(((?>[^\s()]+)|(?R))*\)
| # else match a link with title
(https?|ftp):\/\/(([^\s()]+)|(?R))+(?<![\.,:;\'"!\?\s])
)/x
REGEXP;
$text = preg_replace_callback($pattern, function ($match) use (&$oembedCount, &$maxOembedCount, &$markdown) {
// Try use oembed
if ($maxOembedCount > $oembedCount) {
$oembed = UrlOembed::GetOEmbed($match[0]);
if ($oembed) {
$oembedCount++;
return $oembed;
}
}
$options = strpos($match[0], Yii::$app->settings->get('baseUrl')) === 0 ? [] : ['target' => '_blank', 'rel' => "noopener noreferrer"];
// The markdown parser will parse the links by itself
return ($markdown) ? $match[0] : Html::a($match[0], Html::decode($match[0]), $options);
}, $text);
// mark emails
return preg_replace_callback('/[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,3})/', function ($match) {
return Html::mailto($match[0]);
}, $text);
}
/**
* Replace emojis from text to img tag
*
@ -138,7 +118,7 @@ REGEXP;
*/
public static function translateEmojis($text, $show = true)
{
$emojis = array(
$emojis = [
"Relaxed", "Yum", "Relieved", "Hearteyes", "Cool", "Smirk",
"KissingClosedEyes", "StuckOutTongue", "StuckOutTongueWinkingEye", "StuckOutTongueClosedEyes", "Disappointed", "Frown",
"ColdSweat", "TiredFace", "Grin", "Sob", "Gasp", "Gasp2",
@ -149,7 +129,7 @@ REGEXP;
"Sleeping", "NoMouth", "Mask", "Worried", "Smile", "Muscle",
"Facepunch", "ThumbsUp", "ThumbsDown", "Beers", "Cocktail", "Burger",
"PoultryLeg", "Party", "Cake", "Sun", "Fire", "Heart"
);
];
return preg_replace_callback('@;(\w*?);@', function($hit) use(&$show, &$emojis) {
if (in_array($hit[1], $emojis)) {
@ -165,14 +145,14 @@ REGEXP;
/**
* Translate guids from users to username
*
* @param strint $text Contains the complete message
* @param string $text Contains the complete message
* @param boolean $buildAnchors Wrap the username with a link to the profile, if it's true
*/
public static function translateMentioning($text, $buildAnchors = true)
{
return preg_replace_callback('@\@\-([us])([\w\-]*?)($|[\.,:;\'"!\?\s])@', function($hit) use(&$buildAnchors) {
if ($hit[1] == 'u') {
$user = \humhub\modules\user\models\User::findOne(['guid' => $hit[2]]);
$user = User::findOne(['guid' => $hit[2]]);
if ($user !== null) {
if ($buildAnchors) {
return ' <span contenteditable="false"><a href="' . $user->getUrl() . '" target="_self" class="atwho-user" data-richtext-feature data-guid="@-u' . $user->guid . '">@' . Html::encode($user->getDisplayName()) . '&#x200b;</a></span>' . $hit[3];
@ -180,7 +160,7 @@ REGEXP;
return " @" . Html::encode($user->getDisplayName()) . $hit[3];
}
} elseif ($hit[1] == 's') {
$space = \humhub\modules\space\models\Space::findOne(['guid' => $hit[2]]);
$space = Space::findOne(['guid' => $hit[2]]);
if ($space !== null) {
if ($buildAnchors) {
@ -192,5 +172,4 @@ REGEXP;
return $hit[0];
}, $text);
}
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\widgets\richtext;
use Yii;
use yii\helpers\Html;
use yii\helpers\Url;
use humhub\widgets\RichText;
/**
* Legacy rich text editor implementation.
*
* @deprecated since 1.3 this is the legacy rich text editor implementation which won't be maintained in the future.
*/
class HumHubRichTextEditor extends AbstractRichTextEditor
{
/**
* Defines the javascript picker implementation.
*
* @var string
*/
public $jsWidget = 'ui.richtext.Richtext';
/**
* @inheritdoc
*/
public static $renderer = [
'class' => HumHubRichText::class
];
public function getAttributes()
{
return [
'class' => "atwho-input form-control humhub-ui-richtext",
'contenteditable' => "true",
];
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\widgets\richtext;
use humhub\models\UrlOembed;
use humhub\modules\user\models\Mentioning;
/**
* Legacy rich text processing logic.
*
* @deprecated since 1.3 this hold the post-processing logic for the legacy humhub rich text
*/
class HumHubRichTextProcessor extends AbstractRichTextProcessor
{
/**
* @inheritdoc
*/
public function parseOembed()
{
preg_replace_callback('/http(.*?)(\s|$)/i', function ($match) {
UrlOembed::preload($match[0]);
}, $this->text);
}
/**
* @inheritdoc
*/
public function parseMentioning()
{
$result = [];
preg_replace_callback('@\@\-u([\w\-]*?)($|\s|\.)@', function ($hit) use (&$record, &$result) {
$result = array_merge($result, Mentioning::mention($hit[1], $this->record));
}, $this->text);
return $result;
}
}

View File

@ -0,0 +1,266 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\widgets\richtext;
use humhub\libs\Helpers;
use humhub\libs\Markdown;
use humhub\modules\content\assets\ProseMirrorRichTextAsset;
use humhub\modules\content\models\ContentContainer;
use humhub\modules\space\models\Space;
use humhub\modules\user\models\User;
use Yii;
use yii\helpers\Html;
use humhub\models\UrlOembed;
/**
* The ProsemirrorRichText is a [Prosemirror](https://prosemirror.net) and [Markdown-it](https://github.com/markdown-it/markdown-it)
* based rich text implementation.
*
* This rich text is a pure markdown based rich text enhanced with some additional features and markdown plugins.
*
* In order to stay compatible with the legacy rich text content, this rich text contains some pre-processing logic on the server side, which can be deactivated
* if not required by means of the `richtextCompatMode` setting of the `content` module.
*
* Note that this rich text, when in edit mode, just outputs an invisible div with pure markdown content, which will be interpreted by
* the related ProsemirrorRichTextEditor.
*
* This rich text implementation supports all features as [[preset]], the [[includes]] and [[excludes]] of plugins
* and is extensible through additional javascript plugins.
*
* Note that the plugin settings as [[preset]], [[includes]], [[excludes]], [[pluginOptions]] have to be set for the editor as
* well as for the rich text output widget.
*
* Beside the default (GFM based) markdown-it syntax, the following plugins are available:
*
* ### anchors
*
* If enabled will add anchors to heading elements. This plugin is disabled by default and can be enabled as follows:
*
* ```php
* RichText::output($text, [
* 'preset' => 'myPreset',
* 'pluginOptions' => [
* 'anchors' => true
* ]
* ]);
*
* // or with specific settings
* RichText::output($text, [
* 'preset' => 'myPreset',
* 'pluginOptions' => [
* 'anchors' => ['permalink' => true]
* ]
* ]);
* ```
* See [markdown-it-anchor](https://www.npmjs.com/package/markdown-it-anchor) for more settings.
*
* ### clipboard
*
* Allows pasting of raw markdown content into the richtext editor.
*
* ### emoji
*
* [twemoji](https://github.com/twitter/twemoji) and [markdown-it-emoji](https://www.npmjs.com/package/markdown-it-emoji) based emojies
*
* ### fullscreen
*
* Adds a enlarge/shrink button to the rich text editor.
*
* ### mention
*
* Markdown link extension for mentionings in the form of [<name>](mention:<guid> "<profile-url>").
*
* ### oembed
*
* Enables scanning and replacement of pasted oembed links in form of link extensions [<url>](oembed:url)
*
* ### placeholder
*
* Text placeholder for the editor input
*
* ### strikethrough
*
* Markdown strikethrough formatting.
*
* ### table
*
* Simple Markdown table support.
*
* ### upload
*
* File upload support.
*
* @author Julian Harrer <julian.harrer@humhub.com>
* @see https://github.com/humhub/humhub-prosemirror for more information about the prosemirror-richtext client implementation
* @see https://prosemirror.net/docs/ Prosemirror documentation
* @see https://github.com/markdown-it/markdown-it markdown-it repository
* @since 1.3
*/
class ProsemirrorRichText extends AbstractRichText
{
/**
* @inheritdoc
*/
public $jsWidget = 'ui.richtext.prosemirror.RichText';
/**
* @var array holds included oembeds used for rendering
*/
private $oembeds = [];
/**
* @inheritdoc
*/
protected static $editorClass = ProsemirrorRichTextEditor::class;
/**
* @inheritdoc
*/
protected static $processorClass = ProsemirrorRichTextProcessor::class;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
if($this->edit) {
$this->visible = false;
}
}
/**
* @inheritdoc
*/
public function run() {
if($this->minimal) {
return $this->renderMinimal();
}
if($this->isCompatibilityMode()) {
$this->text = RichTextCompatibilityParser::parse($this->text);
}
foreach (static::scanLinkExtension($this->text, 'oembed') as $match) {
if(isset($match[3])) {
$this->oembeds[$match[3]] = UrlOembed::GetOEmbed($match[3]);
}
}
$this->text = static::parseMentionings($this->text, $this->edit);
if ($this->maxLength > 0) {
$this->text = Helpers::truncateText($this->text, $this->maxLength);
}
$this->content = $this->text;
return parent::run() . $this->buildOembedOutput();
}
/**
* @return string truncated and stripped text
*/
protected function renderMinimal() {
$parser = new Markdown();
$result = strip_tags($parser->parse($this->text));
return ($this->maxLength > 0) ? Helpers::truncateText($result, $this->maxLength) : $result;
}
/**
* @return string html extension holding the actual oembed dom nodes which will be embedded into the rich text
*/
public function buildOembedOutput()
{
$result = '';
foreach ($this->oembeds as $url => $oembed) {
$result .= Html::tag('div', $oembed, ['data-oembed' => $url]);
}
return Html::tag('div', $result, ['class' => 'richtext-oembed-container', 'style' => 'display:none']);
}
/**
* Parses the given text for mentionings and replaces them with possibly updated values (e.g. name).
*
* @param $text string rich text content to parse
* @param $edit bool if not in edit mode deleted or inactive users will be rendered differently
* @return mixed
*/
public static function parseMentionings($text, $edit = false)
{
// $match[0]: markdown, $match[1]: name, $match[2]: extension(mention) $match[3]: guid, $match[4]: url
return static::replaceLinkExtension($text, 'mention', function($match) use ($edit) {
$contentContainer = ContentContainer::findOne(['guid' => $match[3]]);
$notFoundResult = '['.$match[1].'](mention:'.$match[2].' "#")';
if(!$contentContainer || !$contentContainer->getPolymorphicRelation()) {
// If no user or space was found we leave out the url in the non edit mode.
return $edit ? '['.Html::encode($match[1]).'](mention:'.$match[3].' "'.$match[4].'")' : $notFoundResult;
}
$container = $contentContainer->getPolymorphicRelation();
if($container instanceof User) {
return $container->isActive()
? '['.Html::encode($container->getDisplayName()).'](mention:'.$container->guid.' "'.$container->getUrl().'")'
: $notFoundResult;
} else if($container instanceof Space) {
return '['.Html::encode($container->name).'](mention:'.$container->guid.' "'.$container->getUrl().'")';
}
});
}
/**
* Can be used to scan for link extensions of the form [<text>](<extension>:<url> "<title>") in which the actual meaning
* of the placeholders is up to the extension itself.
*
* @param $text string rich text content to parse
* @param $extension string|null extension string if not given all extension types will be included
* @return array
*/
public static function scanLinkExtension($text, $extension = null)
{
preg_match_all(static::getLinkExtensionPattern($extension), $text, $match, PREG_SET_ORDER);
return $match;
}
/**
* Can be used to scan and replace link extensions of the form [<text>](<extension>:<url> "<title>") in which the actual meaning
* of the placeholders is up to the extension itself.
*
* @param $text string rich text content to parse
* @param $extension string|null extension string if not given all extension types will be included
* @return mixed
*/
public static function replaceLinkExtension($text, $extension = null, $callback)
{
return preg_replace_callback(static::getLinkExtensionPattern($extension), $callback, $text);
}
/**
* @param string $extension the extension to parse, if not set all extensions are included
* @return string the regex pattern for a given extension or all extension if no specific extension string is given
*/
protected static function getLinkExtensionPattern($extension = '[a-zA-Z]+')
{
return '/(?<!\\\\)\[([^\]]*)\]\(('.$extension.'):{1}([^\)\s]*)(?:\s")?([^\)"]*)?(?:")?\)/is';
}
/**
* Checks if the compatibility mode is enabled.
* The compatibility mode is only required, if old content is present and won't be activated for new installations.
*
* @return bool
*/
public function isCompatibilityMode()
{
return Yii::$app->getModule('content')->settings->get('richtextCompatMode', 1);
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\widgets\richtext;
use humhub\modules\content\assets\ProseMirrorRichTextAsset;
use humhub\modules\file\widgets\UploadInput;
/**
* Rich text editor implementation for the ProsemirrorRichText.
*
* @author Julian Harrer <julian.harrer@humhub.com>
* @see ProsemirrorRichText for a more detailed description of supported plugins and features.
* @since 1.3
*/
class ProsemirrorRichTextEditor extends AbstractRichTextEditor
{
/**
* @inheritdoc
*/
public $jsWidget = 'ui.richtext.prosemirror.RichTextEditor';
public static $renderer = [
'class' => ProsemirrorRichText::class
];
/**
* @inheritdoc
*/
public function getAttributes()
{
return [
'class' => 'ProsemirrorEditor'
];
}
/**
* Prepends an upload input form element to the rich text editor used by the upload editor plugin.
*/
public function prepend()
{
return UploadInput::widget([
'id' => $this->getId(true).'-file-upload',
'hideInStream' => true
]);
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\widgets\richtext;
use humhub\models\UrlOembed;
use humhub\modules\user\models\Mentioning;
/**
* Class ProsemirrorRichTextProcessor provides pre-processor logic for oembed and mentionings for the ProsemirrorRichText.
*
* @author Julian Harrer <julian.harrer@humhub.com>
* @since 1.3
*/
class ProsemirrorRichTextProcessor extends AbstractRichTextProcessor
{
/**
* Parses oembed link extensions in the form of [<url>](oembed:<url>) and preloads the given oembed dom.
*/
public function parseOembed()
{
$matches = ProsemirrorRichText::scanLinkExtension($this->text, 'oembed');
foreach ($matches as $match) {
if(isset($match[3])) {
UrlOembed::preload($match[3]);
}
}
}
/**
* Parses mention link extensions in the form of [<url>](mention:<guid> "<link>") and creates mentionings records.
*/
public function parseMentioning()
{
$result = [];
$matches = ProsemirrorRichText::scanLinkExtension($this->text, 'mention');
foreach ($matches as $match) {
if(isset($match[3])) {
$result = array_merge($result, Mentioning::mention($match[3], $this->record));
}
}
return $result;
}
}

View File

@ -0,0 +1,82 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\widgets\richtext;
use Yii;
use yii\helpers\ArrayHelper;
/**
* This class together with the [[RichTextField]] class act as wrapper around the globally configured [[AbstractRichText]]
* implementation and related [[AbstractRichTextEditor]] as well as [[AbstractRichTextProcessor]] by means of the configuration
* parameter `richText`.
*
* This class should be used in favor of the actual [[AbstractRichText]] implementations in order to be able to
* replace the default rich text on demand.
*
* A rich text editor field can be added to a form as follows:
*
* ```php
* $form->field($model, 'richTextField')->widget(RichTextField::class);
* ```
*
* This will render a rich text editor field related to the globally configured rich text implementation.
* After submitting and saving the model record holding the rich text, the [[postProcess()]] function should be called
* usually within the `afterSave()` function of the content record:
*
* ```php
* RichText::postProcess($this->richTextField, $this);
* ```
* This will parse the rich text for features which require post-processing as _Mentionings_ or _Oembed_.
*
* The following line then can be used in a view to render the rich text output:
*
* ```php
* RichText::output($model->richTextField);
* ```
*
* A preview of the rich text can be rendered as follows:
*
* ```php
* RichText::widget(['text' => $model->richTextField, 'minimal' => true, 'maxLength' => 60])
* ```
*
* @author Julian Harrer <julian.harrer@humhub.com>
* @since 1.2
*/
abstract class RichText extends AbstractRichText
{
/**
* Renders the rich text output by determining the configured rich text class.
*/
public static function widget($config = [])
{
if(!isset($config['class'])) {
$config = ArrayHelper::merge(Yii::$app->params['richText'], $config);
}
return call_user_func($config['class'].'::'.'widget', $config);
}
/**
* @return string
*/
public static function getProcessorClass()
{
return call_user_func(Yii::$app->params['richText']['class'].'::getProcessorClass');
}
/**
* @return string
*/
public static function getEditorClass()
{
return call_user_func(Yii::$app->params['richText']['class'].'::getEditorClass');
}
}

View File

@ -0,0 +1,121 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\widgets\richtext;
use humhub\models\UrlOembed;
use humhub\modules\space\models\Space;
use humhub\modules\user\models\User;
use yii\helpers\Html;
class RichTextCompatibilityParser
{
public static function parse($text)
{
$text = static::translateEmojis($text);
$text = static::translateLinks($text);
return static::translateMentionings($text);
}
/**
* Replace emojis from text to img tag
*
* @param string $text Contains the complete message
* @param string $show show smilies or remove it (for activities and notifications)
*/
public static function translateEmojis($text)
{
$emojis = [
"Relaxed", "Yum", "Relieved", "Hearteyes", "Cool", "Smirk",
"KissingClosedEyes", "StuckOutTongue", "StuckOutTongueWinkingEye", "StuckOutTongueClosedEyes", "Disappointed", "Frown",
"ColdSweat", "TiredFace", "Grin", "Sob", "Gasp", "Gasp2",
"Laughing", "Joy", "Sweet", "Satisfied", "Innocent", "Wink",
"Ambivalent", "Expressionless", "Sad", "Slant", "Worried", "Kissing",
"KissingHeart", "Angry", "Naughty", "Furious", "Cry", "OpenMouth",
"Fearful", "Confused", "Weary", "Scream", "Astonished", "Flushed",
"Sleeping", "NoMouth", "Mask", "Worried", "Smile", "Muscle",
"Facepunch", "ThumbsUp", "ThumbsDown", "Beers", "Cocktail", "Burger",
"PoultryLeg", "Party", "Cake", "Sun", "Fire", "Heart"
];
$emojiMapping = [
'Cool' => 'sunglasses',
'Hearteyes' => 'heart_eyes',
'KissingClosedEyes' => 'kissing_closed_eyes',
'StuckOutTongue' => 'stuck_out_tongue',
'StuckOutTongueWinkingEye' => 'stuck_out_tongue_winking_eye',
'StuckOutTongueClosedEyes' => 'stuck_out_tongue_closed_eyes',
'Frown' => 'frowning_face',
'ColdSweat' => 'cold_sweat',
'TiredFace' => 'tired_face',
'Gasp' => 'open_mouth',
'Gasp2' => 'astonished',
'Sweet' => 'grin',
'Ambivalent' => 'neutral_face',
'Sad' => 'disappointed',
'Slant' => 'confused',
'KissingHeart' => 'kissing_heart',
'Naughty' => 'rage',
'Furious' => 'angry',
'OpenMouth' => 'open_mouth',
'NoMouth' => 'no_mouth',
'ThumbsUp' => 'thumbsup',
'ThumbsDown' => 'thumbsdown',
'Burger' => 'hamburger',
'PoultryLeg' => 'poultry_leg',
'Party' => 'beers',
'sun' => 'sun_with_face'
];
return preg_replace_callback('@;(\w*?);@', function ($hit) use (&$emojis, &$emojiMapping) {
if (array_key_exists($hit[1], $emojiMapping)) {
return ':' . $emojiMapping[$hit[1]] . ':';
} else if (in_array($hit[1], $emojis)) {
return ':' . strtolower($hit[1]) . ':';
}
return $hit[0];
}, $text);
}
/**
* Translates links to either default markdown links or oembeds.
*
* @param $text
* @return mixed
*/
private static function translateLinks($text)
{
return preg_replace_callback('/(?<=^|\s)(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s\]\)\\"\'\<]{2,})(?=$|\s)/', function ($hit) {
$url = $hit[0];
return UrlOembed::GetOEmbed($url) ? '[' . $url . '](oembed:' . $url . ')' : '[' . $url . '](' . $url . ')';
}, $text);
}
/**
* Translates old mentionings to new markdown based mentionings.
*
* @param $text
* @return mixed
*/
private static function translateMentionings($text)
{
return preg_replace_callback('@\@\-([us])([\w\-]*?)($|[\.,:;\'"!\?\s])@', function ($hit) {
if ($hit[1] == 'u') {
$container = User::findOne(['guid' => $hit[2]]);
$name = ($container) ? $container->getDisplayName() : 'unknown';
} else {
$container = Space::findOne(['guid' => $hit[2]]);
$name = ($container) ? $container->name : 'unknown';
}
return '[' . Html::encode($name) . '](mention:' . $hit[2] . ' "' . $container->getUrl() . '")';
}, $text);
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\widgets\richtext;
use Yii;
/**
* The RichTextField widget will render a rich text input element.
*
* This class acts as wrapper around the actual [[AbstractRichTextEditor]] implementation, which can either be set
* by the widget configuration `class` or globally by the `richText` configuration parameter.
*
* The following line adds a rich text input field rendered by the globally configured rich text to a form:
*
* ```php
* $form->field($model, 'richTextField')->widget(RichTextField::class);
* ```
*
* @author Julian Harrer <julian.harrer@humhub.com>
* @see RichText for more information about the usage of rich texts
* @author buddha
*/
class RichTextField extends AbstractRichTextEditor
{
/**
* @inheritdoc
*/
public static function widget($config = [])
{
if(!isset($config['class'])) {
$richtextClass = Yii::$app->params['richText']['class'];
return call_user_func($richtextClass.'::editorWidget', $config);
}
return parent::widget($config);
}
}

View File

@ -38,10 +38,14 @@ humhub.module('file', function (module, require, $) {
var that = this;
this.on('upload', function () {
that.$.trigger('click');
that.run();
});
};
Upload.prototype.run = function () {
this.$.trigger('click');
};
Upload.prototype.validate = function () {
return this.$.is('[type="file"]');
};
@ -234,7 +238,7 @@ humhub.module('file', function (module, require, $) {
}
//deprecated event use uploadEnd
this.fire('humhub:file:uploadEnd', [response]);;
this.fire('humhub:file:uploadEnd', [response]);
this.fire('uploadEnd', [response]);
};

View File

@ -8,7 +8,9 @@
namespace humhub\modules\post\controllers;
use humhub\modules\content\widgets\WallCreateContentForm;
use humhub\modules\post\models\Post;
use humhub\modules\post\permissions\CreatePost;
use Yii;
/**
@ -21,7 +23,7 @@ class PostController extends \humhub\modules\content\components\ContentContainer
public function actionPost()
{
// Check createPost Permission
if (!$this->contentContainer->getPermissionManager()->can(new \humhub\modules\post\permissions\CreatePost())) {
if (!$this->contentContainer->getPermissionManager()->can(new CreatePost())) {
return [];
}
@ -36,7 +38,7 @@ class PostController extends \humhub\modules\content\components\ContentContainer
}
*/
return \humhub\modules\content\widgets\WallCreateContentForm::create($post, $this->contentContainer);
return WallCreateContentForm::create($post, $this->contentContainer);
}
public function actionEdit()

View File

@ -8,6 +8,7 @@
namespace humhub\modules\post\models;
use humhub\modules\content\widgets\richtext\RichText;
use Yii;
use humhub\modules\content\components\ContentActiveRecord;
use humhub\modules\search\interfaces\Searchable;
@ -58,9 +59,6 @@ class Post extends ContentActiveRecord implements Searchable
*/
public function beforeSave($insert)
{
// Prebuild Previews for URLs in Message
\humhub\models\UrlOembed::preload($this->message);
// Check if Post Contains an Url
if (preg_match('/http(.*?)(\s|$)/i', $this->message)) {
// Set Filter Flag
@ -77,10 +75,7 @@ class Post extends ContentActiveRecord implements Searchable
{
parent::afterSave($insert, $changedAttributes);
// Handle mentioned users
\humhub\modules\user\models\Mentioning::parse($this, $this->message);
RichText::postProcess($this->message, $this);
return true;
}

View File

@ -1,52 +1,51 @@
<?php
use humhub\compat\CActiveForm;
use humhub\modules\file\widgets\FilePreview;
use humhub\modules\file\widgets\UploadButton;
use humhub\modules\file\widgets\UploadProgress;
use humhub\widgets\Button;
use humhub\widgets\RichtextField;
use humhub\modules\content\widgets\richtext\RichtextField;
use yii\bootstrap\ActiveForm;
/* @var $post \humhub\modules\post\models\Post */
/* @var $submitUrl string */
$submitUrl = $post->content->container->createUrl('/post/post/edit', ['id' => $post->id]);
?>
<div class="content content_edit" id="post_edit_<?php echo $post->id; ?>">
<?php $form = CActiveForm::begin(['id' => 'post-edit-form_' . $post->id]); ?>
<div class="content content_edit" id="post_edit_<?= $post->id; ?>">
<?php $form = ActiveForm::begin(['id' => 'post-edit-form_' . $post->id]); ?>
<!-- create contenteditable div for HEditorWidget to place the data -->
<?= RichtextField::widget([
'id' => 'post_input_'. $post->id,
'placeholder' => Yii::t('PostModule.views_edit', 'Edit your post...'),
'model' => $post,
'attribute' => 'message'
]); ?>
<div class="post-richtext-input-group">
<?= $form->field($post, 'message')->widget(RichtextField::class, [
'id' => 'post_input_'. $post->id,
'placeholder' => Yii::t('PostModule.views_edit', 'Edit your post...')
])->label(false) ?>
<div class="comment-buttons">
<div class="comment-buttons">
<?=
\humhub\modules\file\widgets\UploadButton::widget([
'id' => 'post_upload_' . $post->id,
<?= UploadButton::widget([
'id' => 'post_upload_' . $post->id,
'model' => $post,
'dropZone' => '#post_edit_' . $post->id . ':parent',
'preview' => '#post_upload_preview_' . $post->id,
'progress' => '#post_upload_progress_' . $post->id,
'max' => Yii::$app->getModule('content')->maxAttachedFiles
]) ?>
<?= Button::defaultType(Yii::t('base', 'Save'))->action('editSubmit', $submitUrl)->submit()->cssClass(' btn-comment-submit')->sm(); ?>
</div>
</div>
<?= UploadProgress::widget(['id' => 'post_upload_progress_'.$post->id])?>
<?= FilePreview::widget([
'id' => 'post_upload_preview_' . $post->id,
'options' => ['style' => 'margin-top:10px'],
'model' => $post,
'dropZone' => '#post_edit_' . $post->id . ':parent',
'preview' => '#post_upload_preview_' . $post->id,
'progress' => '#post_upload_progress_' . $post->id,
'max' => Yii::$app->getModule('content')->maxAttachedFiles
])
?>
'edit' => true
]) ?>
<!-- editSubmit action of surrounding StreamEntry component -->
<?= Button::defaultType(Yii::t('base', 'Save'))->action('editSubmit', $submitUrl)->submit()->cssClass(' btn-comment-submit')->sm(); ?>
</div>
<div id="post_upload_progress_<?= $post->id ?>" style="display:none;margin:10px 0px;"></div>
<?=
FilePreview::widget([
'id' => 'post_upload_preview_' . $post->id,
'options' => ['style' => 'margin-top:10px'],
'model' => $post,
'edit' => true
])
?>
<?php CActiveForm::end(); ?>
<?php ActiveForm::end(); ?>
</div>

View File

@ -1,7 +1,13 @@
<?= humhub\widgets\RichtextField::widget([
'id' => 'contentForm_message',
'placeholder' => Yii::t("PostModule.widgets_views_postForm", "What's on your mind?"),
<?php
use humhub\modules\content\widgets\richtext\RichTextField;
?>
<?= RichTextField::widget([
'id' => 'contentForm_message',
'placeholder' => Yii::t("PostModule.widgets_views_postForm", "What's on your mind?"),
'name' => 'message',
'disabled' => (property_exists(Yii::$app->controller, 'contentContainer') && Yii::$app->controller->contentContainer->isArchived()),
'disabledText' => Yii::t("PostModule.widgets_views_postForm", "This space is archived."),
]);?>
]); ?>

View File

@ -1,5 +1,11 @@
<?php
use humhub\modules\content\widgets\richtext\RichText;
/* @var $post \humhub\modules\post\models\Post */
?>
<div data-ui-widget="post.Post" data-state="collapsed" data-ui-init id="post-content-<?= $post->id; ?>" style="overflow: hidden; margin-bottom: 5px;">
<div data-ui-markdown data-ui-show-more style="overflow: hidden;">
<?= humhub\widgets\RichText::widget(['text' => $post->message, 'record' => $post, 'markdown' => true]) ?>
<div data-ui-markdown data-ui-show-more>
<?= RichText::output($post->message) ?>
</div>
</div>

View File

@ -13,6 +13,7 @@ use humhub\modules\content\components\ContentActiveRecord;
use humhub\modules\content\components\ContentAddonActiveRecord;
use humhub\modules\user\notifications\Mentioned;
use yii\base\Exception;
use yii\base\InvalidParamException;
/**
* This is the model class for table "user_mentioning".
@ -97,31 +98,14 @@ class Mentioning extends ActiveRecord
*
* @return User[] Mentioned users
* @throws Exception
* @deprecated since 1.3 use [[\humhub\modules\content\widgets\richtext\RichText::processText()]] instead
*/
public static function parse($record, $text)
{
$result = [];
if ($record instanceof ContentActiveRecord || $record instanceof ContentAddonActiveRecord) {
preg_replace_callback('@\@\-u([\w\-]*?)($|\s|\.)@', function ($hit) use (&$record, &$result) {
$user = User::findOne(['guid' => $hit[1]]);
if ($user !== null) {
// Check the user was already mentioned (e.g. edit)
$mention = self::findOne([
'object_model' => get_class($record),
'object_id' => $record->getPrimaryKey(),
'user_id' => $user->id,
]);
if ($mention === null) {
$mention = new Mentioning(['user_id' => $user->id]);
$mention->setPolymorphicRelation($record);
$mention->save();
$result[] = $user;
// Mentioned users automatically follows the content
$record->content->getPolymorphicRelation()->follow($user->id);
}
}
$result = array_merge($result, static::mention($hit[1], $record));
}, $text);
} else {
throw new Exception("Mentioning can only used in HActiveRecordContent or HActiveRecordContentAddon objects!");
@ -129,6 +113,56 @@ class Mentioning extends ActiveRecord
return $result;
}
/**
* Creates the mentioning for the given `$guids`.
* This function will skip `$guids` which are already mentioned in the given `$record`.
*
* @param string|string[] $guids
* @param ContentActiveRecord|ContentAddonActiveRecord $record
* @return array new mentionings for the given $record
* @throws InvalidParamException if an invalid $record is provided
* @since 1.3
*/
public static function mention($guids, $record)
{
if (!($record instanceof ContentActiveRecord || $record instanceof ContentAddonActiveRecord)) {
throw new InvalidParamException("Mentioning can only used in HActiveRecordContent or HActiveRecordContentAddon objects!");
}
if(is_string($guids)) {
$guids = [$guids];
}
$result = [];
foreach ($guids as $guid) {
$user = User::findOne(['guid' => $guid]);
if(!$user) {
continue;
}
// Check the user was already mentioned (e.g. edit)
$mention = static::findOne([
'object_model' => get_class($record),
'object_id' => $record->getPrimaryKey(),
'user_id' => $user->id,
]);
if (!$mention) {
$mention = new static(['user_id' => $user->id]);
$mention->setPolymorphicRelation($record);
$mention->save();
$result[] = $user;
// Mentioned users automatically follows the content
$record->content->getPolymorphicRelation()->follow($user->id);
}
}
return $result;
}
/**
* Related user
*

View File

@ -7,9 +7,14 @@ humhub.module('user', function(module, require, $) {
var guid = function() {
return module.config.guid;
};
var getLocale = function() {
return module.config.locale;
};
module.export({
isGuest: isGuest,
guid: guid
guid: guid,
getLocale: getLocale
});
});

View File

@ -1,7 +1,7 @@
<?php
use humhub\modules\content\widgets\richtext\RichText;
use yii\helpers\Html;
use humhub\widgets\RichText;
use humhub\modules\user\models\fieldtype\MarkdownEditor;
use humhub\widgets\MarkdownView;
@ -13,13 +13,13 @@ $categories = $user->profile->getProfileFieldCategories();
?>
<div class="panel panel-default">
<div
class="panel-heading"><?php echo Yii::t('UserModule.views_profile_about', '<strong>About</strong> this user'); ?></div>
class="panel-heading"><?= Yii::t('UserModule.views_profile_about', '<strong>About</strong> this user'); ?></div>
<div class="panel-body">
<?php $firstClass = "active"; ?>
<ul id="tabs" class="nav nav-tabs" data-tabs="tabs">
<?php foreach ($categories as $category): ?>
<li class="<?php echo $firstClass; ?>">
<a href="#profile-category-<?php echo $category->id; ?>" data-toggle="tab"><?php echo Html::encode(Yii::t($category->getTranslationCategory(), $category->title)); ?></a>
<li class="<?= $firstClass; ?>">
<a href="#profile-category-<?= $category->id; ?>" data-toggle="tab"><?= Html::encode(Yii::t($category->getTranslationCategory(), $category->title)); ?></a>
</li>
<?php
$firstClass = "";
@ -32,16 +32,16 @@ $categories = $user->profile->getProfileFieldCategories();
<div class="tab-pane <?php
echo $firstClass;
$firstClass = "";
?>" id="profile-category-<?php echo $category->id; ?>">
?>" id="profile-category-<?= $category->id; ?>">
<form class="form-horizontal" role="form">
<?php foreach ($user->profile->getProfileFields($category) as $field) : ?>
<div class="form-group">
<label class="col-sm-3 control-label">
<?php echo Html::encode(Yii::t($field->getTranslationCategory(), $field->title)); ?>
<?= Html::encode(Yii::t($field->getTranslationCategory(), $field->title)); ?>
</label>
<?php if (strtolower($field->title) == 'about'): ?>
<div class="col-sm-9">
<p class="form-control-static"><?php echo RichText::widget(['text' => $field->getUserValue($user, true)]); ?></p>
<p class="form-control-static"><?= RichText::output($field->getUserValue($user, true)); ?></p>
</div>
<?php else: ?>
<div class="col-sm-9">
@ -50,7 +50,7 @@ $categories = $user->profile->getProfileFieldCategories();
<?= MarkdownView::widget(['markdown' => $field->getUserValue($user, false)]); ?>
</p>
<?php else: ?>
<p class="form-control-static"><?php echo $field->getUserValue($user, false); ?></p>
<p class="form-control-static"><?= $field->getUserValue($user, false); ?></p>
<?php endif; ?>
</div>
<?php endif; ?>

View File

@ -30,7 +30,7 @@ class CoreJsConfig extends Widget
$userConfig = ['isGuest' => true];
}
$liveModule = Yii::$app->getModule('live');
$userConfig['locale'] = Yii::$app->formatter->locale;
$this->getView()->registerJsConfig(
[
@ -78,6 +78,72 @@ class CoreJsConfig extends Widget
'info.loading' => Yii::t('base', 'Loading...'),
]
],
'ui.richtext.prosemirror' => [
'emoji' => [
'twemoji' => [
'base' => Yii::getAlias('@web-static/img/twemoji/'),
'size' => '72x72',
]
],
'oembed' => [
'max' => Yii::$app->getModule('content')->maxOembeds
],
'mention' => [
'minInput' => 2,
'minInputText' => Yii::t('base', 'Please type at least {count} characters', ['count' => 2])
],
'text' => [
"Wrap in block quote" => Yii::t('ContentModule.richtexteditor', 'Wrap in block quote'),
"Wrap in bullet list" => Yii::t('ContentModule.richtexteditor', "Wrap in bullet list"),
"Toggle code font" => Yii::t('ContentModule.richtexteditor', "Toggle code font"),
"Change to code block" => Yii::t('ContentModule.richtexteditor', "Change to code block"),
"Code" => Yii::t('ContentModule.richtexteditor', "Code"),
"Toggle emphasis" => Yii::t('ContentModule.richtexteditor', "Toggle emphasis"),
"Change to heading" => Yii::t('ContentModule.richtexteditor', "Change to heading"),
"Insert horizontal rule" => Yii::t('ContentModule.richtexteditor', "Insert horizontal rule"),
"Horizontal rule" => Yii::t('ContentModule.richtexteditor', "Horizontal rule"),
"Insert image" => Yii::t('ContentModule.richtexteditor', "Insert image"),
"Image" => Yii::t('ContentModule.richtexteditor', "Image"),
"Location" => Yii::t('ContentModule.richtexteditor', "Location"),
"Title" => Yii::t('ContentModule.richtexteditor', "Title"),
"Width" => Yii::t('ContentModule.richtexteditor', "Width"),
"Height" => Yii::t('ContentModule.richtexteditor', "Height"),
"Add or remove link" => Yii::t('ContentModule.richtexteditor', "Add or remove link"),
"Create a link" => Yii::t('ContentModule.richtexteditor', "Create a link"),
"Link target" => Yii::t('ContentModule.richtexteditor', "Link target"),
"Wrap in ordered list" => Yii::t('ContentModule.richtexteditor', "Wrap in ordered list"),
"Change to paragraph" => Yii::t('ContentModule.richtexteditor', "Change to paragraph"),
"Paragraph" => Yii::t('ContentModule.richtexteditor', "Paragraph"),
"Toggle strikethrough" => Yii::t('ContentModule.richtexteditor', "Toggle strikethrough"),
"Toggle strong style" => Yii::t('ContentModule.richtexteditor', "Toggle strong style"),
"Create table" => Yii::t('ContentModule.richtexteditor', "Create table"),
"Delete table" => Yii::t('ContentModule.richtexteditor', "Delete table"),
"Insert table" => Yii::t('ContentModule.richtexteditor', "Insert table"),
"Rows" => Yii::t('ContentModule.richtexteditor', "Rows"),
"Columns" => Yii::t('ContentModule.richtexteditor', "Columns"),
"Insert column before" => Yii::t('ContentModule.richtexteditor', "Insert column before"),
"Insert column after" => Yii::t('ContentModule.richtexteditor', "Insert column after"),
"Delete column" => Yii::t('ContentModule.richtexteditor', "Delete column"),
"Insert row before" => Yii::t('ContentModule.richtexteditor', "Insert row before"),
"Insert row after" => Yii::t('ContentModule.richtexteditor', "Insert row after"),
"Delete row" => Yii::t('ContentModule.richtexteditor', "Delete row"),
"Upload and include a File" => Yii::t('ContentModule.richtexteditor', "Upload and include a File"),
"Upload File" => Yii::t('ContentModule.richtexteditor', "Upload File"),
"Insert" => Yii::t('ContentModule.richtexteditor', "Insert"),
"Type" => Yii::t('ContentModule.richtexteditor', "Type"),
"people" => Yii::t('ContentModule.richtexteditor', "People"),
"animals_and_nature" => Yii::t('ContentModule.richtexteditor', "Animals & Nature"),
"food_and_drink" => Yii::t('ContentModule.richtexteditor', "Food & Drink"),
"activity" => Yii::t('ContentModule.richtexteditor', "Activity"),
"travel_and_places" => Yii::t('ContentModule.richtexteditor', "Travel & Places"),
"objects" => Yii::t('ContentModule.richtexteditor', "Objects"),
"symbols" => Yii::t('ContentModule.richtexteditor', "Symbols"),
"flags" => Yii::t('ContentModule.richtexteditor', "Flags"),
]
],
'oembed' => [
'loadUrl' => Url::to(['/oembed'])
],
'ui.markdown', [
'text' => [
'Bold' => Yii::t('widgets_views_markdownEditor', 'Bold'),

View File

@ -85,6 +85,19 @@ class InputWidget extends JsWidget
parent::init();
}
/**
* @return string the field value either by extracting from model or if no model is given `$this->value`
* @since 1.3
*/
protected function getValue()
{
if($this->hasModel()) {
return Html::getAttributeValue($this->model, $this->attribute);
}
return $this->value;
}
/**
* @return bool whether this widget is associated with a data model.
*/

View File

@ -4,6 +4,7 @@ namespace humhub\widgets;
use humhub\components\Widget;
use humhub\libs\Html;
use yii\helpers\ArrayHelper;
/**
* Description of JsWidget
@ -23,7 +24,7 @@ class JsWidget extends Widget
/**
* Js Widget namespace
* @var type
* @var string
*/
public $jsWidget;
@ -31,13 +32,13 @@ class JsWidget extends Widget
* Used to overwrite select input field attributes. This array can be used for overwriting
* texts, or other picker settings.
*
* @var string
* @var []
*/
public $options = [];
/**
* Event action handler.
* @var type
* @var []
*/
public $events = [];
@ -49,7 +50,7 @@ class JsWidget extends Widget
/**
* Used to hide/show the actual input element.
* @var type
* @var boolean
*/
public $visible = true;
@ -77,11 +78,11 @@ class JsWidget extends Widget
* This will render a widget html element specified by $container and $content and the given $options/$event attributes.
* This function should be overwritten for widgets with a more complex rendering.
*
* @return type
* @return string
*/
public function run()
{
return \yii\helpers\Html::tag($this->container, $this->content, $this->getOptions());
return Html::tag($this->container, $this->content, $this->getOptions());
}
/**
@ -99,7 +100,7 @@ class JsWidget extends Widget
$this->setDefaultOptions();
$result = \yii\helpers\ArrayHelper::merge($attributes, $this->options);
$result = ArrayHelper::merge($attributes, $this->options);
if (!$this->visible) {
Html::addCssStyle($result, 'display:none');
@ -140,8 +141,8 @@ class JsWidget extends Widget
*
* Note that the id is automatically included within the <code>getOptions()<code> function.
*
* @param type $autoGenerate
* @return type
* @param boolean $autoGenerate
* @return string
*/
public function getId($autoGenerate = true)
{
@ -158,7 +159,7 @@ class JsWidget extends Widget
*
* The data-* attributes should be inserted to the widgets root element.
*
* @return type
* @return []
*/
protected function getData()
{
@ -167,7 +168,7 @@ class JsWidget extends Widget
/**
* Returns all html attributes for used by this widget and will normally inserted in the widgets root html element.
* @return type
* @return []
*/
protected function getAttributes()
{

View File

@ -28,6 +28,7 @@ use humhub\components\Widget;
* @package humhub.widgets
* @since 0.5
* @author Andreas Strobel
* @deprecated since 1.2
*/
class RichTextEditor extends Widget
{

View File

@ -1,177 +0,0 @@
<?php
namespace humhub\widgets;
use Yii;
use yii\helpers\Html;
use \yii\helpers\Url;
use humhub\widgets\RichText;
/**
* @package humhub.modules_core.user.widgets
* @since 1.2
* @author buddha
*/
class RichtextField extends InputWidget
{
/**
* Defines the javascript picker implementation.
*
* @var string
*/
public $jsWidget = 'ui.richtext.Richtext';
/**
* Minimum character input before triggering search query.
*
* @var integer
*/
public $minInput = 3;
/**
* Can be used to overwrite the default placeholder.
*
* @var string
*/
public $placeholder;
/**
* The url used for the default @ metioning.
* If there is no $searchUrl is given, the $searchRoute will be used instead.
*
* @var string
*/
public $mentioningUrl;
/**
* Route used for the default @ mentioning. This will only be used if
* not $searchUrl is given.
*
* @var string
*/
protected $mentioningRoute = "/search/search/mentioning";
/**
* Richtext features supported for within this feature.
* By default all features will be included.
*
* @var array
*/
public $includes = [];
/**
* Richtext features not supported in this richtext feature.
*
* @var array
*/
public $excludes = [];
/**
* If set to true the picker will be focused automatically.
*
* @var boolean
*/
public $focus = false;
/**
* Disables the input field.
* @var boolean
*/
public $disabled = false;
/**
* Will be used as userfeedback, why this richtext is disabled.
*
* @var string
*/
public $disabledText = false;
/**
* @inheritdoc
*/
public $init = true;
/**
* @inheritdoc
*/
public $visible = true;
/**
* @var boolean defines if the default label should be rendered. This is only available if $form is given.
*/
public $label = false;
/**
* @inhertidoc
*/
public function run()
{
$inputOptions = $this->getAttributes();
$inputOptions['id'] = $this->getId(true) . '_input';
$inputOptions['style'] = 'display:none;color';
$inputOptions['title'] = $this->placeholder;
unset($inputOptions['contenteditable']);
$modelAttribute = $this->attribute;
if ($this->form != null) {
$input = $this->form->field($this->model, $this->attribute)->textarea($inputOptions)->label(false);
$richText = Html::tag('div', RichText::widget(['text' => $this->model->$modelAttribute, 'edit' => true]), $this->getOptions());
$richText = $this->getLabel() . $richText;
} else if ($this->model != null) {
$input = Html::activeTextarea($this->model, $this->attribute, $inputOptions);
$richText = Html::tag('div', RichText::widget(['text' => $this->model->$modelAttribute, 'edit' => true]), $this->getOptions());
$richText = $this->getLabel() . $richText;
} else {
$input = Html::textarea(((!$this->name) ? 'richtext' : $this->name), $this->value, $inputOptions);
$richText = Html::tag('div', RichText::widget(['text' => $this->value, 'edit' => true]), $this->getOptions());
$richText = $this->getLabel() . $richText;
}
return $input . $richText;
}
public function getLabel()
{
if(!$this->label) {
return "";
}
if ($this->label === true && $this->model != null) {
return Html::activeLabel($this->model, $this->attribute, ['class' => 'control-label']);
} else {
return $this->label;
}
}
public function getData()
{
$result = [
'excludes' => $this->excludes,
'includes' => $this->includes,
'mentioning-url' => $this->getMentioningUrl(),
'placeholder' => $this->placeholder,
];
if ($this->disabled) {
$result['disabled'] = true;
$result['disabled-text'] = $this->disabledText;
}
return $result;
}
public function getAttributes()
{
return [
'class' => "atwho-input form-control humhub-ui-richtext",
'contenteditable' => "true",
];
}
public function getMentioningUrl()
{
return ($this->mentioningUrl) ? $this->mentioningUrl : Url::to([$this->mentioningRoute]);
}
}

View File

@ -9,7 +9,7 @@
namespace humhub\widgets\mails;
use Yii;
use humhub\widgets\RichText;
use humhub\modules\content\widgets\richtext\RichText;
use humhub\components\rendering\ViewPathRenderer;
use humhub\components\rendering\Viewable;
use humhub\modules\content\interfaces\ContentOwner;
@ -54,6 +54,8 @@ class MailContentEntry extends \yii\base\Widget
*/
public function run()
{
$content = null;
if (is_string($this->content)) {
$content = $this->content;
} else if ($this->content instanceof Viewable) {
@ -63,14 +65,13 @@ class MailContentEntry extends \yii\base\Widget
} catch (\yii\base\ViewNotFoundException $e) {
Yii::error($e);
}
} else if ($this->content instanceof \humhub\modules\content\interfaces\ContentOwner) {
$content = RichText::widget(['text' => $this->content->getContentDescription(), 'minimal' => true]);
} else if ($this->content instanceof ContentOwner) {
$content = RichText::preview($this->content->getContentDescription());
if(!$this->originator) {
$this->originator = $this->content->content->createdBy;
}
}
return $this->render('mailContentEntry', [
'originator' => $this->originator,
'content' => $content,
@ -79,7 +80,5 @@ class MailContentEntry extends \yii\base\Widget
'isComment' => $this->isComment,
]);
}
}
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 995 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 B

Some files were not shown because too many files have changed in this diff Show More