Added ProseMirror rich text editor.
@ -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
1830
package-lock.json
generated
Normal 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
|
||||
*/
|
||||
|
@ -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)
|
||||
|
30
protected/humhub/assets/ProsemirrorEditorAsset.php
Normal 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'];
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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,
|
||||
]
|
||||
];
|
||||
|
48
protected/humhub/controllers/OembedController.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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
|
@ -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`.
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
/* @var $this humhub\components\View */
|
||||
/* @var $streamUrl string */
|
||||
|
||||
\humhub\modules\activity\assets\ActivityAsset::register($this);
|
||||
|
||||
|
@ -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>
|
@ -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)); ?>"
|
@ -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) . '"';
|
||||
?>
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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)
|
||||
|
@ -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>
|
@ -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())); ?>"
|
@ -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'
|
||||
];
|
||||
|
||||
}
|
@ -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
|
||||
];
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
@ -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
|
||||
});
|
||||
});
|
@ -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);
|
||||
}
|
||||
}
|
@ -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]);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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()) . '​</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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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",
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
]);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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]);
|
||||
};
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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>
|
@ -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."),
|
||||
]);?>
|
||||
]); ?>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
});
|
||||
});
|
||||
|
@ -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; ?>
|
||||
|
@ -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'),
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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]);
|
||||
}
|
||||
|
||||
}
|
@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
BIN
static/img/twemoji/72x72/1f004.png
Normal file
After Width: | Height: | Size: 843 B |
BIN
static/img/twemoji/72x72/1f0cf.png
Normal file
After Width: | Height: | Size: 973 B |
BIN
static/img/twemoji/72x72/1f170.png
Normal file
After Width: | Height: | Size: 806 B |
BIN
static/img/twemoji/72x72/1f171.png
Normal file
After Width: | Height: | Size: 706 B |
BIN
static/img/twemoji/72x72/1f17e.png
Normal file
After Width: | Height: | Size: 869 B |
BIN
static/img/twemoji/72x72/1f17f.png
Normal file
After Width: | Height: | Size: 631 B |
BIN
static/img/twemoji/72x72/1f18e.png
Normal file
After Width: | Height: | Size: 973 B |
BIN
static/img/twemoji/72x72/1f191.png
Normal file
After Width: | Height: | Size: 841 B |
BIN
static/img/twemoji/72x72/1f192.png
Normal file
After Width: | Height: | Size: 940 B |
BIN
static/img/twemoji/72x72/1f193.png
Normal file
After Width: | Height: | Size: 839 B |
BIN
static/img/twemoji/72x72/1f194.png
Normal file
After Width: | Height: | Size: 756 B |
BIN
static/img/twemoji/72x72/1f195.png
Normal file
After Width: | Height: | Size: 965 B |
BIN
static/img/twemoji/72x72/1f196.png
Normal file
After Width: | Height: | Size: 978 B |
BIN
static/img/twemoji/72x72/1f197.png
Normal file
After Width: | Height: | Size: 995 B |
BIN
static/img/twemoji/72x72/1f198.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
static/img/twemoji/72x72/1f199.png
Normal file
After Width: | Height: | Size: 827 B |
BIN
static/img/twemoji/72x72/1f19a.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
static/img/twemoji/72x72/1f1e6-1f1e8.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
static/img/twemoji/72x72/1f1e6-1f1e9.png
Normal file
After Width: | Height: | Size: 693 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1ea.png
Normal file
After Width: | Height: | Size: 435 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1eb.png
Normal file
After Width: | Height: | Size: 744 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1ec.png
Normal file
After Width: | Height: | Size: 952 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1ee.png
Normal file
After Width: | Height: | Size: 984 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1f1.png
Normal file
After Width: | Height: | Size: 966 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1f2.png
Normal file
After Width: | Height: | Size: 402 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1f4.png
Normal file
After Width: | Height: | Size: 755 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1f6.png
Normal file
After Width: | Height: | Size: 716 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1f7.png
Normal file
After Width: | Height: | Size: 633 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1f8.png
Normal file
After Width: | Height: | Size: 838 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1f9.png
Normal file
After Width: | Height: | Size: 342 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1fa.png
Normal file
After Width: | Height: | Size: 886 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1fc.png
Normal file
After Width: | Height: | Size: 595 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1fd.png
Normal file
After Width: | Height: | Size: 377 B |
BIN
static/img/twemoji/72x72/1f1e6-1f1ff.png
Normal file
After Width: | Height: | Size: 583 B |
BIN
static/img/twemoji/72x72/1f1e6.png
Normal file
After Width: | Height: | Size: 804 B |
BIN
static/img/twemoji/72x72/1f1e7-1f1e6.png
Normal file
After Width: | Height: | Size: 806 B |
BIN
static/img/twemoji/72x72/1f1e7-1f1e7.png
Normal file
After Width: | Height: | Size: 599 B |
BIN
static/img/twemoji/72x72/1f1e7-1f1e9.png
Normal file
After Width: | Height: | Size: 582 B |
BIN
static/img/twemoji/72x72/1f1e7-1f1ea.png
Normal file
After Width: | Height: | Size: 417 B |
BIN
static/img/twemoji/72x72/1f1e7-1f1eb.png
Normal file
After Width: | Height: | Size: 595 B |
BIN
static/img/twemoji/72x72/1f1e7-1f1ec.png
Normal file
After Width: | Height: | Size: 406 B |
BIN
static/img/twemoji/72x72/1f1e7-1f1ed.png
Normal file
After Width: | Height: | Size: 632 B |