Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/craftcms/ckeditor into 4.0
Browse files Browse the repository at this point in the history
# Conflicts:
#	CHANGELOG.md
#	src/Field.php
#	src/web/assets/ckeconfig/dist/ckeconfig.js
#	src/web/assets/ckeconfig/dist/ckeconfig.js.map
#	src/web/assets/ckeditor/dist/ckeditor5-craftcms.js
#	src/web/assets/ckeditor/dist/ckeditor5-craftcms.js.map
  • Loading branch information
brandonkelly committed Mar 6, 2024
2 parents 6a41097 + 8a48c7c commit 849e7d8
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 32 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Release Notes for CKEditor for Craft CMS

## Unreleased

- CKEditor config edit pages now warn when switching the Config Options setting from JavaScript to JSON if the JavaScript code contains any functions. ([#152](https://github.com/craftcms/ckeditor/issues/152), [#180](https://github.com/craftcms/ckeditor/pull/180))
- Fixed a bug where the “Link to an asset” option was showing up when there weren’t any available volumes with URLs. ([#179](https://github.com/craftcms/ckeditor/issues/179))

## 4.0.0-beta.7 - 2024-02-21

- Added support for creating anchor links. ([#169](https://github.com/craftcms/ckeditor/discussions/169))
Expand Down
40 changes: 29 additions & 11 deletions src/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,8 @@ protected function inputHtml(mixed $value, ?ElementInterface $element, $inline):
'imageTextAlternative',
],
],
'assetSources' => $this->_assetSources(),
'assetSelectionCriteria' => $this->_assetSelectionCriteria(),
'linkOptions' => $this->_linkOptions($element),
'table' => [
'contentToolbar' => [
Expand Down Expand Up @@ -1264,9 +1266,9 @@ private function _linkOptions(?ElementInterface $element): array
{
$linkOptions = [];

$sectionSources = $this->_sectionSources($element);
$sectionSources = $this->_entrySources($element);
$categorySources = $this->_categorySources($element);
$volumeSources = $this->_volumeSources();
$volumeSources = $this->_assetSources(true);

if (!empty($sectionSources)) {
$linkOptions[] = [
Expand All @@ -1289,16 +1291,12 @@ private function _linkOptions(?ElementInterface $element): array
}

if (!empty($volumeSources)) {
$criteria = [];
if ($this->showUnpermittedFiles) {
$criteria['uploaderId'] = null;
}
$linkOptions[] = [
'label' => Craft::t('ckeditor', 'Link to an asset'),
'elementType' => Asset::class,
'refHandle' => Asset::refHandle(),
'sources' => $volumeSources,
'criteria' => $criteria,
'criteria' => $this->_assetSelectionCriteria(),
];
}

Expand All @@ -1322,13 +1320,13 @@ private function _linkOptions(?ElementInterface $element): array
}

/**
* Returns the available section sources.
* Returns the available entry sources.
*
* @param ElementInterface|null $element The element the field is associated with, if there is one
* @param bool $showSingles Whether to include Singles in the available sources
* @return array
*/
private function _sectionSources(?ElementInterface $element, bool $showSingles = false): array
private function _entrySources(?ElementInterface $element, bool $showSingles = false): array
{
$sources = [];
$sections = Craft::$app->getEntries()->getAllSections();
Expand Down Expand Up @@ -1396,11 +1394,12 @@ private function _categorySources(?ElementInterface $element): array
}

/**
* Returns the available volume sources.
* Returns the available asset sources.
*
* @param bool $withUrlsOnly Whether to only return volumes that have filesystems that have public URLs
* @return string[]
*/
private function _volumeSources(): array
private function _assetSources(bool $withUrlsOnly = false): array
{
if (!$this->availableVolumes) {
return [];
Expand All @@ -1417,6 +1416,11 @@ private function _volumeSources(): array
$volumes = $volumes->filter(fn(Volume $volume) => $userService->checkPermission("viewAssets:$volume->uid"));
}

if ($withUrlsOnly) {
// only allow volumes that belong to FS that have public URLs
$volumes = $volumes->filter(fn(Volume $volume) => $volume->getFs()->hasUrls);
}

$sources = $volumes
->map(fn(Volume $volume) => "volume:$volume->uid")
->values()
Expand All @@ -1431,6 +1435,20 @@ private function _volumeSources(): array
return $sources;
}

/**
* Returns the asset selection criteria.
*
* @return array
*/
private function _assetSelectionCriteria(): array
{
$criteria = [];
if ($this->showUnpermittedFiles) {
$criteria['uploaderId'] = null;
}
return $criteria;
}

/**
* Returns custom element sources keys for given element type.
*
Expand Down
1 change: 1 addition & 0 deletions src/translations/en/ckeditor.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
'View available settings' => 'View available settings',
'Word Limit' => 'Word Limit',
'You can save custom {name} configs as {ext} files in {path}.' => 'You can save custom {name} configs as {ext} files in {path}.',
'Your JavaScript config contains functions. If you switch to JSON, they will be lost. Would you like to continue?',
'{attribute} isn’t valid JSON.' => '{attribute} isn’t valid JSON.',
'{field} should contain at most {max, number} {max, plural, one{word} other{words}}.' => '{field} should contain at most {max, number} {max, plural, one{word} other{words}}.',
];
2 changes: 1 addition & 1 deletion src/web/assets/ckeconfig/dist/ckeconfig.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/ckeconfig/dist/ckeconfig.js.map

Large diffs are not rendered by default.

70 changes: 67 additions & 3 deletions src/web/assets/ckeconfig/src/ConfigOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,53 @@ export default Garnish.Base.extend({

this.defaults = {};

let lastJsValue = null;

new Craft.Listbox($languagePicker, {
onChange: ($selectedOption) => {
this.language = $selectedOption.data('language');
switch (this.language) {
case 'json':
// get the js value
lastJsValue = this.jsEditor.getModel().getValue();
// check if the js value has any functions in it
if (this.jsContainsFunctions(lastJsValue)) {
// if it does - show the confirmation dialogue
if (
!confirm(
Craft.t(
'ckeditor',
'Your JavaScript config contains functions. If you switch to JSON, they will be lost. Would you like to continue?',
),
)
) {
// if user cancels - go back to the previous option (js)
let listbox = $languagePicker.data('listbox');
listbox.$options.not('[data-language="json"]').trigger('click');
break;
}
}
// if user confirms that they want to proceed, or we don't have functions in the js value,
// go ahead and switch
this.$jsonContainer.removeClass('hidden');
this.$jsContainer.addClass('hidden');
const json = this.js2json(this.jsEditor.getModel().getValue());
const json = this.js2json(lastJsValue);
lastJsValue = null;
this.jsonEditor.getModel().setValue(json || '{\n \n}');
this.jsEditor.getModel().setValue('');
break;
case 'js':
this.$jsonContainer.addClass('hidden');
this.$jsContainer.removeClass('hidden');
const js = this.json2js(this.jsonEditor.getModel().getValue());
let js;
// if we have the last remembered js value, it means we're switching back after cancelled confirmation,
// so let's use it
if (lastJsValue !== null) {
js = lastJsValue;
lastJsValue = null;
} else {
js = this.json2js(this.jsonEditor.getModel().getValue());
}
this.jsEditor.getModel().setValue(js || 'return {\n \n}');
this.jsonEditor.getModel().setValue('');
break;
Expand Down Expand Up @@ -204,6 +236,27 @@ export default Garnish.Base.extend({
this.defaults[setting] = schema.$defs[defName].default;
},

replacer: function (key, value) {
if (typeof value === 'function') {
return '__HAS__FUNCTION__';
}
return value;
},

jsContainsFunctions: function (js) {
let config = this.getValidJsonConfig(js);
if (config === false) {
return true;
}

let json = JSON.stringify(config, this.replacer, 2);
if (json.match(/__HAS__FUNCTION__/)) {
return true;
}

return false;
},

config2json: function (config) {
let json = JSON.stringify(config, null, 2);
if (json === '{}') {
Expand All @@ -212,7 +265,7 @@ export default Garnish.Base.extend({
return json;
},

js2json: function (js) {
getValidJsonConfig: function (js) {
const m = (js || '').match(/return\s*(\{[\w\W]*})/);
if (!m) {
return false;
Expand All @@ -225,6 +278,17 @@ export default Garnish.Base.extend({
// oh well
return false;
}

return config;
},

js2json: function (js) {
let config = this.getValidJsonConfig(js);

if (config === false) {
return false;
}

return this.config2json(config);
},

Expand Down
2 changes: 1 addition & 1 deletion src/web/assets/ckeditor/dist/ckeditor5-craftcms.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/ckeditor/dist/ckeditor5-craftcms.js.map

Large diffs are not rendered by default.

26 changes: 12 additions & 14 deletions src/web/assets/ckeditor/src/image/imageinsert/imageinsertui.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default class CraftImageInsertUI extends ImageInsertUI {

init() {
// Make sure there are linked volumes
if (!this._linkOption) {
if (!this._assetSources) {
console.warn(
'Omitting the "image" CKEditor toolbar button, because there aren’t any permitted volumes.',
);
Expand All @@ -31,11 +31,8 @@ export default class CraftImageInsertUI extends ImageInsertUI {
componentFactory.add('imageInsert', componentCreator);
}

get _linkOption() {
const linkOptions = this.editor.config.get('linkOptions');
return linkOptions.find(
(option) => option.elementType === 'craft\\elements\\Asset',
);
get _assetSources() {
return this.editor.config.get('assetSources');
}

_createToolbarImageButton(locale) {
Expand All @@ -53,16 +50,17 @@ export default class CraftImageInsertUI extends ImageInsertUI {
}

_showImageSelectModal() {
const sources = this._assetSources;
const editor = this.editor;
const config = editor.config;
const linkOption = this._linkOption;

Craft.createElementSelectorModal(linkOption.elementType, {
storageKey: `ckeditor:${this.pluginName}:${linkOption.elementType}`,
sources: linkOption.sources,
criteria: Object.assign({}, linkOption.criteria, {
kind: 'image',
}),
const criteria = Object.assign({}, config.get('assetSelectionCriteria'), {
kind: 'image',
});

Craft.createElementSelectorModal('craft\\elements\\Asset', {
storageKey: `ckeditor:${this.pluginName}:'craft\\elements\\Asset'`,
sources,
criteria,
defaultSiteId: config.get('elementSiteId'),
transforms: config.get('transforms'),
multiSelect: true,
Expand Down

0 comments on commit 849e7d8

Please sign in to comment.