├── 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 | '
%s
', 137 | $editingUrl, 138 | $elementId, 139 | $itemContent 140 | ); 141 | } 142 | 143 | /** 144 | * Detects which single field to display when content element enters 145 | * editing mode. Only a single field can be edited. 146 | * 147 | * @param string $contentType 148 | * @return string 149 | */ 150 | protected function detectFieldNamesForContentType($contentType) 151 | { 152 | return $GLOBALS['TCA']['tt_content']['types'][$contentType]['inlineEditedFields']; 153 | } 154 | 155 | /** 156 | * Returns the language service 157 | * @return LanguageService 158 | */ 159 | protected function getLanguageService() 160 | { 161 | return $GLOBALS['LANG']; 162 | } 163 | 164 | } 165 | 166 | -------------------------------------------------------------------------------- /Configuration/TCA/Overrides/tt_content.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Click to edit 8 | Klik her for at redigere elementet 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Resources/Private/Language/locallang_be.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Click to edit 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Resources/Public/Images/inline-edit-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NamelessCoder/inline_page_editing/bf8d4f0bde9e5c184cd31e5475f4c979feb32f58/Resources/Public/Images/inline-edit-example.gif -------------------------------------------------------------------------------- /Resources/Public/Images/inline-new-content-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NamelessCoder/inline_page_editing/bf8d4f0bde9e5c184cd31e5475f4c979feb32f58/Resources/Public/Images/inline-new-content-example.gif -------------------------------------------------------------------------------- /Resources/Public/JavaScript/InlinePageEditingDropDown.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/InlinePageEditingDropDown 16 | */ 17 | define(['jquery'], function($) { 18 | 19 | /** 20 | * 21 | * @type {{}} 22 | * @exports TYPO3/CMS/InlinePageEditing/InlinePageEditingDropDown 23 | */ 24 | var InlinePageEditingDropDown = {}; 25 | 26 | InlinePageEditingDropDown.convertToDropdown = function(element) { 27 | element.wrapAll('
'); 28 | var dropdown = $(element).parent(); 29 | dropdown.append(''); 30 | dropdown.append(''); 31 | dropdown.find('.dropdown-toggle').click(function() { 32 | if (dropdown.find('.dropdown-menu').is(':visible')) { 33 | return; 34 | }; 35 | dropdown.find('.new-content-menu').html('' + 36 | '' + 37 | '' + 38 | '' + 39 | '' 40 | ); 41 | var button = $(this); 42 | var onclick = button.attr('onclick'); 43 | var url = element.attr('href'); 44 | $.ajax({ 45 | url: url, 46 | complete: function(response) { 47 | var responseDocument = $("
" + response.responseText + "
"); 48 | var form = responseDocument.find('#NewContentElementController'); 49 | var gotoFunctionRegExp = new RegExp("function goToalt_doc\(\)[^\n]+\n([^\}]+)", "g"); 50 | var gotoFunctionBody = gotoFunctionRegExp.exec(response.responseText)[2]; 51 | eval('goToalt_doc = function() { ' + gotoFunctionBody.replace('return false;', '') + '; }'); 52 | var icon; 53 | var link; 54 | $('.new-content-menu').html(''); 55 | dropdown.find('.new-content-menu').append(form); 56 | form.hide(); 57 | var tabTitles = form.find('.nav.nav-tabs.t3js-tabs a, .t3js-tabmenu-item a'); 58 | var index = 0; 59 | var html = '
'; 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 | --------------------------------------------------------------------------------