├── Classes
└── Hooks
│ ├── AssetAttacher.php
│ └── ContentPreview.php
├── Configuration
└── TCA
│ └── Overrides
│ └── tt_content.php
├── README.md
├── Resources
├── Private
│ └── Language
│ │ ├── da.locallang_be.xlf
│ │ └── locallang_be.xlf
└── Public
│ ├── Images
│ ├── inline-edit-example.gif
│ └── inline-new-content-example.gif
│ ├── JavaScript
│ ├── InlinePageEditingDropDown.js
│ └── InlinePageEditingEngine.js
│ └── Stylesheet
│ └── Styles.css
├── composer.json
├── ext_emconf.php
└── ext_localconf.php
/Classes/Hooks/AssetAttacher.php:
--------------------------------------------------------------------------------
1 | loadJquery();
27 | $pageRenderer->loadRequireJsModule('TYPO3/CMS/InlinePageEditing/InlinePageEditingEngine');
28 | if (version_compare(ExtensionManagementUtility::getExtensionVersion('core'), '8.7', '<')) {
29 | $pageRenderer->loadRequireJsModule('TYPO3/CMS/InlinePageEditing/InlinePageEditingDropDown');
30 | }
31 | $pageRenderer->addCssFile(
32 | GeneralUtility::getFileAbsFileName('EXT:inline_page_editing/Resources/Public/Stylesheet/Styles.css')
33 | );
34 | static::$loaded = true;
35 | }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/Classes/Hooks/ContentPreview.php:
--------------------------------------------------------------------------------
1 | includeJavascriptInPageRendererIfNotIncluded();
43 | if (empty($itemContent)) {
44 | switch ($contentType) {
45 | case 'header':
46 | $itemContent = sprintf('%s', $row['header']);
47 | if ($row['subheader']) {
48 | $itemContent = $parentObject->linkEditContent($parentObject->renderText($row['subheader']), $row) . '
';
49 | }
50 | break;
51 | case 'uploads':
52 | if ($row['media']) {
53 | $itemContent = $parentObject->linkEditContent($parentObject->getThumbCodeUnlinked($row, 'tt_content', 'media'), $row) . '
';
54 | }
55 | break;
56 | case 'menu':
57 | $itemContent = $parentObject->linkEditContent('' . htmlspecialchars($parentObject->CType_labels[$contentType]) . '', $row) . '
';
58 | $menuTypeLabel = $this->getLanguageService()->sL(
59 | BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'menu_type', $row['menu_type'])
60 | );
61 | $menuTypeLabel = $menuTypeLabel ?: 'invalid menu type';
62 | $itemContent .= $parentObject->linkEditContent($menuTypeLabel, $row);
63 | break;
64 | case 'shortcut':
65 | if (!empty($row['records'])) {
66 | $itemContent = 'Shortcut: ' . $row['records'];
67 | }
68 | break;
69 | case 'text':
70 | case 'textpic':
71 | case 'textmedia':
72 | case 'table':
73 | case 'bullets':
74 | if ($row['bodytext']) {
75 | $itemContent = $parentObject->linkEditContent($parentObject->renderText($row['bodytext']), $row);
76 | }
77 | if ($row['image']) {
78 | $itemContent = $parentObject->linkEditContent($parentObject->getThumbCodeUnlinked($row, 'tt_content', 'image'), $row);
79 | }
80 | break;
81 | case 'html':
82 | $itemContent = $parentObject->linkEditContent(nl2br(htmlentities($row['bodytext'])), $row) . '
';
83 | break;
84 | default:
85 | break;
86 | }
87 | }
88 | $itemContent = trim($itemContent);
89 | if (empty($itemContent)) {
90 | $itemContent = ($row['header'] ? $row['header'] . ': ' : '') . $this->getLanguageService()->sL(self::LLPATH . 'label.edit');
91 | }
92 | $drawItem = false;
93 | $headerContent = '';
94 | $itemContent = $this->renderClickEnableWrapper($itemContent, $row);
95 | }
96 |
97 | /**
98 | * @param EditDocumentController $controller
99 | * @return void
100 | */
101 | public function processEditForm(EditDocumentController $controller)
102 | {
103 | (new AssetAttacher())->includeJavascriptInPageRendererIfNotIncluded();
104 | }
105 |
106 | /**
107 | * @return PageLayoutController
108 | */
109 | protected function getPageLayoutController()
110 | {
111 | return $GLOBALS['SOBE'];
112 | }
113 |
114 | /**
115 | * @param string $itemContent
116 | * @param array $row
117 | * @return string
118 | */
119 | protected function renderClickEnableWrapper($itemContent, array $row)
120 | {
121 | $elementId = 'e' . $row['uid'];
122 | $uri = rtrim(GeneralUtility::getIndpEnv('REQUEST_URI'), '&');
123 | $urlParameters = [
124 | 'inline' => 1,
125 | 'edit' => [
126 | 'tt_content' => [
127 | $row['uid'] => 'edit'
128 | ]
129 | ],
130 | 'columnsOnly' => $this->detectFieldNamesForContentType($row['CType']),
131 | 'returnUrl' => $uri . '&finished=1&element=' . $elementId . '&contentId=' . $row['uid']
132 | ];
133 | $editingUrl = BackendUtility::getModuleUrl('record_edit', $urlParameters);
134 | return sprintf(
135 | '
';
60 | form.find('.tab-content div[role="tabpanel"]').each(function() {
61 | html += '
';
62 | html += '
' + $(tabTitles[index]).text() + '
';
63 | var subIndex = 0;
64 | $(this).find('.media').each(function() {
65 | icon = $(this).find('.icon-markup img');
66 | link = $(this).find('a:first-child');
67 | link.attr('title', $(this).html());
68 | link.html('');
69 | link.css({whiteSpace: "nowrap"});
70 | link.prepend(icon).addClass('dropdown-item pull-left');
71 | subIndex++;
72 | html += link.wrap('
').parent().html();
73 | });
74 | html += '
';
75 | if (index - 1 % 2 == 0 && index > 0) {
76 | html += '
';
77 | }
78 | index++;
79 | });
80 | html += '
';
81 | dropdown.find('.new-content-menu').append(html);
82 | dropdown.find('a').tooltip({html: true});
83 | dropdown.find('.new-content-menu').css({width: dropdown.parent().parent().width()});
84 | }
85 | });
86 | });
87 | };
88 |
89 | $(".t3js-page-new-ce a:first-child").each(function() { InlinePageEditingDropDown.convertToDropdown($(this)); });
90 |
91 | return InlinePageEditingDropDown;
92 | });
93 |
--------------------------------------------------------------------------------
/Resources/Public/JavaScript/InlinePageEditingEngine.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of the TYPO3 CMS project.
3 | *
4 | * It is free software; you can redistribute it and/or modify it under
5 | * the terms of the GNU General Public License, either version 2
6 | * of the License, or any later version.
7 | *
8 | * For the full copyright and license information, please read the
9 | * LICENSE.txt file that was distributed with this source code.
10 | *
11 | * The TYPO3 project - inspiring people to share!
12 | */
13 |
14 | /**
15 | * Module: TYPO3/CMS/InlinePageEditing/InlinePageEditingEngine
16 | */
17 | define(['jquery'], function($) {
18 |
19 | /**
20 | *
21 | * @type {{}}
22 | * @exports TYPO3/CMS/InlinePageEditing/InlinePageEditingEngine
23 | */
24 | var InlinePageEditing = {};
25 |
26 | InlinePageEditing.getUrlParameter = function(sParam) {
27 | var sPageURL = decodeURIComponent(window.location.search.substring(1)),
28 | sURLVariables = sPageURL.split('&'),
29 | sParameterName,
30 | i;
31 |
32 | for (i = 0; i < sURLVariables.length; i++) {
33 | sParameterName = sURLVariables[i].split('=');
34 |
35 | if (sParameterName[0] === sParam) {
36 | return sParameterName[1] === undefined ? true : sParameterName[1];
37 | }
38 | }
39 | };
40 |
41 | InlinePageEditing.clickEditAction = function() {
42 | var id = $(this).attr('data-element-id');
43 | var iframe = $('
');
44 | if ($(this).attr('class').indexOf('open') >= 0) {
45 | return false;
46 | }
47 | $(this).addClass('open').removeAttr('data-toggle').tooltip('destroy');
48 | $(this).html(iframe);
49 | return false;
50 | };
51 |
52 | InlinePageEditing.initializeInsideInlineWindow = function() {
53 | // We are receiving an "inline" rendering request from the page module, to display the editing form
54 | // directly in the preview of a content element. In order to present a more reasonable form to our
55 | // users we remove some superfluous HTML and change some click handlers to emit window events which
56 | // our click handler above has added a listener for.
57 |
58 | $('body').addClass('inline');
59 | $('.module-body').removeAttr('style').removeClass('module-body');
60 | $('span[data-identifier="actions-edit-undo"]').parent().remove();
61 | $('span[data-identifier="actions-document-open"]').parent().click(InlinePageEditing.redirectParentWindowToUrl);
62 | };
63 |
64 | InlinePageEditing.resizeFrame = function() {
65 | // Continuously update frame height of parent by dispatching window signals
66 | window.parent.postMessage(
67 | [
68 | "setFrameHeight",
69 | InlinePageEditing.getUrlParameter('element'),
70 | $('div.module-docheader').height() + $('div.t3js-module-body').height() + 25
71 | ],
72 | "*"
73 | );
74 | };
75 |
76 | InlinePageEditing.redirectParentWindowToUrl = function() {
77 | window.parent.postMessage(["redirectTo", $(this).attr('href').replace('&inline=1', '')], "*");
78 | return false;
79 | };
80 |
81 | InlinePageEditing.initializeEditEnableListeners = function() {
82 | $(".inline-edit-click-enable").click(InlinePageEditing.clickEditAction);
83 | };
84 |
85 | InlinePageEditing.initializeWindowEvents = function() {
86 | window.addEventListener('message', function(e) {
87 | if (e.data[0] == 'setFrameHeight') {
88 | $('#' + e.data[1]).height(e.data[2] + 'px');
89 | } else if (e.data[0] == 'redirectTo') {
90 | window.location.href = e.data[1];
91 | } else if (e.data[0] == 'closeFrame') {
92 | var element = $('#' + e.data[1]).parent();
93 | element.removeClass('open');
94 | element.html(e.data[2]).click(InlinePageEditing.clickEditAction);
95 | }
96 | }, false);
97 | };
98 |
99 | InlinePageEditing.dispatchWindowCloseEvent = function() {
100 | // End-of-edit reached; extract resulting HTML and message to parent window, dispatch close action.
101 | var container = $('#element-tt_content-' + InlinePageEditing.getUrlParameter('contentId') + ' .exampleContent');
102 | var html;
103 | if (container.find('.inline-edit-click-enable').length > 0) {
104 | html = container.find('.inline-edit-click-enable').html();
105 | } else {
106 | html = container.html();
107 | };
108 | window.parent.postMessage(["closeFrame", InlinePageEditing.getUrlParameter('element'), html], "*");
109 | };
110 |
111 | // Module initialization sequence
112 | if (InlinePageEditing.getUrlParameter('finished')) {
113 | $(InlinePageEditing.dispatchWindowCloseEvent);
114 | }
115 | if (InlinePageEditing.getUrlParameter('inline')) {
116 | $(InlinePageEditing.initializeInsideInlineWindow);
117 | setInterval(InlinePageEditing.resizeFrame, 100);
118 | } else {
119 | $(InlinePageEditing.initializeWindowEvents);
120 | $(InlinePageEditing.initializeEditEnableListeners);
121 | };
122 |
123 | return InlinePageEditing;
124 | });
125 |
--------------------------------------------------------------------------------
/Resources/Public/Stylesheet/Styles.css:
--------------------------------------------------------------------------------
1 | body.inline {
2 | height: auto !important;
3 | }
4 | body.inline .module-docheader-bar-navigation,
5 | body.inline .t3js-editform-delete-record,
6 | body.inline .help-block,
7 | body.inline .module-docheader-bar-column-right,
8 | body.inline h1 {
9 | display: none;
10 | }
11 | body.inline .module-docheader,
12 | body.inline form {
13 | position: relative;
14 | }
15 | body.inline .module-docheader-bar-column-left {
16 | margin-top: 1em;
17 | }
18 | .t3js-module-body {
19 | padding-top: 1em !important;
20 | }
21 | iframe.inline-editable {
22 | border-style: none;
23 | height: 50px;
24 | width: 100%;
25 | }
26 | body.inline #EditDocumentController {
27 | width: 100%;
28 | }
29 | .t3-page-ce.t3js-page-ce {
30 | transform: none !important;
31 | }
32 |
33 | .t3-page-ce.t3js-page-ce .new-content-menu {
34 | transform: translate3d(0,0,0) !important;
35 | z-index: 9999999;
36 | position: absolute;
37 | max-width: 500px;
38 | }
39 |
40 | .tooltip-inner .media-body {
41 | color: white;
42 | }
43 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "namelesscoder/inline-page-editing",
3 | "description": "Edit content elements directly in page module without switching views",
4 | "type": "typo3-cms-extension",
5 | "license": "GPL-3.0+",
6 | "autoload": {
7 | "psr-4": {
8 | "NamelessCoder\\InlinePageEditing\\": "Classes/"
9 | }
10 | },
11 | "require": {
12 | "typo3/cms-core": ">=7.6.0",
13 | "typo3/cms-backend": ">=7.6.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ext_emconf.php:
--------------------------------------------------------------------------------
1 | 'Page module inline editing',
4 | 'description' => 'Enables editing of content elements in the backend module without opening the content editing form (displays configurable form fields in preview, activates on click)',
5 | 'category' => 'misc',
6 | 'author' => 'Claus Due',
7 | 'author_email' => 'claus@namelesscoder.net',
8 | 'author_company' => '',
9 | 'shy' => '',
10 | 'dependencies' => '',
11 | 'conflicts' => '',
12 | 'priority' => '',
13 | 'module' => '',
14 | 'state' => 'beta',
15 | 'internal' => '',
16 | 'uploadfolder' => 0,
17 | 'createDirs' => '',
18 | 'modify_tables' => '',
19 | 'clearCacheOnLoad' => 0,
20 | 'lockType' => '',
21 | 'version' => '1.2.0',
22 | 'constraints' => [
23 | 'depends' => [
24 | 'php' => '5.6.0-7.4.99',
25 | 'typo3' => '7.6.0-10.4.99',
26 | ],
27 | 'conflicts' => [],
28 | 'suggests' => [],
29 | ],
30 | 'suggests' => [],
31 | '_md5_values_when_last_written' => '',
32 | ];
33 |
--------------------------------------------------------------------------------
/ext_localconf.php:
--------------------------------------------------------------------------------
1 | includeJavascriptInPageRendererIfNotIncluded';
8 |
9 | /** @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher */
10 | $signalSlotDispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
11 | $signalSlotDispatcher->connect(
12 | \TYPO3\CMS\Backend\Controller\EditDocumentController::class,
13 | 'initAfter',
14 | \NamelessCoder\InlinePageEditing\Hooks\ContentPreview::class,
15 | 'processEditForm'
16 | );
17 |
18 |
--------------------------------------------------------------------------------