-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #294 from craftcms/feature/pt-1892-image-editor
image editor plugin
- Loading branch information
Showing
10 changed files
with
330 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/** | ||
* @link https://craftcms.com/ | ||
* @copyright Copyright (c) Pixel & Tonic, Inc. | ||
* @license GPL-3.0-or-later | ||
*/ | ||
|
||
import {Plugin} from 'ckeditor5/src/core'; | ||
import ImageEditorEditing from './imageeditor/imageeditorediting'; | ||
import ImageEditorUI from './imageeditor/imageeditorui'; | ||
|
||
export default class ImageEditor extends Plugin { | ||
static get requires() { | ||
return [ImageEditorEditing, ImageEditorUI]; | ||
} | ||
|
||
static get pluginName() { | ||
return 'ImageEditor'; | ||
} | ||
} |
184 changes: 184 additions & 0 deletions
184
src/web/assets/ckeditor/src/image/imageeditor/imageeditorcommand.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
/** | ||
* @link https://craftcms.com/ | ||
* @copyright Copyright (c) Pixel & Tonic, Inc. | ||
* @license GPL-3.0-or-later | ||
*/ | ||
|
||
import {Command} from 'ckeditor5/src/core'; | ||
|
||
/** | ||
* The transform image command. | ||
*/ | ||
export default class ImageEditorCommand extends Command { | ||
refresh() { | ||
const element = this._element(); | ||
const srcInfo = this._srcInfo(element); | ||
this.isEnabled = !!srcInfo; | ||
|
||
// if the command is still enabled - check permissions too | ||
if (this.isEnabled) { | ||
let data = { | ||
assetId: srcInfo.assetId, | ||
}; | ||
|
||
Craft.sendActionRequest('POST', 'ckeditor/ckeditor/image-permissions', { | ||
data, | ||
}).then((response) => { | ||
if (response.data.editable === false) { | ||
this.isEnabled = false; | ||
} | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Returns the selected image element. | ||
*/ | ||
_element() { | ||
const editor = this.editor; | ||
const imageUtils = editor.plugins.get('ImageUtils'); | ||
return imageUtils.getClosestSelectedImageElement( | ||
editor.model.document.selection, | ||
); | ||
} | ||
|
||
/** | ||
* Checks if element has a src attribute and at least an asset id. | ||
* Returns null if not and array containing src, baseSrc, asset id and transform (if used). | ||
* | ||
* @param element | ||
* @returns {{transform: *, src: *, assetId: *, baseSrc: *}|null} | ||
* @private | ||
*/ | ||
_srcInfo(element) { | ||
if (!element || !element.hasAttribute('src')) { | ||
return null; | ||
} | ||
|
||
const src = element.getAttribute('src'); | ||
const match = src.match( | ||
/(.*)#asset:(\d+)(?::transform:([a-zA-Z][a-zA-Z0-9_]*))?/, | ||
); | ||
if (!match) { | ||
return null; | ||
} | ||
|
||
return { | ||
src, | ||
baseSrc: match[1], | ||
assetId: match[2], | ||
transform: match[3], | ||
}; | ||
} | ||
|
||
/** | ||
* Executes the command. | ||
* | ||
* @fires execute | ||
*/ | ||
execute() { | ||
const editor = this.editor; | ||
const model = editor.model; | ||
const element = this._element(); | ||
const srcInfo = this._srcInfo(element); | ||
|
||
if (srcInfo) { | ||
let settings = { | ||
allowSavingAsNew: false, // todo: we might want to change that, but currently we're doing the same functionality as in Redactor | ||
onSave: (data) => { | ||
this._reloadImage(srcInfo.assetId, data); | ||
}, | ||
allowDegreeFractions: Craft.isImagick, | ||
}; | ||
|
||
new Craft.AssetImageEditor(srcInfo.assetId, settings); | ||
} | ||
} | ||
|
||
/** | ||
* Reloads the matching images after save was triggered from the Image Editor. | ||
* | ||
* @param data | ||
*/ | ||
_reloadImage(assetId, data) { | ||
let editor = this.editor; | ||
let model = editor.model; | ||
|
||
// get all images that are Craft Assets | ||
let images = this._getAllImageAssets(); | ||
|
||
// go through them all and get the ones with matching asset id | ||
images.forEach((image) => { | ||
// if it's the image we just edited | ||
if (image.srcInfo.assetId == assetId) { | ||
// if it doesn't have a transform | ||
if (!image.srcInfo.transform) { | ||
// get new src | ||
let newSrc = | ||
image.srcInfo.baseSrc + | ||
'?' + | ||
new Date().getTime() + | ||
'#asset:' + | ||
image.srcInfo.assetId; | ||
|
||
// and replace | ||
model.change((writer) => { | ||
writer.setAttribute('src', newSrc, image.element); | ||
}); | ||
} else { | ||
let data = { | ||
assetId: image.srcInfo.assetId, | ||
handle: image.srcInfo.transform, | ||
}; | ||
|
||
// get the new url | ||
Craft.sendActionRequest('POST', 'assets/generate-transform', { | ||
data, | ||
}).then((response) => { | ||
// get new src | ||
let newSrc = | ||
response.data.url + | ||
'?' + | ||
new Date().getTime() + | ||
'#asset:' + | ||
image.srcInfo.assetId + | ||
':transform:' + | ||
image.srcInfo.transform; | ||
|
||
// and replace | ||
model.change((writer) => { | ||
writer.setAttribute('src', newSrc, image.element); | ||
}); | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Returns all images present in the editor that are Craft Assets. | ||
* | ||
* @returns {*[]} | ||
* @private | ||
*/ | ||
_getAllImageAssets() { | ||
const editor = this.editor; | ||
const model = editor.model; | ||
const range = model.createRangeIn(model.document.getRoot()); | ||
|
||
let images = []; | ||
for (const value of range.getWalker({ignoreElementEnd: true})) { | ||
if (value.item.is('element') && value.item.name === 'imageBlock') { | ||
let srcInfo = this._srcInfo(value.item); | ||
if (srcInfo) { | ||
images.push({ | ||
element: value.item, | ||
srcInfo: srcInfo, | ||
}); | ||
} | ||
} | ||
} | ||
|
||
return images; | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
src/web/assets/ckeditor/src/image/imageeditor/imageeditorediting.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/** | ||
* @link https://craftcms.com/ | ||
* @copyright Copyright (c) Pixel & Tonic, Inc. | ||
* @license GPL-3.0-or-later | ||
*/ | ||
|
||
import {Plugin} from 'ckeditor5/src/core'; | ||
import ImageUtils from '@ckeditor/ckeditor5-image/src/imageutils'; | ||
import ImageEditorCommand from './imageeditorcommand'; | ||
|
||
export default class ImageEditorEditing extends Plugin { | ||
static get requires() { | ||
return [ImageUtils]; | ||
} | ||
|
||
static get pluginName() { | ||
return 'ImageEditorEditing'; | ||
} | ||
|
||
init() { | ||
const editor = this.editor; | ||
const imageEditorCommand = new ImageEditorCommand(editor); | ||
editor.commands.add('imageEditor', imageEditorCommand); | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
src/web/assets/ckeditor/src/image/imageeditor/imageeditorui.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/** | ||
* @link https://craftcms.com/ | ||
* @copyright Copyright (c) Pixel & Tonic, Inc. | ||
* @license GPL-3.0-or-later | ||
*/ | ||
|
||
import {Plugin, icons} from 'ckeditor5/src/core'; | ||
import {ButtonView} from 'ckeditor5/src/ui'; | ||
import ImageEditorEditing from './imageeditorediting'; | ||
|
||
export default class ImageEditorUI extends Plugin { | ||
static get requires() { | ||
return [ImageEditorEditing]; | ||
} | ||
|
||
static get pluginName() { | ||
return 'ImageEditorUI'; | ||
} | ||
|
||
init() { | ||
const editor = this.editor; | ||
const command = editor.commands.get('imageEditor'); | ||
this.bind('isEnabled').to(command); | ||
this._registerImageEditorButton(); | ||
} | ||
|
||
/** | ||
* A helper function that creates a button component for the plugin that triggers launch of the Image Editor. | ||
*/ | ||
_registerImageEditorButton() { | ||
const editor = this.editor; | ||
const t = editor.t; | ||
const command = editor.commands.get('imageEditor'); | ||
|
||
const componentCreator = () => { | ||
const buttonView = new ButtonView(); | ||
|
||
buttonView.set({ | ||
label: t('Edit Image'), | ||
withText: true, | ||
}); | ||
|
||
buttonView.bind('isEnabled').to(command); | ||
|
||
// Execute command when a button is clicked. | ||
this.listenTo(buttonView, 'execute', (evt) => { | ||
editor.execute('imageEditor'); | ||
editor.editing.view.focus(); | ||
}); | ||
|
||
return buttonView; | ||
}; | ||
|
||
editor.ui.componentFactory.add('imageEditor', componentCreator); | ||
} | ||
} |