├── Classes ├── Backend │ ├── Controller │ │ └── ContentElement │ │ │ └── NewContentElementController.php │ └── Form │ │ └── FormDataProvider │ │ └── TcaFrontendRichtextConfiguration.php ├── Controller │ ├── EditorController.php │ ├── Event │ │ ├── Handler │ │ │ ├── BootCompletedEventHandler.php │ │ │ └── TypoScriptPrepareFieldUpdateEventHandler.php │ │ ├── PrepareFieldUpdateEvent.php │ │ └── PrepareFieldUpdateEventHandlerInterface.php │ ├── FrontendEditingModuleController.php │ └── ReceiverController.php ├── EditingPanel │ ├── FrontendEditingDropzoneModifier.php │ └── FrontendEditingPanel.php ├── Hook │ └── TypoLinkPostProcHook.php ├── Html │ └── FrontendEditingSanitizerBuilder.php ├── Middleware │ ├── FrontendEditingInitiator.php │ └── FrontendEditingSetupSimulator.php ├── Service │ ├── AccessService.php │ └── ContentEditableWrapperService.php ├── Utility │ └── ConfigurationUtility.php └── ViewHelpers │ ├── ContentEditableViewHelper.php │ ├── CustomDropZoneViewHelper.php │ ├── IsFrontendEditingActiveViewHelper.php │ └── IsPlaceholderEnabledViewHelper.php ├── Configuration ├── Backend │ └── AjaxRoutes.php ├── RTE │ ├── BrOnly.yaml │ └── ListOnly.yaml ├── RequestMiddlewares.php ├── Services.yaml ├── TCA │ └── Overrides │ │ └── sys_template.php ├── TsLint.yml └── TypoScript │ ├── FluidStyledContent11 │ └── setup.typoscript │ └── setup.typoscript ├── Documentation ├── ConfigureAndExtend │ ├── EmptyColumns │ │ └── Index.rst │ ├── ExtensionConfiguration │ │ └── Index.rst │ ├── HooksAndEvents │ │ └── Index.rst │ ├── Index.rst │ ├── JavaScript │ │ └── Index.rst │ ├── TCA │ │ └── Index.rst │ ├── TypoScript │ │ └── Index.rst │ ├── UserTs │ │ └── Index.rst │ └── ViewHelpers │ │ └── Index.rst ├── Development │ ├── Dependencies │ │ └── Index.rst │ ├── Index.rst │ ├── JavaScript │ │ └── Index.rst │ └── Workflow │ │ └── Index.rst ├── Images │ ├── AddTypoScript.png │ ├── CustomRecordsDropzone.png │ ├── ExtensionManagerSettingsOverview.png │ ├── FullEditorInBackend.png │ └── add-typoscript.png ├── Includes.rst.txt ├── Index.rst ├── InlineEditing │ ├── Index.rst │ └── NewsCustomRecordExample.html ├── Installation │ └── Index.rst ├── Introduction │ └── Index.rst ├── KnownIssues │ └── Index.rst ├── NewContentElements │ └── Index.rst ├── Settings.cfg ├── Sitemap.rst └── genindex.rst ├── README.md ├── Resources ├── Private │ ├── .htaccess │ ├── Language │ │ ├── locallang.xlf │ │ ├── locallang_be.xlf │ │ └── locallang_mod.xlf │ ├── Layouts │ │ └── DocHeader.html │ ├── Partials │ │ ├── ContentElementsToolbar.html │ │ ├── CustomRecordTab.html │ │ ├── FluidStyledContent11 │ │ │ └── Header │ │ │ │ ├── All.html │ │ │ │ ├── Header.html │ │ │ │ └── SubHeader.html │ │ └── WizardElementTabs.html │ ├── Sass │ │ ├── Elements │ │ │ ├── _ckeditor.scss │ │ │ ├── _content-elements-toolbar.scss │ │ │ ├── _modal.scss │ │ │ ├── _other.scss │ │ │ ├── _scrollarea.scss │ │ │ └── _toastr.scss │ │ ├── Partials │ │ │ ├── _accordion.scss │ │ │ ├── _elements.scss │ │ │ ├── _inputs.scss │ │ │ ├── _titles.scss │ │ │ └── _wrappers.scss │ │ ├── _colors.scss │ │ ├── _constants.scss │ │ ├── _icons.scss │ │ ├── _inline_action_icon.scss │ │ ├── backend_module.scss │ │ └── inline_editing.scss │ └── Templates │ │ ├── FluidStyledContent11 │ │ ├── Text.html │ │ ├── Textmedia.html │ │ └── Textpic.html │ │ └── FrontendEditingModule │ │ └── Show.html └── Public │ ├── Css │ ├── Fonts │ │ ├── icons.eot │ │ ├── icons.svg │ │ ├── icons.ttf │ │ ├── icons.woff │ │ ├── selection.json │ │ └── style.css │ ├── backend_module.css │ └── inline_editing.css │ ├── Icons │ ├── Extension.png │ ├── ext_icon.png │ └── visible_icon.svg │ ├── JavaScript │ ├── BackendModule.js │ ├── Contrib │ │ ├── ckeditor-jquery-adapter.js │ │ ├── dexie.min.js │ │ ├── dexie.min.js.map │ │ ├── immutable.js │ │ ├── pino.js │ │ ├── toastr.js │ │ └── ulog │ │ │ ├── ulog.js │ │ │ └── ulog.js.map │ ├── Crud.js │ ├── Editor.js │ ├── FrontendEditing.js │ ├── GUI.js │ ├── LoadingScreen.js │ ├── Notification.js │ ├── ParentWindow.js │ ├── Plugins │ │ ├── confighelper │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── docs │ │ │ │ ├── install.html │ │ │ │ └── styles.css │ │ │ └── plugin.js │ │ └── openlink │ │ │ ├── README.md │ │ │ ├── icons │ │ │ ├── hidpi │ │ │ │ └── openLink.png │ │ │ └── openLink.png │ │ │ ├── lang │ │ │ ├── bg.js │ │ │ ├── de.js │ │ │ ├── en.js │ │ │ ├── it.js │ │ │ ├── pl.js │ │ │ └── ru.js │ │ │ └── plugin.js │ ├── Scroller.js │ ├── Storage.js │ └── Utils │ │ ├── IndexedDB.js │ │ ├── Logger.js │ │ ├── LoggerPersist.js │ │ ├── LoggerWindowError.js │ │ ├── Translator.js │ │ └── TranslatorLoader.js │ └── Templates │ └── Close.html ├── composer.json ├── ext_conf_template.txt ├── ext_emconf.php ├── ext_localconf.php ├── ext_tables.php └── ext_typoscript_setup.typoscript /Classes/Backend/Controller/ContentElement/NewContentElementController.php: -------------------------------------------------------------------------------- 1 | getWizards(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Classes/Backend/Form/FormDataProvider/TcaFrontendRichtextConfiguration.php: -------------------------------------------------------------------------------- 1 | $fieldConfig) { 24 | if ( 25 | empty($fieldConfig['config']['type']) 26 | || $fieldConfig['config']['type'] !== 'text' 27 | || !isset($fieldConfig['config']['enableFrontendRichtext']) 28 | ) { 29 | continue; 30 | } 31 | 32 | $fieldConfig['config']['enableRichtext'] = (bool)$fieldConfig['config']['enableFrontendRichtext']; 33 | 34 | $fieldConfig['config']['richtextConfiguration'] = 35 | $fieldConfig['config']['frontendRichtextConfiguration']; 36 | 37 | unset($fieldConfig['config']['frontendRichtextConfiguration']); 38 | 39 | $result['processedTca']['columns'][$fieldName] = $fieldConfig; 40 | } 41 | 42 | return $result; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Classes/Controller/Event/Handler/BootCompletedEventHandler.php: -------------------------------------------------------------------------------- 1 | loadRequireJsModule('TYPO3/CMS/FrontendEditing/ParentWindow'); 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Classes/Controller/Event/Handler/TypoScriptPrepareFieldUpdateEventHandler.php: -------------------------------------------------------------------------------- 1 | configurationManager = $configurationManager; 31 | } 32 | 33 | /** 34 | * Processes incoming values using stdWrap from TypoScript. 35 | * 36 | * Two configuration arrays are used (in order of preference): 37 | * 38 | * `config.tx_frontendediting.contentPersistPreProcessing` contains type and field-specific configurations. 39 | * `contentPersistPreProcessing...` accepts any stdWrap configuratoion property. 40 | * Setting `` to "*" indicates a wildcard catching all types not explicitly set. 41 | * `` "0" (zero as string) is the default type of any TYPO3 record. 42 | * 43 | * `config.tx_frontendediting.contentPersistPreProcessingPatterns` will be used if no configuration matches are 44 | * found in `contentPersistPreProcessing`. 45 | * `contentPersistPreProcessingPatterns.` accepts any stdWrap configuration property. 46 | * `` is any RTE preset string, as set in a field's TCA configuration `frontendConfiguration` or 47 | * `frontendRichtextConfiguration`. 48 | * 49 | * @param PrepareFieldUpdateEvent $event 50 | */ 51 | public function __invoke(PrepareFieldUpdateEvent $event): void 52 | { 53 | $setup = $this->getTypoScriptSetup()['contentPersistPreProcessing.'] ?? []; 54 | 55 | $typeValue = BackendUtility::getTCAtypeValue($event->getTable(), $event->getRecord()); 56 | 57 | if ( 58 | isset($setup[$event->getTable() . '.'][$typeValue . '.']) 59 | && !is_array($setup[$event->getTable() . '.'][$typeValue . '.']) 60 | && is_array($setup[$event->getTable()]['*.']) 61 | ) { 62 | $typeValue = '*'; 63 | } 64 | 65 | $stdWrapConfiguration = ''; 66 | if ( 67 | isset($setup[$event->getTable() . '.'][$typeValue . '.']) 68 | && is_array($setup[$event->getTable() . '.'][$typeValue . '.']) 69 | ) { 70 | $stdWrapConfiguration = $setup[$event->getTable() . '.'][$typeValue . '.'][$event->getField() . '.']; 71 | } 72 | 73 | if (!is_array($stdWrapConfiguration)) { 74 | $tableTca = $GLOBALS['TCA'][$event->getTable()]; 75 | $fieldConfig = $tableTca['columns'][$event->getField()]['config']; 76 | 77 | ArrayUtility::mergeRecursiveWithOverrule( 78 | $fieldConfig, 79 | $tableTca['types'][$typeValue]['columnsOverrides'][$event->getField()]['config'] ?? [] 80 | ); 81 | 82 | if (isset($fieldConfig['enableFrontendRichtext']) && (bool)$fieldConfig['enableFrontendRichtext'] === true) { 83 | $rteEnabled = true; 84 | } else { 85 | $rteEnabled = (bool)($fieldConfig['enableRichtext'] ?? false); 86 | } 87 | 88 | $rtePreset = $fieldConfig['frontendRichtextConfiguration'] ?? (isset($fieldConfig['richtextConfiguration'])) 89 | ? $fieldConfig['richtextConfiguration'] : []; 90 | 91 | if (!$rteEnabled || !$rtePreset) { 92 | return; 93 | } 94 | 95 | $stdWrapConfiguration = 96 | $this->getTypoScriptSetup()['contentPersistPreProcessingPatterns.'][$rtePreset . '.'] ?? null; 97 | 98 | if (!is_array($stdWrapConfiguration)) { 99 | return; 100 | } 101 | } 102 | 103 | /** @var ContentObjectRenderer $contentObjectRenderer */ 104 | $contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class); 105 | 106 | $event->setContent( 107 | $contentObjectRenderer->stdWrap( 108 | $event->getContent(), 109 | $stdWrapConfiguration 110 | ) 111 | ); 112 | } 113 | 114 | /** 115 | * Return the TypoScript configuration in config.tx_frontendediting 116 | * 117 | * @return array 118 | */ 119 | protected function getTypoScriptSetup(): array 120 | { 121 | return $this->configurationManager->getTypoScriptSetup()['config.']['tx_frontendediting.'] ?? []; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Classes/Controller/Event/PrepareFieldUpdateEvent.php: -------------------------------------------------------------------------------- 1 | table = $table; 41 | $this->field = $field; 42 | $this->content = $content; 43 | $this->record = $record; 44 | } 45 | 46 | /** 47 | * Returns the table being updated. 48 | * 49 | * @return string 50 | */ 51 | public function getTable(): string 52 | { 53 | return $this->table; 54 | } 55 | 56 | /** 57 | * Returns the updated content of the field. 58 | * 59 | * @return array 60 | */ 61 | public function getRecord(): array 62 | { 63 | return $this->record; 64 | } 65 | 66 | /** 67 | * Returns the name of the field being updated. 68 | * 69 | * @return string 70 | */ 71 | public function getField(): string 72 | { 73 | return $this->field; 74 | } 75 | 76 | /** 77 | * Returns the updated content of the field. 78 | * 79 | * @return string 80 | */ 81 | public function getContent(): string 82 | { 83 | return $this->content; 84 | } 85 | 86 | /** 87 | * @param string $content 88 | */ 89 | public function setContent(string $content): void 90 | { 91 | $this->content = $content; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Classes/Controller/Event/PrepareFieldUpdateEventHandlerInterface.php: -------------------------------------------------------------------------------- 1 | lastTypoLinkUrl; 41 | $unparsedUrl = $contentObjectRenderer->parameters['href'] ?? false; 42 | $linkPageUid = $params['linkDetails']['pageuid'] ?? false; 43 | 44 | // Create a new DOMDocument object to manipulate the A tag 45 | $doc = new DOMDocument(); 46 | $doc->loadHTML($params['finalTag']); 47 | $a = $doc->getElementsByTagName('a')->item(0); 48 | 49 | // Set href to unparsed URL if set, else set it to parsed URL 50 | // this is done so, when the editor modify a CE, the unparsed URL is saved instead of the parsed one 51 | $href = $unparsedUrl ?: $parsedUrl; 52 | $a->setAttribute('href', $href); 53 | 54 | // Add data-url-page-uid used to eventually navigate Frontend Editing module to the page of the clicked link 55 | if ($linkPageUid) { $a->setAttribute('data-url-page-uid', $linkPageUid); } 56 | 57 | // Add data-url-parsed to the final tag to allow editor to navigate the FE also in Frontend Editing mode 58 | $a->setAttribute('data-url-parsed', $parsedUrl); 59 | 60 | // Save the final tag removing the closing tag '' 61 | $params['finalTag'] = str_replace('', '', $doc->saveHTML($a)); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Classes/Html/FrontendEditingSanitizerBuilder.php: -------------------------------------------------------------------------------- 1 | uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); 62 | $this->pageRenderer = new PageRenderer(); 63 | } 64 | 65 | /** 66 | * Modify the frontend request if Frontend Editing is enabled 67 | * 68 | * @param ServerRequestInterface $request 69 | * @param RequestHandlerInterface $handler 70 | * @return ResponseInterface 71 | */ 72 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface 73 | { 74 | if (AccessService::isEnabled()) { 75 | // Get TSFE controller from the request 76 | $this->typoScriptFrontendController = $request->getAttribute('frontend.controller'); 77 | 78 | // Make sure edit icons are displayed 79 | $GLOBALS['TSFE']->displayFieldEditIcons = 1; 80 | 81 | // Change the sanitizer to allow the attributes used in Frontend Editing. 82 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['htmlSanitizer']['default'] = 83 | FrontendEditingSanitizerBuilder::class; 84 | 85 | // Special content is about to be shown, so the cache must be disabled. 86 | $this->typoScriptFrontendController->set_no_cache('Frontend Editing enabled', true); 87 | 88 | // Set the preview info message as an HTML comment, so it doesn't display 89 | $this->typoScriptFrontendController->config['config']['message_preview'] = ''; 90 | } 91 | 92 | return $handler->handle($request); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Classes/Middleware/FrontendEditingSetupSimulator.php: -------------------------------------------------------------------------------- 1 | setAspect( 49 | 'visibility', 50 | GeneralUtility::makeInstance(VisibilityAspect::class, true, $showHiddenItems) 51 | ); 52 | 53 | // TODO: handle also simulateDate and simulateUserGroup like in adminpanel 54 | } 55 | return $handler->handle($request); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Classes/Service/AccessService.php: -------------------------------------------------------------------------------- 1 | isAdmin()) { 66 | return true; 67 | } 68 | if ($GLOBALS['TCA']['pages']['ctrl']['adminOnly'] ?? false) { 69 | return false; 70 | } 71 | 72 | return $page !== [] 73 | && !($page[$GLOBALS['TCA']['pages']['ctrl']['editlock'] ?? null] ?? false) 74 | && $backendUser->doesUserHaveAccess($page, Permission::PAGE_EDIT) 75 | && $backendUser->checkLanguageAccess($languageId) 76 | && $backendUser->check('tables_modify', 'pages'); 77 | } 78 | 79 | /** 80 | * Has the user edit rights for the content of the page? 81 | * 82 | * @param array $page 83 | * 84 | * @return bool 85 | */ 86 | public static function isPageContentEditAllowed(array $page): bool 87 | { 88 | return $GLOBALS['BE_USER']->doesUserHaveAccess($page, Permission::CONTENT_EDIT); 89 | } 90 | 91 | /** 92 | * Check if it is backend context 93 | * 94 | * @return bool 95 | */ 96 | public static function isBackendContext(): bool 97 | { 98 | return isset($GLOBALS['BE_USER']); 99 | } 100 | 101 | /** 102 | * @param int $pageId 103 | * @param int $pageLanguageId 104 | * @return string 105 | */ 106 | public static function getTranslationMode(int $pageId, int $pageLanguageId): string 107 | { 108 | $translationMode = ''; 109 | 110 | // Get translation mode only in BE context and for not default language 111 | if (AccessService::isBackendContext() && $pageLanguageId > 0) { 112 | $pageinfo = BackendUtility::readPageAccess($pageId, $GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW)); 113 | $context = GeneralUtility::makeInstance( 114 | PageLayoutContext::class, 115 | $pageinfo, 116 | GeneralUtility::makeInstance(BackendLayoutView::class)->getBackendLayoutForPage($pageId) 117 | ); 118 | $contentFetcher = GeneralUtility::makeInstance(ContentFetcher::class, $context); 119 | 120 | $translationInfo = $contentFetcher->getTranslationData( 121 | $contentFetcher->getFlatContentRecords($pageLanguageId), 122 | $pageLanguageId 123 | ); 124 | 125 | $translationMode = $translationInfo['mode'] ?? ''; 126 | } 127 | 128 | return $translationMode; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Classes/Utility/ConfigurationUtility.php: -------------------------------------------------------------------------------- 1 | get('frontend_editing'); 36 | 37 | if (is_array($configuration)) { 38 | return $configuration; 39 | } 40 | 41 | return []; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Classes/ViewHelpers/ContentEditableViewHelper.php: -------------------------------------------------------------------------------- 1 | 35 | * {item.bodytext} 36 | * 37 | * 38 | * Output: 39 | *
40 | * This is the content text to edit 41 | *
42 | */ 43 | class ContentEditableViewHelper extends AbstractTagBasedViewHelper 44 | { 45 | /** 46 | * Disable the escaping of children 47 | * 48 | * @var bool 49 | */ 50 | protected $escapeChildren = false; 51 | 52 | /** 53 | * Disable that the content itself isn't escaped 54 | * 55 | * @var bool 56 | */ 57 | protected $escapeOutput = false; 58 | 59 | /** 60 | * Initialize arguments 61 | */ 62 | public function initializeArguments(): void 63 | { 64 | parent::initializeArguments(); 65 | 66 | $this->registerUniversalTagAttributes(); 67 | 68 | $this->registerArgument( 69 | 'table', 70 | 'string', 71 | 'The database table name to be used for saving the content', 72 | true 73 | ); 74 | $this->registerArgument( 75 | 'field', 76 | 'string', 77 | 'The database table field name to be used for saving the content' 78 | ); 79 | $this->registerArgument( 80 | 'uid', 81 | 'string', 82 | 'The database uid (identifier) to be used for the record when saving the content', 83 | true 84 | ); 85 | $this->registerArgument( 86 | 'tag', 87 | 'string', 88 | 'An optional tag name, e.g. "div" or "span".' 89 | ); 90 | } 91 | 92 | /** 93 | * Add a content-editable tag around the content. 94 | * 95 | * @return string Rendered HTML markup 96 | * @throws RouteNotFoundException 97 | * @noinspection PhpMissingParentCallCommonInspection 98 | */ 99 | public function render(): string 100 | { 101 | $content = $this->renderChildren(); 102 | 103 | if (!AccessService::isBackendContext()) { 104 | return $this->renderAsTag($content); 105 | } 106 | 107 | $pageRepositoryClassName = PageRepository::class; 108 | $record = BackendUtility::getRecord($this->arguments['table'], (int)$this->arguments['uid']); 109 | try { 110 | $isPageContentEditAllowed = AccessService::isPageContentEditAllowed( 111 | GeneralUtility::makeInstance($pageRepositoryClassName) 112 | ->getPage_noCheck($record['pid']) 113 | ); 114 | } catch (Exception $exception) { 115 | // Suppress Exception and no database access 116 | $isPageContentEditAllowed = true; 117 | } 118 | 119 | $backendUser = $this->getBackendUser(); 120 | $isTableEditAllowed = $backendUser->check('tables_modify', $this->arguments['table']); 121 | 122 | if (!AccessService::isEnabled() || !$isPageContentEditAllowed || !$isTableEditAllowed) { 123 | $content = $content ?: ''; 124 | return $this->renderAsTag($content); 125 | } 126 | 127 | $filteredArguments = array_diff_key( 128 | $this->arguments, 129 | array_fill_keys( 130 | [ 131 | 'table', 132 | 'field', 133 | 'uid', 134 | 'tag' 135 | ], 136 | '' 137 | ) 138 | ); 139 | 140 | $wrapperService = GeneralUtility::makeInstance(ContentEditableWrapperService::class); 141 | if (empty($this->arguments['field'])) { 142 | $content = $wrapperService->wrapContent( 143 | $this->arguments['table'], 144 | (int)$this->arguments['uid'], 145 | ($record ?: []), 146 | (string)$content 147 | ); 148 | } else { 149 | $content = $wrapperService->wrapContentToBeEditable( 150 | $this->arguments['table'], 151 | $this->arguments['field'], 152 | (int)$this->arguments['uid'], 153 | (string)$content, 154 | $this->arguments['tag'], 155 | $filteredArguments 156 | ); 157 | } 158 | 159 | return $content; 160 | } 161 | 162 | /** 163 | * Render as a non-editable tag or just content if $this->arguments[tag] is not set. 164 | * 165 | * @param string|null $content 166 | * @return string 167 | */ 168 | protected function renderAsTag(?string $content): string 169 | { 170 | if ($this->arguments['tag'] !== null) { 171 | $this->tagName = $this->arguments['tag']; 172 | $this->tag->setTagName($this->tagName); 173 | $this->tag->setContent($content); 174 | 175 | $content = $this->tag->render(); 176 | } elseif ($content === null) { 177 | $content = ''; 178 | } 179 | 180 | return $content; 181 | } 182 | 183 | /** 184 | * @return BackendUserAuthentication 185 | */ 186 | protected function getBackendUser(): BackendUserAuthentication 187 | { 188 | return $GLOBALS['BE_USER']; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /Classes/ViewHelpers/CustomDropZoneViewHelper.php: -------------------------------------------------------------------------------- 1 | 33 | *

some content

34 | * 35 | * 36 | * Output: 37 | *

some content

38 | *
46 | */ 47 | class CustomDropZoneViewHelper extends AbstractViewHelper 48 | { 49 | use CompileWithRenderStatic; 50 | 51 | /** 52 | * Disable the escaping of children 53 | * 54 | * @var bool 55 | */ 56 | protected $escapeChildren = false; 57 | 58 | /** 59 | * Disable that the content itself isn't escaped 60 | * 61 | * @var bool 62 | */ 63 | protected $escapeOutput = false; 64 | 65 | /** 66 | * Initialize arguments 67 | */ 68 | public function initializeArguments(): void 69 | { 70 | parent::initializeArguments(); 71 | $this->registerArgument( 72 | 'tables', 73 | 'array', 74 | 'The database tables name allowed to be droped', 75 | true 76 | ); 77 | $this->registerArgument( 78 | 'defVals', 79 | 'array', 80 | 'Default value for new record droped in zone' 81 | ); 82 | $this->registerArgument( 83 | 'pageUid', 84 | 'string', 85 | 'Override storage page uid for new record droped in zone' 86 | ); 87 | $this->registerArgument( 88 | 'prepend', 89 | 'bool', 90 | 'Prepend the zone to the content' 91 | ); 92 | } 93 | 94 | /** 95 | * Add a content-editable div around the content 96 | * 97 | * @param array $arguments 98 | * @param Closure $renderChildrenClosure 99 | * @param RenderingContextInterface $renderingContext 100 | * 101 | * @return mixed Rendered result 102 | * @throws RouteNotFoundException 103 | * @noinspection PhpMissingParentCallCommonInspection 104 | */ 105 | public static function renderStatic( 106 | array $arguments, 107 | Closure $renderChildrenClosure, 108 | RenderingContextInterface $renderingContext 109 | ): mixed { 110 | $content = $renderChildrenClosure(); 111 | if (!AccessService::isEnabled()) { return $content; } 112 | 113 | /** @var ContentEditableWrapperService $wrapperService */ 114 | $wrapperService = GeneralUtility::makeInstance(ContentEditableWrapperService::class); 115 | 116 | $defaultValues = is_array($arguments['defVals']) ? $arguments['defVals'] : []; 117 | 118 | return $wrapperService->wrapContentWithCustomDropzone( 119 | implode(',', $arguments['tables']), 120 | (string)$content, 121 | $defaultValues, 122 | (int)$arguments['pageUid'], 123 | (bool)$arguments['prepend'] 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Classes/ViewHelpers/IsFrontendEditingActiveViewHelper.php: -------------------------------------------------------------------------------- 1 | 33 | * You're using Frontend Editing. Congratulations! 34 | * 35 | */ 36 | class IsFrontendEditingActiveViewHelper extends AbstractViewHelper 37 | { 38 | use CompileWithRenderStatic; 39 | 40 | /** 41 | * Returns true if the frontend editor is active 42 | * 43 | * @param array $arguments 44 | * @param Closure $renderChildrenClosure 45 | * @param RenderingContextInterface $renderingContext 46 | * @return bool|string 47 | * @noinspection PhpMissingParentCallCommonInspection 48 | */ 49 | public static function renderStatic( 50 | array $arguments, 51 | Closure $renderChildrenClosure, 52 | RenderingContextInterface $renderingContext 53 | ): bool|string { 54 | return AccessService::isEnabled(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Classes/ViewHelpers/IsPlaceholderEnabledViewHelper.php: -------------------------------------------------------------------------------- 1 | 34 | * {header} 35 | * 36 | */ 37 | class IsPlaceholderEnabledViewHelper extends AbstractViewHelper 38 | { 39 | use CompileWithRenderStatic; 40 | 41 | /** 42 | * Returns true if the frontend editor is active and the placeholder feature is enabled 43 | * 44 | * @param array $arguments 45 | * @param Closure $renderChildrenClosure 46 | * @param RenderingContextInterface $renderingContext 47 | * @return bool|string 48 | * @noinspection PhpMissingParentCallCommonInspection 49 | */ 50 | public static function renderStatic( 51 | array $arguments, 52 | Closure $renderChildrenClosure, 53 | RenderingContextInterface $renderingContext 54 | ): bool|string { 55 | return AccessService::isEnabled() 56 | && ConfigurationUtility::getExtensionConfiguration()['enablePlaceholders']; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Configuration/Backend/AjaxRoutes.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'path' => '/frontend-editing/process', 14 | 'target' => ReceiverController::class . '::processRequest' 15 | ], 16 | // Load CKEditor configuration per record on demand 17 | 'frontendediting_editorconfiguration' => [ 18 | 'path' => '/frontend-editing/editor-configuration', 19 | 'target' => EditorController::class . '::getConfigurationAction' 20 | ], 21 | ]; 22 | -------------------------------------------------------------------------------- /Configuration/RTE/BrOnly.yaml: -------------------------------------------------------------------------------- 1 | # Load default processing options 2 | imports: 3 | - { resource: "EXT:rte_ckeditor/Configuration/RTE/Editor/Base.yaml" } 4 | 5 | processing: 6 | mode: default 7 | 8 | allowTags: 9 | - br 10 | 11 | allowAttributes: [] 12 | 13 | allowTagsOutside: [] 14 | 15 | # Minimal configuration for the editor 16 | editor: 17 | config: 18 | toolbarGroups: 19 | - { name: clipboard, groups: [clipboard, undo] } 20 | - { name: editing, groups: [find, selection, spellchecker] } 21 | - { name: specialcharacters, groups: [ insertcharacters ] } 22 | 23 | enterMode: 2 24 | forceEnterMode: true 25 | forcePasteAsPlainText: true 26 | -------------------------------------------------------------------------------- /Configuration/RTE/ListOnly.yaml: -------------------------------------------------------------------------------- 1 | # Load default processing options 2 | imports: 3 | - { resource: "EXT:rte_ckeditor/Configuration/RTE/Editor/Base.yaml" } 4 | 5 | processing: 6 | mode: default 7 | 8 | allowTags: 9 | - li 10 | 11 | allowAttributes: [] 12 | 13 | allowTagsOutside: 14 | - li 15 | 16 | # Minimal configuration for the editor 17 | editor: 18 | config: 19 | toolbarGroups: 20 | - { name: clipboard, groups: [clipboard, undo] } 21 | - { name: editing, groups: [find, selection, spellchecker] } 22 | - { name: specialcharacters, groups: [ insertcharacters ] } 23 | 24 | allowedContent: 25 | - li 26 | 27 | disallowedContent: 28 | - ul 29 | - ol 30 | 31 | forcePasteAsPlainText: true 32 | -------------------------------------------------------------------------------- /Configuration/RequestMiddlewares.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'typo3/frontendediting/setup-simulator' => [ 12 | 'target' => FrontendEditingSetupSimulator::class, 13 | 'after' => [ 14 | 'typo3/cms-frontend/page-resolver', 15 | ], 16 | 'before' => [ 17 | 'typo3/cms-frontend/preview-simulator', 18 | ], 19 | ], 20 | 'typo3/frontendediting/initiator' => [ 21 | 'target' => FrontendEditingInitiator::class, 22 | 'after' => [ 23 | 'typo3/cms-frontend/shortcut-and-mountpoint-redirect', 24 | ], 25 | 'before' => [ 26 | 'typo3/cms-frontend/content-length-headers', 27 | ], 28 | ], 29 | ], 30 | ]; 31 | -------------------------------------------------------------------------------- /Configuration/Services.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | autowire: true 4 | autoconfigure: true 5 | public: false 6 | 7 | TYPO3\CMS\FrontendEditing\: 8 | resource: '../Classes/*' 9 | 10 | TYPO3\CMS\FrontendEditing\Controller\FrontendEditingModuleController: 11 | tags: ['backend.controller'] 12 | 13 | TYPO3\CMS\FrontendEditing\Controller\ReceiverController: 14 | public: true 15 | 16 | TYPO3\CMS\FrontendEditing\Backend\Controller\ContentElement\NewContentElementController: 17 | public: true 18 | 19 | TYPO3\CMS\FrontendEditing\Controller\Event\Handler\TypoScriptPrepareFieldUpdateEventHandler: 20 | tags: 21 | - name: event.listener 22 | identifier: typoScriptPrepareFieldUpdate 23 | event: TYPO3\CMS\FrontendEditing\Controller\Event\PrepareFieldUpdateEvent 24 | 25 | TYPO3\CMS\FrontendEditing\Controller\Event\Handler\BootCompletedEventHandler: 26 | tags: 27 | - name: event.listener 28 | identifier: bootCompletedEventHandler 29 | event: TYPO3\CMS\Core\Core\Event\BootCompletedEvent 30 | -------------------------------------------------------------------------------- /Configuration/TCA/Overrides/sys_template.php: -------------------------------------------------------------------------------- 1 | #i 56 | replace.char = 10 57 | useRegExp = 1 58 | } 59 | } 60 | } 61 | 62 | listonly { 63 | replacement { 64 | 10 { 65 | search.char = 13 66 | replace = 67 | } 68 | 69 | 20 { 70 | search.char = 10 71 | replace = 72 | } 73 | 74 | 30 { 75 | search = #\s+# 76 | replace.char = 32 77 | useRegExp = 1 78 | } 79 | 80 | 40 { 81 | search = ##i 82 | replace { 83 | char = 10 84 | wrap = | 85 | } 86 | useRegExp = 1 87 | } 88 | 89 | 50 { 90 | search = #]*>([^<]+)#i 91 | replace = ${1} 92 | useRegExp = 1 93 | } 94 | } 95 | } 96 | } 97 | 98 | # Example processing of content before it is being persisted to database. For all types, use = * 99 | # contentPersistPreProcessing { 100 | # { 101 | # { 102 | # { 103 | # # Any stdWrap property 104 | # } 105 | # } 106 | # } 107 | # } 108 | 109 | customRecordEditing { 110 | tx_news_pi1 { 111 | actionName = detail 112 | recordName = news 113 | tableName = tx_news_domain_model_news 114 | listTypeName = news_pi1 115 | } 116 | } 117 | 118 | # Name of the field that stores the uid of the parent of nested CEs 119 | # used to add a drop zone before the first nested CEs in a column of a container. 120 | # Configured as default for EXT:container 121 | parentOfCeUidField = tx_container_parent 122 | } 123 | } 124 | 125 | # Prevent HTML output for wrapContent to be rendered as string only 126 | lib.parseFunc_RTE.nonTypoTagStdWrap.encapsLines > 127 | [global] 128 | -------------------------------------------------------------------------------- /Documentation/ConfigureAndExtend/EmptyColumns/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | 4 | 5 | .. _emptycolumns: 6 | 7 | ============================ 8 | Drop zones for empty columns 9 | ============================ 10 | 11 | Premise 12 | ~~~~~~~ 13 | The Frontend Editing extension automatically adds one drop zone at the beginning and one at the end of each content element so the user can drop a new content element before or after an existing content element. 14 | 15 | When a page or container is empty, there are no drop zones to allow the user to add new content elements. The Frontend Editing extension can't automatically add the first drop zone because it can't know the colPos value when the frontend is rendered. 16 | 17 | To solve this issue, the admin can add a custom drop zone in the page or container templates. 18 | 19 | .. _emptycolumns-page: 20 | 21 | Example of custom drop zone for a column of a page 22 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 23 | 24 | .. code-block:: html 25 | 26 | 27 | 28 | The needed argument in this case are: 29 | 30 | * the table (tt_content) 31 | * the page uid 32 | * the colPos in which the element will be placed 33 | 34 | .. _emptycolumns-container: 35 | 36 | Example of custom drop zone for a column of a container (EXT:container) 37 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 38 | 39 | .. code-block:: html 40 | 41 | 42 | 43 | The needed argument in this case are: 44 | 45 | * the table of the CE (tt_content) 46 | * the page uid 47 | * the colPos of the column of the container in which the element will be placed as default value (defVals) 48 | * in the case of EXT:container, the uid of the parent (field: tx_container_parent) as default value (defVals) 49 | -------------------------------------------------------------------------------- /Documentation/ConfigureAndExtend/ExtensionConfiguration/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | 4 | 5 | .. _extension-manager-settings: 6 | 7 | ================================== 8 | Available extension configurations 9 | ================================== 10 | 11 | These configurations can be found in *Admin Tools > Settings*. 12 | 13 | .. _extension-manager-settings-basic: 14 | 15 | 16 | Basic 17 | ===== 18 | 19 | The tag name that will be used for the Content editable wrapper 20 | --------------------------------------------------------------- 21 | 22 | :aspect:`DataType` 23 | string 24 | 25 | :aspect:`Default` 26 | div 27 | 28 | The default wrapper tag name used by the frontend editing functionality is 29 | `div`, but you can change it to something else here. 30 | 31 | .. tip:: 32 | 33 | You can override the default tag using the 34 | :ref:`core:contentEditable ViewHelper's ` optional 35 | tag argument. 36 | 37 | 38 | .. _extension-manager-settings-features: 39 | 40 | Features 41 | ======== 42 | 43 | Enable placeholders and direct drop-to-edit without modal 44 | --------------------------------------------------------- 45 | 46 | :aspect:`DataType` 47 | boolean 48 | 49 | :aspect:`Default` 50 | true 51 | 52 | This feature makes empty editable content areas appear with placeholders. Content elements will also appear directly after you have dropped them on the page, skipping the modal pop-up with a backend form that will otherwise appear. 53 | 54 | You should adapt your templates to show empty fields using the :ref:`viewhelpers-isplaceholderenabled` view helper. 55 | -------------------------------------------------------------------------------- /Documentation/ConfigureAndExtend/HooksAndEvents/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. highlight:: rst 4 | 5 | .. _hooksandevents: 6 | 7 | 8 | ================ 9 | Hooks and events 10 | ================ 11 | 12 | 13 | .. _events: 14 | 15 | PSR-14 Events 16 | ============= 17 | 18 | .. _events-preparefieldupdateevent: 19 | 20 | PrepareFieldUpdateEvent 21 | ----------------------- 22 | 23 | :aspect:`Event Class` 24 | \TYPO3\CMS\FrontendEditing\Controller\Event\PrepareFieldUpdateEvent 25 | 26 | :aspect:`Event Handler Interface` 27 | \TYPO3\CMS\FrontendEditing\Controller\Event\PrepareFieldUpdateEventHandlerInterface 28 | 29 | Called before saved data from Frontend Editing is persisted to the database. 30 | 31 | 32 | Example event handler 33 | ~~~~~~~~~~~~~~~~~~~~~ 34 | 35 | .. code-block:: php 36 | 37 | setContent(utf8_decode($event->getContent())); 51 | } 52 | } 53 | 54 | 55 | .. _hooks: 56 | 57 | Hooks 58 | ===== 59 | 60 | This is used in case you need to influence on a process of wrapping with drop zone of some specific content elements 61 | 62 | - Register your hook in ext_localconf.php 63 | 64 | .. code-block:: php 65 | 66 | pageRenderer->loadRequireJsModule('TYPO3/CMS/FrontendEditing/Utils/TranslatorLoader', "function(TranslatorLoader) { 37 | TranslatorLoader.configure({ 38 | translationLabels: ${translationLabelMap}, 39 | namespaceMapping: ${translationLabelMapping}, 40 | }); 41 | }"); 42 | 43 | No merge strategy is used in the frontend editing default bootstrap. So if it 44 | was configured before, no server side `translationLabels` get configured. 45 | Instead it uses the default fallback implementation. 46 | 47 | Be aware that the `namespaceMapping` configuration property may change without 48 | deprecation warning since it is not stable. 49 | -------------------------------------------------------------------------------- /Documentation/ConfigureAndExtend/TCA/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | 4 | .. _tca: 5 | 6 | ========================= 7 | Additions to standard TCA 8 | ========================= 9 | 10 | Frontend Editing adds some configuration options to the TCA that can be used to 11 | make fields behave in a specific manner when editing them in the frontend. 12 | 13 | 14 | .. _tca-fielddefinitions: 15 | 16 | Field definitions 17 | ================= 18 | 19 | Found in :php:`$GLOBALS['TCA'][
]['columns'][]` 20 | 21 | 22 | .. _tca-enablefrontendrichtext: 23 | 24 | [config][enableFrontendRichtext] 25 | -------------------------------- 26 | 27 | :aspect:`DataType` 28 | boolean 29 | 30 | Enable rich-text editing in FrontendEditing only. 31 | 32 | Inherits and overrides the value from `[config][enableRichtext]`. 33 | 34 | 35 | .. _tca-frontendrichtextconfiguration: 36 | 37 | [config][frontendRichtextConfiguration] 38 | --------------------------------------- 39 | 40 | :aspect:`DataType` 41 | string 42 | 43 | Set or change the rich-text editing preset in FrontendEditing only. 44 | 45 | Inherits and overrides the value from `[config][richtextConfiguration]`. 46 | -------------------------------------------------------------------------------- /Documentation/ConfigureAndExtend/TypoScript/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | 4 | .. _typoscript: 5 | 6 | ==================== 7 | Module Configuration 8 | ==================== 9 | 10 | Each section refers to property names within `module.tx_frontendediting`. 11 | 12 | 13 | .. _typoscript-customrecords: 14 | 15 | customRecords 16 | ============= 17 | 18 | :aspect:`DataType` 19 | array of table names with default values. 20 | 21 | Default values for records created with the 22 | :ref:`customDropZone ` ViewHelper. 23 | 24 | .. code-block:: typoscript 25 | 26 | module.tx_frontendediting.settings { 27 | customRecords { 28 | tx_news_domain_model_news { 29 | pid = 6 30 | } 31 | } 32 | } 33 | 34 | 35 | .. _typoscript-customrecordediting: 36 | 37 | customRecordEditing 38 | =================== 39 | 40 | Configure :ref:`typoscript-custom-record-editing`. 41 | 42 | .. code-block:: typoscript 43 | 44 | config.tx_frontendediting { 45 | customRecordEditing { 46 | tx_news_pi1 { 47 | actionName = detail 48 | recordName = news 49 | tableName = tx_news_domain_model_news 50 | listTypeName = news_pi1 51 | } 52 | } 53 | } 54 | 55 | ======================== 56 | TypoScript Configuration 57 | ======================== 58 | 59 | Each section refers to property names within `config.tx_frontendediting`. 60 | 61 | 62 | .. _typoscript-contentpersistpreprocessing: 63 | 64 | contentPersistPreProcessing 65 | =========================== 66 | 67 | Modify data for a specific table, field, and record type when saved in Frontend 68 | Editing before the data is persisted to the database. This allows you to 69 | remove any (or all) HTML tags or modify the data to better suit the way it 70 | should be persisted. 71 | 72 | This property consists of nested arrays. 73 | 74 | .. code-block:: typoscript 75 | 76 | config.tx_frontendediting { 77 | contentPersistPreProcessing { 78 | { 79 | { 80 | { 81 | # Any stdWrap property 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | :aspect:`` 89 | The name of the table. 90 | 91 | :aspect:`` 92 | The record type as defined in :php:`$GLOBALS['TCA'][]['types']`. 93 | `0` (zero as string) is the default type. `*` is a wildcard that will apply 94 | to any type not explicitly defined. 95 | 96 | :aspect:`` 97 | The field name. Add any :ref:`stdwrap ` configurations to 98 | modify the data. You can also use the :ref:`userFunc ` 99 | property to modify data using PHP. 100 | 101 | Example 1 102 | --------- 103 | 104 | Strip all HTML tags from the `bodytext` field in the `tt_content` table if the 105 | record type (e.g. the `CType` field for content elements) is "bullets". 106 | 107 | .. code-block:: typoscript 108 | 109 | config.tx_frontendediting { 110 | contentPersistPreProcessing { 111 | tt_content { 112 | bullets { 113 | bodytext { 114 | stripHtml = 1 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | Example 2 122 | --------- 123 | 124 | Convert the date of a news item from the frontend display format dd/mm/yyyy 125 | to the ISO 8601 format YYYY-MM-DDT00:00:00Z, so that it can be saved correctly 126 | in the database when the editor changes it. 127 | 128 | .. code-block:: typoscript 129 | 130 | config.tx_frontendediting { 131 | contentPersistPreProcessing { 132 | tx_news_domain_model_news { 133 | 0 { 134 | datetime { 135 | replacement { 136 | 10 { 137 | search = /([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{4})/ 138 | replace = \3-\2-\1T00:00:00Z 139 | useRegExp = 1 140 | } 141 | } 142 | } 143 | } 144 | } 145 | } 146 | } 147 | 148 | See also :ref:`typoscript-contentpersistpreprocessingpatterns`. 149 | 150 | 151 | .. _typoscript-contentpersistpreprocessingpatterns: 152 | 153 | contentPersistPreProcessingPatterns 154 | =================================== 155 | 156 | Modify data for any field with a specific RTE preset before the data is 157 | persisted to the database. This allows you to remove any (or all) HTML tags or 158 | modify the data to better suit the way it should be persisted. 159 | 160 | .. code-block:: typoscript 161 | 162 | config.tx_frontendediting { 163 | contentPersistPreProcessingPatterns { 164 | { 165 | replacement { 166 | 10 { 167 | search = ##i 168 | replace.char = 10 169 | useRegExp = 1 170 | } 171 | } 172 | } 173 | } 174 | } 175 | 176 | :aspect:`` 177 | An RTE preset defined in 178 | :php:`$GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']`. Add any 179 | :ref:`stdwrap ` configurations to modify the data. The modification is 180 | applied to any field where `[config][enableRichtext]` or 181 | `[config][enableFrontendRichtext]` is set and 182 | `[config][frontendRichtextConfiguration]` or 183 | `[config][frontendRichtextConfiguration]` is set to the same as ``. 184 | 185 | 186 | Example 187 | ------- 188 | 189 | Strip all HTML tags from any field using the RTE preset "default" when saving 190 | data in Frontend Editing. 191 | 192 | .. code-block:: typoscript 193 | 194 | config.tx_frontendediting { 195 | contentPersistPreProcessing { 196 | default { 197 | stripHtml = 1 198 | } 199 | } 200 | } 201 | 202 | 203 | See also :ref:`typoscript-contentpersistpreprocessing`. This property is only 204 | applied if no match was found there. 205 | -------------------------------------------------------------------------------- /Documentation/ConfigureAndExtend/UserTs/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | 4 | 5 | .. _userts: 6 | 7 | ====== 8 | UserTS 9 | ====== 10 | 11 | It's possible to configure TSconfig for BE user or BE user group. 12 | 13 | Disallow editing for content elements 14 | ===================================== 15 | 16 | Possibility to disallow a user or a user group editing to a specific set of content elements. 17 | A comma separated list of content id´s are provided, this setting is ignored for admin users. 18 | 19 | .. code-block:: typoscript 20 | 21 | frontend_editing.disallow_content_editing = 29,30 22 | -------------------------------------------------------------------------------- /Documentation/ConfigureAndExtend/ViewHelpers/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _viewhelpers: 4 | 5 | ==================== 6 | ViewHelper Reference 7 | ==================== 8 | 9 | .. tip:: 10 | 11 | The Frontend Editing extension automatically includes its ViewHelpers in the 12 | `core` namespace, so you don't have to declare the namespace. 13 | 14 | .. _viewhelpers-contenteditable: 15 | 16 | contentEditable 17 | =============== 18 | 19 | Defines editable regions within Fluid templates. 20 | 21 | Arguments 22 | --------- 23 | 24 | table 25 | ~~~~~ 26 | 27 | :aspect:`DataType` 28 | string 29 | 30 | :aspect:`Required` 31 | true 32 | 33 | :aspect:`Description` 34 | The database table name to where the data should be saved. 35 | 36 | field 37 | ~~~~~ 38 | 39 | :aspect:`DataType` 40 | string 41 | 42 | :aspect:`Required` 43 | false 44 | 45 | :aspect:`Description` 46 | The database field to where the data should be saved. 47 | 48 | uid 49 | ~~~ 50 | 51 | :aspect:`DataType` 52 | string 53 | 54 | :aspect:`Required` 55 | true 56 | 57 | :aspect:`Description` 58 | The database field to where the data should be saved 59 | 60 | tag 61 | ~~~ 62 | 63 | :aspect:`DataType` 64 | string 65 | 66 | :aspect:`Required` 67 | false 68 | 69 | :aspect:`Description` 70 | The HTML tag to use for the editable wrapper. 71 | 72 | additionalAttributes 73 | ~~~~~~~~~~~~~~~~~~~~ 74 | 75 | :aspect:`DataType` 76 | mixed 77 | 78 | :aspect:`Required` 79 | false 80 | 81 | :aspect:`Description` 82 | Additional tag attributes. They will be added directly to the resulting HTML tag. 83 | 84 | data 85 | ~~~~ 86 | 87 | :aspect:`DataType` 88 | mixed 89 | 90 | :aspect:`Required` 91 | false 92 | 93 | :aspect:`Description` 94 | Additional data-* attributes. They will each be added with a "data-" prefix. 95 | 96 | Basic example 97 | ------------- 98 | 99 | This makes the field `bodytext` of the table `tt_content` editable: 100 | 101 | .. code-block:: html 102 | 103 | 104 | {bodytext} 105 | 106 | 107 | When frontend editing is enabled and the user is viewing the page in the 108 | Frontend Editing module in the backend, the output HTML will look something like 109 | this: 110 | 111 | .. code-block:: html 112 | 113 |
114 | This is the content text to edit 115 |
116 | 117 | When the user is viewing the page anywhere else than in the Frontend Editing 118 | module, or if Frontend Editing is disabled, the output HTML will look like this: 119 | 120 | .. code-block:: html 121 | 122 | This is the content text to edit 123 | 124 | Example with custom tag 125 | ----------------------- 126 | 127 | Using the `tag` argument, you can make a HTML tag into an editable region. In 128 | this case we are using an `
    ` tag to make the list items within it editable. 129 | 130 | .. code-block:: html 131 | 132 | 133 | 134 |
  • {item}
  • 135 |
    136 |
    137 | 138 | When frontend editing is enabled and the user is viewing the page in the 139 | Frontend Editing module in the backend, the output HTML will look something like 140 | this: 141 | 142 | .. code-block:: html 143 | 144 |
      145 |
    • Item 1
    • 146 |
    • Item 2
    • 147 |
    • Item 3
    • 148 | 149 | 150 | When the user is viewing the page anywhere else than in the Frontend Editing 151 | module, or if Frontend Editing is disabled, the output HTML will look like this: 152 | 153 | .. code-block:: html 154 | 155 |
        156 |
      • Item 1
      • 157 |
      • Item 2
      • 158 |
      • Item 3
      • 159 |
      160 | 161 | There is also a possibility to make the content element editable through the popup backend editor. 162 | It is done by skipping the *field* option in the view helper: 163 | 164 | .. code-block:: html 165 | 166 | 167 | {item.bodytext} 168 | 169 | 170 | 171 | .. _viewhelpers-customdropzone: 172 | 173 | customDropZone 174 | =============== 175 | 176 | Inserts a custom drop zone, for example to drop news directly into the news 177 | list. 178 | 179 | Default values are defined in TypoScript. See :ref:`typoscript-customrecords` 180 | for more information. 181 | 182 | Arguments 183 | --------- 184 | 185 | tables 186 | ~~~~~~ 187 | 188 | :aspect:`DataType` 189 | array of strings 190 | 191 | :aspect:`Required` 192 | true 193 | 194 | :aspect:`Description` 195 | Table names allowed to be dropped in this drop zone. 196 | 197 | Example 198 | ------- 199 | 200 | .. code-block:: html 201 | 202 | 203 | 204 | 205 | The result will look like this: 206 | 207 | .. figure:: ../../Images/CustomRecordsDropzone.png 208 | :alt: Custom records for dropzones 209 | 210 | 211 | .. _viewhelpers-isfrontendeditingactive: 212 | 213 | 214 | isFrontendEditingActive 215 | ======================= 216 | 217 | Useful to determine whether or not frontend editing is active. Use in conditions 218 | to hide or show content for editors. 219 | 220 | Example 221 | ------- 222 | 223 | .. code-block:: html 224 | 225 | 226 |

      You're using Frontend Editing. Congratulations!

      227 |
      228 | 229 | No output if Frontend editing is disabled. Output if Frontend Editing is 230 | enabled: 231 | 232 | .. code-block:: html 233 | 234 |

      You're using Frontend Editing. Congratulations!

      235 | 236 | 237 | .. _viewhelpers-isplaceholderenabled: 238 | 239 | isPlaceholderEnabled 240 | ==================== 241 | 242 | Use this view helper in conditions to show empty fields when the 243 | :ref:`placeholder feature ` is enabled. 244 | 245 | Example 246 | ------- 247 | 248 | .. code-block:: html 249 | 250 | 251 | {header} 252 | 253 | -------------------------------------------------------------------------------- /Documentation/Development/Dependencies/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. highlight:: rst 4 | 5 | 6 | Dependencies 7 | ============ 8 | 9 | We rely on node.js for a lot of our tooling. Go to http://nodejs.org/ and install it. 10 | 11 | To install the tooling dependencies, run: 12 | 13 | :: 14 | 15 | npm install 16 | 17 | Composer must also be installed. Download and install it from https://getcomposer.org/, then install the Composer dependencies: 18 | 19 | :: 20 | 21 | composer install 22 | -------------------------------------------------------------------------------- /Documentation/Development/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. highlight:: rst 4 | 5 | =========== 6 | Development 7 | =========== 8 | 9 | Instructions on how to develop the frontend editing extension 10 | 11 | .. toctree:: 12 | :titlesonly: 13 | 14 | Dependencies/Index 15 | Workflow/Index 16 | JavaScript/Index 17 | -------------------------------------------------------------------------------- /Documentation/Development/JavaScript/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | 4 | .. _javascriptdev: 5 | 6 | Javascript 7 | ================== 8 | 9 | .. _javascriptdev-logger: 10 | 11 | Logger 12 | ------ 13 | The `ulog` logging framework is used to handle logs. It is a highly configurable 14 | and extendable logger. 15 | 16 | Persist log record on Typo3 server is not implemented yet. 17 | 18 | Loggers available: 19 | 20 | * `FEditing:Component:LoadingScreen` 21 | * `FEditing:Component:Widget:Notification` 22 | * `FEditing:CRUD` 23 | * `FEditing:Editor` 24 | * `FEditing:FrontendEditing` 25 | * `FEditing:GUI` 26 | * `FEditing:localStorage` 27 | * `FEditing:Utils:Scroller` 28 | * `FEditing:Utils:TranslatorFactory` 29 | * `FEditing:Utils:TranslatorLoader` 30 | 31 | 32 | .. _javascriptdev-logger-level: 33 | 34 | Log Levels 35 | ---------- 36 | 37 | Following log levels exists: 38 | 39 | * `error` : Used if an exception occured and the normal handling failed. 40 | * `warn`: Also used if an exception occured, but the normal handling can be 41 | done. 42 | * `info`: Used if an state change occured like an event (eg. mouse click) 43 | * `log`: Used if an point of interesse is reached like the an array to loop. 44 | * `debug`: Used if an calculation happend. 45 | * `trace` (Not implemented yet): Used if an function get called and end. 46 | Only useful to record the call chains. 47 | 48 | There are some special log levels only used to configure ulog: 49 | 50 | * `none`: No logs get selected 51 | * `all`: Normaly it is like `trace`. But if `ulog` log levels get extended it 52 | is much more save to use `all`. 53 | 54 | **Hint:** The log level during log selection in configuration are chained. Use 55 | `warn` log level does indirectly also select `error` logs. 56 | 57 | 58 | .. _javascriptdev-logger-config: 59 | 60 | Configuration 61 | ------------- 62 | 63 | Configuration can be done as URL parameter or/and as localStorage item. 64 | There are follwing configuration options: 65 | 66 | * `log`: The main setting to control logger’s levels with 67 | * `log_output`: To configure the output of the `output` channel, where 68 | logging should go 69 | * `log_drain`: To configure the output of the `drain` channel 70 | 71 | More details can be found at: https://ulog.js.org/#configure 72 | 73 | 74 | .. _javascriptdev-logger-config-syntax: 75 | 76 | Syntax 77 | ------ 78 | 79 | ` = [; ] ...` 80 | Configuration options can have more than one configuration entry: 81 | 82 | ` := [ = ] ` 83 | Configuration entry have a value and a filter to select the loggers to apply the config: 84 | **Hint:** If no filter or a wildcard is set, the config is used as default config. 85 | 86 | ` := [ , [ - ] ] ...` 87 | The filter is a list of RegEx with option to add or substract loggers from the selection set: 88 | 89 | .. attention:: 90 | 91 | * in `log` the `` is a log level 92 | * in `log_` the `` is one or more `output` 93 | 94 | 95 | .. _javascriptdev-logger-config-example: 96 | 97 | Examples 98 | -------- 99 | 100 | With URL parameters: 101 | 102 | * to log in general info level: `?log=info` (Technically, this is equals to: 103 | `?log=*=info`.) 104 | * To log widgets in debug and the rest in info mode: 105 | `?log=info;FEditing:Component:Widget:*=debug` 106 | * To print and persist log in output channel: `?log_ouput=console persist` 107 | * To print logs in general and persist logs from widgets in output channel: 108 | `?log_ouput=console;FEditing:Component:Widget:*=console persist` 109 | * To print logs in general and only persist logs from widgets in output 110 | channel: `?log_ouput=console;FEditing:Component:Widget:*=persist` 111 | 112 | 113 | .. _javascriptdev-logger-output: 114 | 115 | Output 116 | ------ 117 | 118 | By default there are two outputs: 119 | 120 | * `console`: default browser `console` 121 | * `noop`: No operation function, used to prevent log in `drain` channel 122 | 123 | 124 | .. _javascriptdev-logger-channel: 125 | 126 | Channel 127 | ------- 128 | 129 | By default there are two channels: 130 | 131 | * `output`: Used if `log` configuration is matching. eg. `log=info` and 132 | `logRecord.level` is smaller than info. 133 | * `drain`: Opposite to `output` channel. Used to have the opportunity to 134 | handle this log records to like persist in indexedDB. 135 | 136 | 137 | .. _javascriptdev-logger-extension: 138 | 139 | Extensions 140 | ---------- 141 | 142 | There are two extensions as AMD available to use: 143 | 144 | * `TYPO3/CMS/FrontendEditing/Utils/LoggerPersist` 145 | Add two output `Persist`, `Server` and a new channel `highorder` 146 | * `TYPO3/CMS/FrontendEditing/Utils/LoggerWindowError` 147 | Register window error handlers to log them with a logger. 148 | 149 | 150 | .. _javascriptdev-logger-extension-highorder: 151 | 152 | `highorder` channel 153 | ------------------- 154 | 155 | There are two new configuration options: 156 | 157 | * `highorder_log`: The main setting to control `highorder` channel selection. 158 | *Hint:* Analog to the `log` option and the `output` channel 159 | * `log_highorder`: To configure the output of the `highorder` channel, where 160 | logging should go 161 | -------------------------------------------------------------------------------- /Documentation/Development/Workflow/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. highlight:: rst 4 | 5 | ======== 6 | Workflow 7 | ======== 8 | 9 | For working with the extension, the following can be run to accomplish common tasks. 10 | 11 | Test 12 | ---- 13 | 14 | Run the following command to execute the PHP codesniffer: 15 | 16 | :: 17 | 18 | npm run php:codesniffer 19 | 20 | Run the following command to run the PHP Unit tests: 21 | 22 | 23 | :: 24 | 25 | npm run php:unittests 26 | 27 | Run this command to simulate the full build process locally: 28 | 29 | :: 30 | 31 | npm run build:suite --silent 32 | 33 | Add node_modules to Public/Resources folder 34 | ------------------------------------------- 35 | 36 | Add the toastr (notifications) node_module to Public/Resources folder: 37 | 38 | :: 39 | 40 | npm run add:resource:toastr 41 | 42 | Add the immutable (https://facebook.github.io/immutable-js/) node_module to Public/Resources folder: 43 | 44 | :: 45 | 46 | npm run add:resource:immutable 47 | 48 | Styling 49 | ------- 50 | 51 | The extension is using SASS. The build-process order is: 52 | 53 | * CSS linting, based on TYPO3.CMS (stylelint) 54 | * Compile to CSS (node-sass) 55 | * Add vendor prefixes, based on TYPO3.CMS (autoprefixer) 56 | * Minifying CSS (postcss-clean) 57 | 58 | Use the following watch command while developing: 59 | 60 | :: 61 | 62 | npm run watch:css 63 | 64 | Use the following command to build the stylesheets: 65 | 66 | :: 67 | 68 | npm run build:css 69 | 70 | 71 | Publish 72 | ------- 73 | 74 | Use the following command to copy all necessary node_modules into Public/Resources folder and compile the SASS: 75 | 76 | :: 77 | 78 | npm run build:extension 79 | -------------------------------------------------------------------------------- /Documentation/Images/AddTypoScript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfTYPO3/frontend_editing/25352da0585179bfefe44972c2bf9f64303b3bd0/Documentation/Images/AddTypoScript.png -------------------------------------------------------------------------------- /Documentation/Images/CustomRecordsDropzone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfTYPO3/frontend_editing/25352da0585179bfefe44972c2bf9f64303b3bd0/Documentation/Images/CustomRecordsDropzone.png -------------------------------------------------------------------------------- /Documentation/Images/ExtensionManagerSettingsOverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfTYPO3/frontend_editing/25352da0585179bfefe44972c2bf9f64303b3bd0/Documentation/Images/ExtensionManagerSettingsOverview.png -------------------------------------------------------------------------------- /Documentation/Images/FullEditorInBackend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfTYPO3/frontend_editing/25352da0585179bfefe44972c2bf9f64303b3bd0/Documentation/Images/FullEditorInBackend.png -------------------------------------------------------------------------------- /Documentation/Images/add-typoscript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfTYPO3/frontend_editing/25352da0585179bfefe44972c2bf9f64303b3bd0/Documentation/Images/add-typoscript.png -------------------------------------------------------------------------------- /Documentation/Includes.rst.txt: -------------------------------------------------------------------------------- 1 | .. More information about this file: 2 | https://docs.typo3.org/m/typo3/docs-how-to-document/main/en-us/GeneralConventions/FileStructure.html#includes-rst-txt 3 | 4 | .. ---------- 5 | .. text roles 6 | .. ---------- 7 | 8 | .. role:: aspect(emphasis) 9 | .. role:: bash(code) 10 | .. role:: html(code) 11 | .. role:: js(code) 12 | .. role:: php(code) 13 | .. role:: rst(code) 14 | .. role:: sep(strong) 15 | .. role:: sql(code) 16 | 17 | .. role:: tsconfig(code) 18 | :class: typoscript 19 | 20 | .. role:: typoscript(code) 21 | .. role:: xml(code) 22 | :class: html 23 | 24 | .. role:: yaml(code) 25 | 26 | .. default-role:: code 27 | 28 | .. --------- 29 | .. highlight 30 | .. --------- 31 | 32 | .. By default, code blocks use PHP syntax highlighting 33 | 34 | .. highlight:: php 35 | -------------------------------------------------------------------------------- /Documentation/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | ================ 4 | Frontend Editing 5 | ================ 6 | 7 | :Extension key: 8 | frontend_editing 9 | 10 | :Package name: 11 | friendsoftypo3/frontend-editing 12 | 13 | :Version: 14 | |release| 15 | 16 | :Language: 17 | en 18 | 19 | :Author: 20 | TYPO3 Community 21 | 22 | :License: 23 | This document is published under the 24 | `Open Content License `__. 25 | 26 | :Rendered: 27 | |today| 28 | 29 | ---- 30 | 31 | This extension enhances the TYPO3 CMS with the possibility of frontend editing 32 | with `CKeditor`_, the rich text editor of the TYPO3 backend. 33 | 34 | .. _CKeditor: https://ckeditor.com/ 35 | 36 | ---- 37 | 38 | **Table of Contents:** 39 | 40 | .. toctree:: 41 | :maxdepth: 2 42 | :titlesonly: 43 | 44 | Introduction/Index 45 | Installation/Index 46 | InlineEditing/Index 47 | ConfigureAndExtend/Index 48 | NewContentElements/Index 49 | Development/Index 50 | KnownIssues/Index 51 | 52 | .. Meta Menu 53 | 54 | .. toctree:: 55 | :hidden: 56 | 57 | Sitemap 58 | genindex 59 | -------------------------------------------------------------------------------- /Documentation/Installation/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | 4 | 5 | .. _installation: 6 | 7 | ============ 8 | Installation 9 | ============ 10 | 11 | Required steps 12 | ============== 13 | 14 | The following steps are required to use frontend editing in a TYPO3 installation. 15 | 16 | * Install the extension 17 | 18 | #. **Using Composer** (recommended): 19 | 20 | .. code-block:: bash 21 | 22 | composer req friendsoftypo3/frontend-editing 23 | 24 | or by downloading the extension. 25 | 26 | #. **Using the Extension Manager** in *Admin Tools > Extensions* as explained 27 | in the :ref:`Extension Installation ` 28 | chapter of the official TYPO3 documentation. 29 | 30 | * Add the static TypoScript template *Frontend Editing* to the site roots where the features should be activated 31 | 32 | .. figure:: ../Images/AddTypoScript.png 33 | :alt: TYPO3 frontend editing TyposScript 34 | 35 | .. _optional-settings: 36 | 37 | Optional steps 38 | ============== 39 | 40 | * Set the baseUrl for frontend editing if server path is not a top directory. This is done by adding the following part to setup typoscript: 41 | 42 | .. code-block:: typoscript 43 | 44 | plugin.tx_frontendediting.baseUrl = / 45 | 46 | * Include the static template *Editable Fluid Styled Content v11*, to include basic editable templates for Fluid Styled Content in TYPO3 v11. 47 | 48 | * Disable the :ref:`placeholder feature ` in the extension configuration. By disabling this feature, empty editable content areas will no longer with placeholders. When creating a new content element by dragging and dropping onto the page, a modal pop-up with a backend form will appear so that you can fill in the initial content. 49 | -------------------------------------------------------------------------------- /Documentation/Introduction/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | 4 | 5 | .. _introduction: 6 | 7 | 8 | Introduction 9 | ============ 10 | 11 | 12 | .. _about-this-document: 13 | 14 | About this extension 15 | -------------------- 16 | 17 | This package adds frontend editing capability to TYPO3 CMS, using `CKEditor `__, the rich text editor used by the TYPO3 core. The editor appears as a new backend module. When selecting a page in the page tree, the page becomes editable. Editable regions are configured in the templates, using Fluid or TypoScript. 18 | 19 | .. figure:: ../Images/FullEditorInBackend.png 20 | :alt: Screenshot of the Frontend Editor in the TYPO3 backend. 21 | 22 | Frontend Editing in the TYPO3 Backend. 23 | 24 | .. _credits: 25 | 26 | Credits 27 | ------- 28 | 29 | Initial parts of this manual was based on :doc:`Inside TYPO3 `. It was adjusted and 30 | updated to fit with the frontend_editing modules as found in current versions. 31 | 32 | The :ref:`placeholder feature ` makes use of the `confighelper `__ CKEditor plugin by AlfonsoML. The plugin is released under `Mozilla Public License 2.0 `__. 33 | 34 | 35 | .. _feedback: 36 | 37 | Feedback 38 | -------- 39 | 40 | If you find a bug either in this manual or in the frontend editing / 41 | extension please use the `bug tracker `__. 42 | 43 | .. _contribution: 44 | 45 | Contribution 46 | ------------ 47 | 48 | `Pull requests `__ are very welcome indeed. Please feel free to contribute features, bug fixes, and improvements. 49 | 50 | `Donate to the project through PayPal `__ 51 | 52 | -------------------------------------------------------------------------------- /Documentation/KnownIssues/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | 4 | 5 | .. _known_issues: 6 | 7 | Known issues 8 | ------------ 9 | 10 | We are aware of a few issues that can affect the user experience. By explaining the issues here, we hope you will be able to circumvent them until they are fixed. 11 | 12 | Blank page when disableNoCacheParameter is set 13 | """""""""""""""""""""""""""""""""""""""""""""" 14 | 15 | Pages may not render properly (blank page) if the setting `[FE][disableNoCacheParameter]` is enabled when using the Frontend Editor. 16 | 17 | Route Enhancers 18 | """"""""""""""" 19 | 20 | Nice urls may not render properly when using a Route Enhancer for a record. The following error may occur `#1537633463 OutOfRangeException Hash not resolvable`. 21 | To solve this add the following setting `[FE][pageNotFoundOnCHashError] = false` in LocalConfiguration.php. 22 | 23 | Other issues 24 | """""""""""" 25 | 26 | A full list of bugs can be found in our `issue tracker `__. 27 | -------------------------------------------------------------------------------- /Documentation/NewContentElements/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | 4 | 5 | .. _content-elements: 6 | 7 | Content elements 8 | ---------------- 9 | 10 | New Content Elements 11 | """""""""""""""""""" 12 | 13 | It's possible to add drop zones for new content elements in a custom content elements. This is done by the class called ContentEditableWrapperService. 14 | 15 | - Example of usage. 16 | 17 | .. code-block:: typoscript 18 | 19 | page = PAGE 20 | page.1001 = USER 21 | page.1001 { 22 | userFunc = Your\NameSpace\YourWrappingClass->wrapWithDropZone 23 | } 24 | 25 | - Create your PHP class with user function 26 | 27 | .. code-block:: php 28 | 29 | wrapContentWithDropzone( 50 | 'tt_content', // table name 51 | $uid, // CE uid 52 | -1, // page uid, pid 53 | $content, 54 | 0, // colPos 55 | // additional fields if needed 56 | [ 57 | 'subheader' => 'default subheader' 58 | ] 59 | ); 60 | } 61 | 62 | return $content; 63 | } 64 | } 65 | 66 | .. _custom-dropzone-modifier: 67 | 68 | Custom Dropzone modifier (using frontend editing together with Grid Elements) 69 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 70 | 71 | Here is a full example of how to use the hook (wrapWithDropZone) together with 72 | `Grid Elements`_ and multi column splitters: 73 | 74 | .. _Grid Elements: https://extensions.typo3.org/extension/gridelements 75 | 76 | https://gist.github.com/joekolade/674ecba5c2615901581d6c4e4c272b4a 77 | -------------------------------------------------------------------------------- /Documentation/Settings.cfg: -------------------------------------------------------------------------------- 1 | # More information about this file: 2 | # https://docs.typo3.org/m/typo3/docs-how-to-document/main/en-us/GeneralConventions/FileStructure.html#settings-cfg 3 | 4 | [general] 5 | 6 | project = Frontend Editing 7 | version = main (development) 8 | release = main (development) 9 | copyright = since 2016 by the TYPO3 contributors 10 | 11 | [html_theme_options] 12 | 13 | # "Edit on GitHub" button 14 | github_repository = FriendsOfTYPO3/frontend_editing 15 | github_branch = master 16 | 17 | # Footer links 18 | project_home = https://extensions.typo3.org/extension/frontend_editing/ 19 | project_contact = https://typo3.slack.com/archives/C9WGPPKK9 20 | project_repository = https://github.com/FriendsOfTYPO3/frontend_editing 21 | project_issues = https://github.com/FriendsOfTYPO3/frontend_editing/issues 22 | project_discussions = 23 | 24 | use_opensearch = 25 | 26 | [intersphinx_mapping] 27 | 28 | # Official TYPO3 manuals 29 | # h2document = https://docs.typo3.org/m/typo3/docs-how-to-document/main/en-us/ 30 | # t3cheatsheets = https://docs.typo3.org/m/typo3/docs-cheatsheets/main/en-us/ 31 | # t3contribute = https://docs.typo3.org/m/typo3/guide-contributionworkflow/main/en-us/ 32 | t3coreapi = https://docs.typo3.org/m/typo3/reference-coreapi/11.5/en-us/ 33 | # t3docteam = https://docs.typo3.org/m/typo3/team-t3docteam/main/en-us/ 34 | # t3editors = https://docs.typo3.org/m/typo3/tutorial-editors/main/en-us/ 35 | # t3extbasebook = https://docs.typo3.org/m/typo3/book-extbasefluid/main/en-us/ 36 | # t3extexample = https://docs.typo3.org/m/typo3/guide-example-extension-manual/main/en-us/ 37 | # t3home = https://docs.typo3.org/ 38 | t3inside = https://docs.typo3.org/m/typo3/reference-inside/main/en-us/ 39 | # t3install = https://docs.typo3.org/m/typo3/guide-installation/main/en-us/ 40 | # t3l10n = https://docs.typo3.org/m/typo3/guide-frontendlocalization/main/en-us/ 41 | # t3sitepackage = https://docs.typo3.org/m/typo3/tutorial-sitepackage/main/en-us/ 42 | t3start = https://docs.typo3.org/m/typo3/tutorial-getting-started/11.5/en-us/ 43 | # t3tca = https://docs.typo3.org/m/typo3/reference-tca/main/en-us/ 44 | # t3templating = https://docs.typo3.org/m/typo3/tutorial-templating/main/en-us/ 45 | # t3translate = https://docs.typo3.org/m/typo3/guide-frontendlocalization/main/en-us/ 46 | # t3tsconfig = https://docs.typo3.org/m/typo3/reference-tsconfig/main/en-us/ 47 | t3tsref = https://docs.typo3.org/m/typo3/reference-typoscript/11.5/en-us/ 48 | # t3ts45 = https://docs.typo3.org/m/typo3/tutorial-typoscript-in-45-minutes/main/en-us/ 49 | # t3viewhelper = https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/ 50 | # t3upgrade = https://docs.typo3.org/m/typo3/guide-installation/main/en-us/ 51 | 52 | # TYPO3 system extensions 53 | # ext_adminpanel = https://docs.typo3.org/c/typo3/cms-adminpanel/main/en-us/ 54 | # ext_core = https://docs.typo3.org/c/typo3/cms-core/main/en-us/ 55 | # ext_dashboard = https://docs.typo3.org/c/typo3/cms-dashboard/main/en-us/ 56 | # ext_felogin = https://docs.typo3.org/c/typo3/cms-felogin/main/en-us/ 57 | # ext_form = https://docs.typo3.org/c/typo3/cms-form/main/en-us/ 58 | # ext_fsc = https://docs.typo3.org/c/typo3/cms-fluid-styled-content/main/en-us/ 59 | # ext_indexed_search = https://docs.typo3.org/c/typo3/cms-indexed-search/main/en-us/ 60 | # ext_rte_ckeditor = https://docs.typo3.org/c/typo3/cms-rte-ckeditor/main/en-us/ 61 | # ext_scheduler = https://docs.typo3.org/c/typo3/cms-scheduler/main/en-us/ 62 | # ext_seo = https://docs.typo3.org/c/typo3/cms-seo/main/en-us/ 63 | # ext_workspaces = https://docs.typo3.org/c/typo3/cms-workspaces/main/en-us/ 64 | -------------------------------------------------------------------------------- /Documentation/Sitemap.rst: -------------------------------------------------------------------------------- 1 | :template: sitemap.html 2 | 3 | .. include:: /Includes.rst.txt 4 | 5 | ======= 6 | Sitemap 7 | ======= 8 | 9 | .. The sitemap.html template will insert here the page tree automatically. 10 | -------------------------------------------------------------------------------- /Documentation/genindex.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | ===== 4 | Index 5 | ===== 6 | 7 | .. Sphinx will insert here the general index automatically. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Stable Version](https://poser.pugx.org/friendsoftypo3/frontend-editing/v/stable.svg)](https://extensions.typo3.org/extension/frontend_editing/) 2 | [![TYPO3 11](https://img.shields.io/badge/TYPO3-11-orange.svg?style=flat-square)](https://get.typo3.org/version/11) 3 | [![Total Downloads](https://poser.pugx.org/friendsoftypo3/frontend-editing/d/total.svg)](https://packagist.org/packages/friendsoftypo3/frontend-editing) 4 | [![Monthly Downloads](https://poser.pugx.org/friendsoftypo3/frontend-editing/d/monthly)](https://packagist.org/packages/friendsoftypo3/frontend-editing) 5 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate/?hosted_button_id=B7YZKNCHTYHRW) 6 | 7 | # TYPO3 extension `frontend_editing` 8 | 9 | This extension enhances the TYPO3 CMS with the possibility of frontend editing 10 | with [CKeditor](http://ckeditor.com/), the rich text editor of the TYPO3 11 | backend. 12 | 13 | | | URL | 14 | |------------------|----------------------------------------------------------------------| 15 | | **Repository:** | https://github.com/FriendsOfTYPO3/frontend_editing | 16 | | **Read online:** | https://docs.typo3.org/p/friendsoftypo3/frontend-editing/main/en-us/ | 17 | | **TER:** | https://extensions.typo3.org/extension/frontend_editing/ | 18 | -------------------------------------------------------------------------------- /Resources/Private/.htaccess: -------------------------------------------------------------------------------- 1 | deny from all 2 | -------------------------------------------------------------------------------- /Resources/Private/Language/locallang_be.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 |
      6 | 7 | 8 | SEO provider 9 | 10 | 11 | The tag name that will be used for the Content editable wrapper 12 | 13 | 14 | Enable placeholders and direct drop-to-edit without modal. 15 | 16 | 17 | Disable Frontend Editing loading screen. 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Resources/Private/Language/locallang_mod.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
      5 | 6 | 7 | Frontend editing 8 | 9 | 10 | Shows the look of the current page and lets you edit the content directly. 11 | 12 | 13 | Frontend editing 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Resources/Private/Layouts/DocHeader.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /Resources/Private/Partials/ContentElementsToolbar.html: -------------------------------------------------------------------------------- 1 | {namespace fe=TYPO3\CMS\FrontendEditing\ViewHelpers} 2 | 3 |
      4 |
      5 |
      6 | 7 |
      8 | 9 |
      10 | 11 |
      12 |
      13 |
      14 |
      15 | -------------------------------------------------------------------------------- /Resources/Private/Partials/CustomRecordTab.html: -------------------------------------------------------------------------------- 1 | {namespace core=TYPO3\CMS\Core\ViewHelpers} 2 | 3 |
      4 |
      5 |
      6 | 7 |
      8 |
      9 | 10 |
      11 |
      12 |
      13 | 14 |
      21 | 22 | 23 | 24 |
      25 | 26 | {customRecord.table} 27 |
      28 |
      29 |
      30 |
      31 |
      32 | -------------------------------------------------------------------------------- /Resources/Private/Partials/FluidStyledContent11/Header/All.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
      5 | 11 | 16 | 19 |
      20 |
      21 |
      22 | 23 | -------------------------------------------------------------------------------- /Resources/Private/Partials/FluidStyledContent11/Header/Header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

      6 | {header} 7 |

      8 |
      9 | 10 |

      11 | {header} 12 |

      13 |
      14 | 15 |

      16 | {header} 17 |

      18 |
      19 | 20 |

      21 | {header} 22 |

      23 |
      24 | 25 |
      26 | {header} 27 |
      28 |
      29 | 30 |
      31 | {header} 32 |
      33 |
      34 | 35 | -- do not show header -- 36 | 37 | 38 | 39 | 44 | 45 | 46 |
      47 |
      48 | 49 | -------------------------------------------------------------------------------- /Resources/Private/Partials/FluidStyledContent11/Header/SubHeader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

      6 | {subheader} 7 |

      8 |
      9 | 10 |

      11 | {subheader} 12 |

      13 |
      14 | 15 |

      16 | {subheader} 17 |

      18 |
      19 | 20 |
      21 | {subheader} 22 |
      23 |
      24 | 25 |
      26 | {subheader} 27 |
      28 |
      29 | 30 | 31 | 35 | 36 | 37 |
      38 |
      39 | 40 | -------------------------------------------------------------------------------- /Resources/Private/Partials/WizardElementTabs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
      4 |
      5 |
      6 | {wizardTab.description} 7 |
      8 |
      9 | 10 | 11 | 12 |
      13 |
      14 |
      15 | 16 |
      22 | 23 | {contentItem.iconHtml -> f:format.raw()} 24 | 25 |
      26 | {contentItem.title} 27 | {contentItem.description} 28 |
      29 |
      30 |
      31 |
      32 |
      33 |
      34 |
      -------------------------------------------------------------------------------- /Resources/Private/Sass/Elements/_ckeditor.scss: -------------------------------------------------------------------------------- 1 | .cke_float { 2 | .cke_top { 3 | box-shadow: none !important; 4 | background-image: none !important; 5 | background-color: $t3-gray !important; 6 | border: 0 !important; 7 | } 8 | } 9 | 10 | .cke_bottom { 11 | background-image: none !important; 12 | } 13 | 14 | .cke_toolgroup { 15 | border: 0 !important; 16 | box-shadow: none !important; 17 | background: $white !important; 18 | background-image: none !important; 19 | } 20 | 21 | .cke_dialog_title { 22 | background-image: none !important; 23 | color: $black !important; 24 | text-shadow: 0 1px 0 lighten($gray-light, 10%) !important; 25 | } 26 | -------------------------------------------------------------------------------- /Resources/Private/Sass/Elements/_content-elements-toolbar.scss: -------------------------------------------------------------------------------- 1 | .t3-frontend-editing__content-elements-toolbar { 2 | text-align: left; 3 | border-left: 1px solid $t3-gray-divider; 4 | background-color: $t3-gray; 5 | color: $black; 6 | position: absolute; 7 | top: 0; 8 | right: -$right-bar-width; 9 | width: $right-bar-width; 10 | bottom: 0; 11 | z-index: 10998; 12 | font-family: $font-family-base; 13 | 14 | &.closed { 15 | display: none; 16 | } 17 | 18 | .elements-wrapper { 19 | overflow-y: scroll; 20 | height: 100vh; 21 | 22 | .elements { 23 | background-color: $t3-gray; 24 | 25 | &:first-child .accordion-container:first-child .accordion { border-top: none; } 26 | } 27 | } 28 | 29 | &::-webkit-scrollbar { 30 | width: 8px; 31 | height: 8px; 32 | } 33 | 34 | &::-webkit-scrollbar-corner { 35 | background-color: $scroll-bar-color; 36 | } 37 | 38 | &::-webkit-scrollbar-thumb { 39 | background-color: $black; 40 | border: 1px solid $scroll-bar-color; 41 | } 42 | 43 | &::-webkit-scrollbar-track { 44 | background-color: $scroll-bar-color; 45 | } 46 | } 47 | 48 | .t3-frontend-editing__save, 49 | .t3-frontend-editing__discard { 50 | .btn-text { 51 | position: relative; 52 | top: -3px; 53 | } 54 | 55 | .icon-icons-save { 56 | font-size: 26px; 57 | position: relative; 58 | top: 1px; 59 | } 60 | 61 | .icon-icons-cancel { 62 | font-size: 26px; 63 | position: relative; 64 | top: 2px; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Resources/Private/Sass/Elements/_modal.scss: -------------------------------------------------------------------------------- 1 | .t3js-modal { 2 | font-family: $font-family-base; 3 | z-index: 9999999 !important; 4 | } 5 | 6 | .t3-frontend-editing__modal { 7 | font-size: 16px; 8 | 9 | .close { 10 | font-size: inherit; 11 | 12 | &:hover { 13 | outline: inherit; 14 | } 15 | 16 | &:focus { 17 | opacity: 1; 18 | outline: 0; 19 | box-shadow: $focus-shadow; 20 | border-radius: 2px; 21 | 22 | &:hover { 23 | box-shadow: $focus-shadow; 24 | } 25 | } 26 | } 27 | 28 | .modal-title { 29 | font-size: 1.25em; 30 | font-weight: bold; 31 | } 32 | 33 | .modal-body { 34 | padding-top: 1.5em; 35 | padding-bottom: 1.5em; 36 | } 37 | 38 | p { 39 | margin: 0.25em 0; 40 | } 41 | 42 | .modal-footer .btn + .btn { 43 | margin-left: 15px; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Resources/Private/Sass/Elements/_other.scss: -------------------------------------------------------------------------------- 1 | #tx_frontendediting_iframe { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0 auto; 5 | display: block; 6 | box-shadow: 0 0 15px 0 $gray; 7 | } 8 | 9 | .t3-frontend-editing__loading-screen { 10 | position: absolute; 11 | left: 0; 12 | top: 0; 13 | bottom: 0; 14 | right: 0; 15 | background: transparentize($color-t3-gray-dark, 0.2); 16 | 17 | .icon-spin { 18 | position: absolute; 19 | top: 50%; 20 | left: 50%; 21 | margin: -28px 0 0 -25px; 22 | 23 | .icon-markup { 24 | animation: icon-spin 2s infinite linear; 25 | display: block; 26 | } 27 | 28 | @keyframes icon-spin { 29 | 0% { 30 | transform: rotate(0deg); 31 | } 32 | 33 | 100% { 34 | transform: rotate(359deg); 35 | } 36 | } 37 | } 38 | } 39 | 40 | .hidden { 41 | display: none; 42 | } 43 | 44 | body { 45 | &.t3-frontend-editing__body { 46 | overflow: hidden; 47 | display: flex; 48 | flex-direction: column; 49 | } 50 | 51 | margin: 0; 52 | overflow-x: hidden; 53 | -webkit-font-smoothing: subpixel-antialiased; 54 | } 55 | 56 | * { 57 | -webkit-touch-callout: none; /* iOS Safari */ 58 | -webkit-user-select: none; /* Safari */ 59 | -khtml-user-select: none; /* Konqueror HTML */ 60 | -moz-user-select: none; /* Firefox */ 61 | -ms-user-select: none; /* Internet Explorer/Edge */ 62 | user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */ 63 | } 64 | 65 | .t3-frontend-editing__ckeditor-bar__wrapper { 66 | padding: 0 18px; 67 | background: $t3-gray; 68 | border-bottom: 1px solid $t3-gray-divider; 69 | min-height: calc(100% + 1px); // 100% of module-docheader + 1px to overlay module-docheader bottom border with its own border 70 | height: auto; 71 | position: absolute; 72 | top: 0; 73 | z-index: 1; 74 | margin: 0 auto; 75 | width: auto; 76 | left: 0; 77 | right: 0; 78 | } 79 | 80 | .t3-frontend-editing__ckeditor-bar { 81 | background: $t3-gray; 82 | color: #fff; 83 | height: auto; 84 | position: relative; 85 | z-index: 10998; 86 | margin: 0 auto; 87 | width: auto; 88 | left: 0; 89 | 90 | > .cke { 91 | position: relative !important; 92 | top: -1px !important; 93 | left: 0 !important; 94 | right: auto !important; 95 | box-shadow: unset; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Resources/Private/Sass/Elements/_scrollarea.scss: -------------------------------------------------------------------------------- 1 | :root{ 2 | --scroll-area-background: #{transparentize(black, 0.2)}; 3 | } 4 | .scrollarea { 5 | contain: strict; 6 | position: absolute; 7 | left: 0; 8 | right: 0; 9 | background: var(--scroll-area-background); 10 | min-height: $scroll-area-min-height; 11 | max-height: $scroll-area-max-height; 12 | height: $scroll-area-height; 13 | } 14 | 15 | .scrollarea-top { 16 | top: 0; 17 | } 18 | 19 | .scrollarea-bottom { 20 | bottom: 0; 21 | } 22 | 23 | .scrollarea--arrow:before { 24 | content: ""; 25 | display: block; 26 | width: $scroll-area-arrow-size; 27 | height: $scroll-area-arrow-size; 28 | top: 50%; 29 | margin: auto; 30 | margin-top: -($scroll-area-arrow-size / 2); 31 | position: relative; 32 | border: $scroll-area-arrow-width solid $scroll-area-arrow-color; 33 | transform: rotate(45deg); 34 | transition: 35 | border-color $scroll-area-arrow-transition-time $scroll-area-arrow-transition-function, 36 | transform $scroll-area-arrow-transition-time $scroll-area-arrow-transition-function; 37 | } 38 | 39 | .scrollarea--arrow-up:before { 40 | border-bottom-width: 0; 41 | border-right-width: 0; 42 | } 43 | 44 | .scrollarea--arrow-down:before { 45 | border-top-width: 0; 46 | border-left-width: 0; 47 | } 48 | 49 | .scrollarea--arrow__mouseover:before, 50 | .scrollarea--arrow:hover:before { 51 | transform: translateY(-5px) rotate(45deg); 52 | border-color: $scroll-area-arrow-color--hover; 53 | } 54 | 55 | .scrollarea--arrow-down:hover:before, 56 | .scrollarea--arrow-down.scrollarea--arrow__mouseover:before { 57 | transform: translateY(5px) rotate(45deg); 58 | } 59 | -------------------------------------------------------------------------------- /Resources/Private/Sass/Partials/_accordion.scss: -------------------------------------------------------------------------------- 1 | .accordion { 2 | border-top: 1px solid $t3-gray-divider; 3 | text-align: left; 4 | } 5 | 6 | .accordion-content { 7 | padding: 0; 8 | display: none; 9 | background-color: $t3-gray; 10 | border-top: 1px solid $t3-gray-divider; 11 | 12 | &.show { 13 | display: block; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Resources/Private/Sass/Partials/_elements.scss: -------------------------------------------------------------------------------- 1 | .content-icon { 2 | width: 50px; 3 | height: 50px; 4 | } 5 | 6 | .accordion-list .element:last-of-type { 7 | border-bottom: 0; 8 | } 9 | 10 | .element { 11 | display: inline-block; 12 | margin-bottom: 10px; 13 | position: relative; 14 | 15 | .description { 16 | width: 220px; 17 | display: none; 18 | margin-left: 5px; 19 | 20 | .title-default { 21 | display: inline-block; 22 | width: 230px; 23 | padding: 0; 24 | line-height: 1.2; 25 | color: $black; 26 | font-size: $font-size-smaller; 27 | 28 | &:first-of-type { 29 | font-size: $font-size-smaller; 30 | font-weight: bold; 31 | padding-top: 4px; 32 | color: $black; 33 | } 34 | } 35 | } 36 | 37 | div { 38 | pointer-events: none; 39 | } 40 | 41 | span { 42 | pointer-events: none; 43 | } 44 | 45 | &:hover { 46 | outline: solid 2px $color-t3-primary; 47 | } 48 | } 49 | 50 | .custom-record-tab { 51 | .accordion { 52 | .element-action { 53 | padding: 6px 10px; 54 | } 55 | } 56 | 57 | .element:hover { 58 | cursor: pointer; 59 | } 60 | } 61 | 62 | .wizard-elements-tabs .element:hover { 63 | cursor: move; 64 | } 65 | 66 | .accordion { 67 | .grid, 68 | .list-view { 69 | font-size: 32px; 70 | position: relative; 71 | top: 7px; 72 | width: 24px; 73 | display: inline-block; 74 | cursor: pointer; 75 | color: $t3-gray-divider; 76 | 77 | &:hover, 78 | &.active { 79 | color: $black; 80 | } 81 | } 82 | 83 | .trigger { 84 | transform: rotate(0); 85 | transition-duration: 0.2s; 86 | transform-origin: center; 87 | font-size: 26px; 88 | position: relative; 89 | top: 5px; 90 | display: inline-block; 91 | cursor: pointer; 92 | 93 | &.active { 94 | transform: rotate(180deg); 95 | transition-duration: 0.2s; 96 | transform-origin: center; 97 | } 98 | } 99 | 100 | .element-title { 101 | display: inline-block; 102 | font-size: var(--bs-body-font-size); 103 | padding: 3px 0; 104 | max-width: 205px; 105 | cursor: pointer; 106 | } 107 | 108 | .element-action { 109 | text-align: right; 110 | width: auto; 111 | display: inline-block; 112 | float: right; 113 | padding: 2px 10px; 114 | } 115 | } 116 | 117 | .accordion-grid { 118 | .grid { 119 | color: $black; 120 | } 121 | 122 | .accordion-content { 123 | padding: 10px; 124 | } 125 | } 126 | 127 | .accordion-list { 128 | .description { 129 | display: inline-block; 130 | } 131 | 132 | .element { 133 | display: block; 134 | background-color: $t3-gray; 135 | margin: 0; 136 | margin-bottom: 2px; 137 | padding: 10px; 138 | border-bottom: 1px solid $t3-gray-divider; 139 | 140 | .content-icon { 141 | vertical-align: top; 142 | display: inline-block; 143 | margin: 0 auto; 144 | 145 | .icon-size-default { 146 | height: 100%; 147 | width: 100%; 148 | 149 | .icon-markup { 150 | width: 50px; 151 | height: 50px; 152 | text-align: center; 153 | vertical-align: middle; 154 | display: block; 155 | margin: 0 auto; 156 | 157 | img { 158 | height: 40px; 159 | width: 40px; 160 | margin: 0 auto; 161 | top: 10%; 162 | position: relative; 163 | } 164 | } 165 | } 166 | } 167 | } 168 | 169 | .list-view { 170 | color: $black; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Resources/Private/Sass/Partials/_inputs.scss: -------------------------------------------------------------------------------- 1 | input[type=text] { 2 | background: $white; 3 | color: $gray-dark; 4 | font: $font-size-small $font-family-user-input; 5 | padding: 6px; 6 | border: 2px solid $gray; 7 | outline: none; 8 | line-height: 1.25em; 9 | 10 | &:hover { 11 | border-color: $gray-light; 12 | } 13 | 14 | &:focus { 15 | border-color: $orange-primary; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Resources/Private/Sass/Partials/_titles.scss: -------------------------------------------------------------------------------- 1 | .title-default { 2 | padding: 10px 0; 3 | } 4 | 5 | .title-center { 6 | text-align: center; 7 | padding: 10px; 8 | display: inline-block; 9 | } 10 | 11 | .title-right { 12 | text-align: right; 13 | padding: 10px; 14 | display: inline-block; 15 | } 16 | 17 | .title-left { 18 | text-align: left; 19 | padding: 10px; 20 | display: inline-block; 21 | } 22 | 23 | .title-normal-casing { 24 | font-size: $font-size-small; 25 | text-transform: none; 26 | margin-top: 4px; 27 | } 28 | -------------------------------------------------------------------------------- /Resources/Private/Sass/Partials/_wrappers.scss: -------------------------------------------------------------------------------- 1 | .padding-wrapper { 2 | padding: 0 14px; 3 | } 4 | 5 | .dark-transparent-bg { 6 | background-color: $t3-gray; 7 | } 8 | -------------------------------------------------------------------------------- /Resources/Private/Sass/_colors.scss: -------------------------------------------------------------------------------- 1 | // 2 | // LESS Variables for Colors 3 | // ------------------------- 4 | // Description: Styles for frontend editing. 5 | // Requires: - 6 | // Usage: Include this file in your standalone LESS file like: 7 | // [ @import '../Less/Configuration/Variables/_colors.less'; ] 8 | // 9 | 10 | // TYPO3 corporate design 11 | $color-t3-primary: #ff8700; 12 | $color-t3-gray-superlight: #f4f4f4; 13 | $color-t3-gray-light: #b9b9b9; 14 | $color-t3-gray: #8c8c8c; 15 | $color-t3-gray-dark: #515151; 16 | $color-t3-green: #5abc55; 17 | $color-t3-yellow: #ffc800; 18 | $color-t3-red: #dd123d; 19 | $color-t3-blue: #0080ff; 20 | 21 | // Bootstrap 22 | $gray-darker: rgb(30, 30, 30); 23 | $gray-dark: rgb(90, 90, 90); 24 | $gray: rgb(115, 115, 115); 25 | $gray-light: rgb(215, 215, 215); 26 | $gray-lighter: rgb(245, 245, 245); 27 | $brand-primary: #0078e6; 28 | $brand-success: #79a548; 29 | $brand-info: #6daae0; 30 | $brand-warning: #e8a33d; 31 | $brand-danger: #c83c3c; 32 | $brand-notice: #333; 33 | 34 | // Fonts 35 | $text-color: #000; 36 | 37 | // Custom 38 | $color-white: #fff; 39 | $color-yellow-light: #ffc857; 40 | $color-green-dark: darkgreen; 41 | $color-red-dark: maroon; 42 | -------------------------------------------------------------------------------- /Resources/Private/Sass/_constants.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $orange-primary: #ff8700; 3 | $t3-gray: #eee; 4 | $t3-gray-divider: #c3c3c3; 5 | $gray-dark: #191919; 6 | $gray: #4c4c4c; 7 | $gray-light: #838383; 8 | $gray-bright: #efefef; 9 | $gray-mid: #737373; 10 | 11 | $gray-icon-bg: #555f63; 12 | $black: #0f0f0f; 13 | $white: #fff; 14 | 15 | $scroll-bar-color: #3f3f3f; 16 | $scroll-bar-track: rgba(63, 63, 63, 0.6); 17 | 18 | $transparent-opacity: rgba(30, 30, 30, 0.9); 19 | 20 | // Font 21 | $font-family-base: Verdana, Arial, Helvetica, sans-serif; 22 | 23 | $font-family-user-input: $font-family-base; 24 | 25 | $font-size-smaller: 12px; 26 | $font-size-small: 14px; 27 | $font-size-regular: 16px; 28 | $font-size-large: 18px; 29 | 30 | /* Toolbars */ 31 | 32 | // Header top 33 | $header-top-bg-color: $black; 34 | 35 | // Right bar 36 | $right-bar-width: 325px; 37 | 38 | // DropZones 39 | $drop-zone-bg-color: rgba(167, 215, 255, 1); 40 | $drop-zone-enabled-height: 25px; 41 | $drop-zone-active-height: 60px; 42 | $drop-zone-active-border-color: rgba(0, 145, 210, 1); 43 | 44 | // Modal 45 | $modal-background-color: rgba(255, 255, 255, 0.5); 46 | 47 | // Timing 48 | $push-duration: 0.2s; 49 | $push-timing: linear; 50 | 51 | $fade-duration: 0.2s; 52 | $fade-timing: linear; 53 | 54 | // Extra space 55 | $twenty: 20px; 56 | 57 | // shadows 58 | $focus-shadow: 0 0 0 2px transparentize($gray-darker, 0.1); 59 | $focus-shadow-sm: 0 0 0 1px transparentize($gray-darker, 0.1); 60 | 61 | // scroll area 62 | $scroll-area-max-height: 100px; 63 | $scroll-area-min-height: 50px; 64 | $scroll-area-height: 8%; 65 | 66 | $scroll-area-arrow-size: 20px; 67 | $scroll-area-arrow-color: rgba(255, 255, 255, 0.8); 68 | $scroll-area-arrow-width: 8px; 69 | $scroll-area-arrow-color--hover: white; 70 | 71 | $scroll-area-arrow-transition-time: 250ms; 72 | $scroll-area-arrow-transition-function: ease; 73 | -------------------------------------------------------------------------------- /Resources/Private/Sass/_icons.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icons'; 3 | src: url('Fonts/icons.eot?fgsxdn'); 4 | src: 5 | url('Fonts/icons.eot?fgsxdn#iefix') format('embedded-opentype'), 6 | url('Fonts/icons.ttf?fgsxdn') format('truetype'), 7 | url('Fonts/icons.woff?fgsxdn') format('woff'), 8 | url('Fonts/icons.svg?fgsxdn#icons') format('svg'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | .icons { 14 | font-family: 'icons'; 15 | speak: none; 16 | font-style: normal; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | 25 | .icon-icons-arrow-double:before { 26 | content: "\e90e"; 27 | } 28 | 29 | .icon-icons-list:before { 30 | content: "\e909"; 31 | } 32 | 33 | .icon-icons-grid:before { 34 | content: "\e90a"; 35 | } 36 | 37 | .icon-icons-arrow-up:before { 38 | content: "\e90f"; 39 | } 40 | 41 | .icon-icons-arrow-down:before { 42 | content: "\e90b"; 43 | } 44 | 45 | .icon-icons-cancel:before { 46 | content: "\e90c"; 47 | } 48 | 49 | .icon-icons-save:before { 50 | content: "\e90d"; 51 | } 52 | 53 | .icon-icons-site-tree:before { 54 | content: "\e900"; 55 | } 56 | 57 | .icon-icons-back:before { 58 | content: "\e901"; 59 | } 60 | 61 | .icon-icons-workspace:before { 62 | content: "\e902"; 63 | } 64 | 65 | .icon-icons-tools-settings:before { 66 | content: "\e903"; 67 | } 68 | 69 | .icon-icons-lg-desktop:before { 70 | content: "\e904"; 71 | } 72 | 73 | .icon-icons-desktop:before { 74 | content: "\e905"; 75 | } 76 | 77 | .icon-icons-tablet:before { 78 | content: "\e906"; 79 | } 80 | 81 | .icon-icons-phone:before { 82 | content: "\e907"; 83 | } 84 | 85 | .icon-icons-page:before { 86 | content: "\e908"; 87 | } 88 | -------------------------------------------------------------------------------- /Resources/Private/Sass/_inline_action_icon.scss: -------------------------------------------------------------------------------- 1 | // inline actions icons styling - end section borrowed from backend.css - modified though 2 | @mixin inline-action-icon { 3 | .icon { 4 | position: relative; 5 | display: inline-block; 6 | overflow: hidden; 7 | white-space: nowrap; 8 | vertical-align: bottom; 9 | } 10 | 11 | .icon svg, 12 | .icon img { 13 | display: block; 14 | height: 100%; 15 | width: 100%; 16 | transform: translate3d(0, 0, 0); 17 | } 18 | 19 | .icon * { 20 | display: block; 21 | line-height: inherit; 22 | } 23 | 24 | .icon-markup { 25 | position: absolute; 26 | display: block; 27 | text-align: center; 28 | top: 5px; 29 | left: 5px; 30 | right: 5px; 31 | bottom: 5px; 32 | } 33 | 34 | .icon-overlay { 35 | position: absolute; 36 | bottom: 0; 37 | right: 0; 38 | height: 68.75%; 39 | width: 68.75%; 40 | text-align: center; 41 | } 42 | 43 | .icon-color { 44 | fill: currentColor; 45 | } 46 | 47 | .icon-spin .icon-markup { 48 | animation: icon-spin 2s infinite linear; 49 | } 50 | 51 | @keyframes icon-spin { 52 | 0% { 53 | transform: rotate(0deg); 54 | } 55 | 56 | 100% { 57 | transform: rotate(359deg); 58 | } 59 | } 60 | 61 | .icon-state-disabled .icon-markup { 62 | opacity: 0.5; 63 | } 64 | 65 | .icon-size-small { 66 | height: 26px; 67 | width: 26px; 68 | line-height: 26px; 69 | } 70 | 71 | .icon-size-default { 72 | height: 42px; 73 | width: 42px; 74 | line-height: 42px; 75 | } 76 | 77 | .icon-size-large { 78 | height: 58px; 79 | width: 58px; 80 | line-height: 58px; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Resources/Private/Sass/backend_module.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Frontend editing, backend module Styles 3 | // --------------- 4 | // Description: Backend module styles 5 | // Requires: ../Sass/Configuration/Variables/_colors.scss 6 | // Usage: This file is compiled to the standalone CSS file: 7 | // ../typo3_src/typo3/sysext/frontend_editing/Resources/Public/Css/inline_editing.css 8 | // 9 | 10 | // Include Color Variables 11 | @import '_colors.scss'; 12 | 13 | @import '_constants.scss'; 14 | @import '_icons.scss'; 15 | @import 'Partials/_inputs.scss'; 16 | @import 'Partials/_titles.scss'; 17 | @import 'Partials/_accordion.scss'; 18 | @import 'Partials/_wrappers.scss'; 19 | @import 'Partials/_elements.scss'; 20 | @import 'Elements/_content-elements-toolbar.scss'; 21 | @import 'Elements/_other.scss'; 22 | @import 'Elements/_ckeditor.scss'; 23 | @import 'Elements/_toastr.scss'; 24 | @import 'Elements/_modal.scss'; 25 | @import 'Elements/_scrollarea.scss'; 26 | 27 | $module-bg-color: #494949; 28 | $dropdown-bg-color: #292929; 29 | $dropdown-btn-hover-color: #424242; 30 | 31 | .typo3-module-frontendediting { 32 | .module { 33 | display: flex; 34 | flex-direction: column; 35 | background-color: transparent; 36 | 37 | .t3-frontend-editing__save { 38 | &.active { 39 | background-color: $color-t3-primary; 40 | border-color: $color-t3-primary; 41 | color: $color-white; 42 | box-shadow: none; // Remove default TYPO3 shadow for active btns 43 | } 44 | } 45 | 46 | .t3-frontend-editing__discard { 47 | &.active { 48 | background-color: $gray-darker; 49 | border-color: $gray-darker; 50 | color: $color-white; 51 | box-shadow: none; // Remove default TYPO3 shadow for active btns 52 | } 53 | } 54 | 55 | .module-button-active { 56 | background-color: $color-t3-primary; 57 | border-color: $color-t3-primary; 58 | color: $color-white; 59 | } 60 | 61 | // Reset FullyRenderedButton container (hard-coded in TYPO3) 62 | span.btn.btn-default { 63 | border: 0; 64 | background-color: transparent; 65 | padding: 2px; 66 | 67 | &.active, 68 | &:active { 69 | box-shadow: none; 70 | } 71 | } 72 | 73 | // Hide placeholder icon from preset split button (TYPO3 split button requires an icon) 74 | .t3js-change-preset { 75 | .icon-miscellaneous-placeholder { 76 | display: none; 77 | } 78 | } 79 | 80 | .dropdown-menu { 81 | width: 200px; 82 | 83 | li { 84 | background-color: $dropdown-bg-color; 85 | } 86 | 87 | // Darken background of dropdown buttons 88 | .btn:not(.divider) { 89 | background-color: $dropdown-bg-color; 90 | text-align: left; 91 | width: 100%; 92 | 93 | &:hover, 94 | &:focus, 95 | &.focus { 96 | color: $color-white; 97 | background-color: $dropdown-btn-hover-color; 98 | } 99 | } 100 | 101 | // Style btn divider (TYPO3 ButtonBar doesn't allow div so I must use btn as divider) 102 | .btn.divider { 103 | cursor: default; 104 | color: $color-white; 105 | background-color: $dropdown-bg-color; 106 | } 107 | 108 | // Can't use svg with ButtonBar->makeLinkButton()->setIcon() so I invert them 109 | .icon-markup { 110 | .icon-color { 111 | fill: $color-white; 112 | } 113 | } 114 | } 115 | 116 | .module-body { 117 | background-color: $module-bg-color; 118 | padding: 0 !important; // Must use !important because TYPO3 set padding-top via JS 119 | overflow: hidden; // Avoid scrollbars when FE iframe is full size 120 | text-align: center; // Center the div containing the FE iframe 121 | flex: 1; 122 | 123 | .typo3-messages { 124 | text-align: left; 125 | margin-left: 24px; 126 | margin-top: 24px; 127 | margin-right: 24px; 128 | } 129 | 130 | .frontendediting-resizeable { 131 | display: inline-block; 132 | } 133 | 134 | #tx_frontendediting_iframe { 135 | border: 0; 136 | height: 100%; 137 | width: 100%; 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Resources/Private/Sass/inline_editing.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Frontend editing, inline editing Styles 3 | // --------------- 4 | // Description: Global styles for inline editing wrapper. 5 | // Requires: ../Sass/Configuration/Variables/_colors.scss 6 | // Usage: This file is compiled to the standalone CSS file: 7 | // ../typo3_src/typo3/sysext/frontend_editing/Resources/Public/Css/inline_editing.css 8 | // 9 | 10 | // Include Color Variables 11 | @import '_colors.scss'; 12 | @import '_inline_action_icon.scss'; 13 | 14 | // minimize external css affecting ours (ckeditor can create momentary spans when hovering) 15 | 16 | *[contenteditable="true"] { 17 | & > span:before, 18 | & > span:after { 19 | content: none !important; 20 | } 21 | 22 | &:hover:not(.hover-disabled) { 23 | outline: 1px dotted transparentize($color-t3-primary, 0.7); 24 | } 25 | 26 | &:focus { 27 | outline: 1px dotted $color-t3-primary; 28 | } 29 | } 30 | 31 | // hide preview info box from TYPO3 admin panel 32 | 33 | #typo3-previewInfo, 34 | #typo3-preview-info { 35 | display: none !important; 36 | } 37 | 38 | // visualize hidden elements 39 | 40 | .t3-frontend-editing__hidden-element { 41 | opacity: 0.6; 42 | } 43 | 44 | // dropzone (areas where you can add content) 45 | 46 | .t3-frontend-editing__dropzone { 47 | display: block; 48 | height: 0; 49 | background-color: $color-t3-primary; 50 | transition: height 0.2s; 51 | margin: 0; 52 | padding: 0; 53 | } 54 | 55 | // Use case: 56 | // An admin could want to add a custom drop zone to allow the editors to drop a CE also inside empty page or container columns. 57 | // This will result in 2 adjacent drop zones when there are already CEs inside the column (one custom and one of the first CE). 58 | // This CSS rule will hide the second adjacent drop zone if they are both dropzones for tt_content CE. 59 | .t3-frontend-editing__dropzone[data-allowed-tables="tt_content"] + .t3-frontend-editing__dropzone[data-allowed-tables="tt_content"] { display: none !important; } 60 | 61 | .dropzones-enabled .t3-frontend-editing__dropzone { 62 | height: 25px; 63 | border: solid 1px white; 64 | 65 | &.active { 66 | border: dashed 1px $gray; 67 | background-color: $color-t3-green; 68 | } 69 | 70 | &.t3-frontend-editing__dropzone-hidden { 71 | display: none; 72 | visibility: hidden; 73 | } 74 | } 75 | 76 | // container for editing content element 77 | 78 | .t3-frontend-editing__ce { 79 | position: relative !important; 80 | min-height: 35px; 81 | 82 | &:hover:not(.hover-disabled) { 83 | outline: 1px dotted transparentize($color-t3-gray, 0.5); 84 | } 85 | 86 | &[draggable="true"]:hover { 87 | cursor: pointer; 88 | } 89 | } 90 | 91 | // inline actions (buttons like edit, hide, delete, new) 92 | 93 | .t3-frontend-editing__inline-actions { 94 | 95 | // minimize external css affecting ours 96 | 97 | &:before, 98 | &:after, 99 | span:before, 100 | span:after, 101 | img:before, 102 | img:after { 103 | content: none !important; 104 | } 105 | 106 | line-height: 1; 107 | display: none; // has also been set inline to hide before this styling is being applied 108 | position: absolute !important; 109 | bottom: 100%; 110 | left: -1px; 111 | color: $color-white; 112 | background-color: transparentize($color-t3-gray-dark, 0.2); 113 | z-index: 16777271; // maximum z-index value to make sure it is always on top 114 | text-align: right; 115 | 116 | @include inline-action-icon; 117 | 118 | .icon { 119 | background-color: transparentize($color-white, 0.7); 120 | 121 | // Cursor ponter for any icon except the flag icons 122 | &:not([class*="icon-flags"]) { cursor: pointer; } 123 | 124 | &:hover { 125 | background-color: transparentize($gray-light, 0.3); 126 | } 127 | 128 | &.icon-actions-move { 129 | background-color: $color-t3-primary; 130 | cursor: grab; 131 | } 132 | } 133 | } 134 | 135 | // show inline actions when hovering 136 | 137 | .t3-frontend-editing__ce:hover .t3-frontend-editing__inline-actions:not(.hover-disabled) { 138 | display: inline-block !important; // overrides inline styling 139 | } 140 | -------------------------------------------------------------------------------- /Resources/Private/Templates/FluidStyledContent11/Text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {data.bodytext} 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/Private/Templates/FluidStyledContent11/Textmedia.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
      13 | 14 | 15 | 16 | 17 | 18 | 19 |
      20 | 21 | 22 | 23 | {data.bodytext} 24 |
      25 |
      26 | 27 | 28 | 29 |
      30 | 31 |
      32 |
      33 |
      34 |
      35 |
      36 | 37 | 38 | 39 | 40 |
      41 | 42 |
      43 | 44 | -------------------------------------------------------------------------------- /Resources/Private/Templates/FluidStyledContent11/Textpic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
      13 | 14 | 15 | 16 | 17 | 18 | 19 |
      20 | 21 | 22 | 23 | {data.bodytext} 24 |
      25 |
      26 | 27 | 28 | 29 |
      30 | 31 |
      32 |
      33 |
      34 |
      35 |
      36 | 37 | 38 | 39 | 40 |
      41 | 42 |
      43 | 44 | -------------------------------------------------------------------------------- /Resources/Private/Templates/FrontendEditingModule/Show.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {f:if(condition: current.width, then: '{current.width}px', else: '100%')} 10 | {f:if(condition: current.height, then: '{current.height}px', else: '100%')} 11 |
      12 | 13 | 14 | 15 | 16 |
      {loadingIcon -> f:format.raw()}
      17 |
      18 |
      19 |
      20 |
      21 | -------------------------------------------------------------------------------- /Resources/Public/Css/Fonts/icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfTYPO3/frontend_editing/25352da0585179bfefe44972c2bf9f64303b3bd0/Resources/Public/Css/Fonts/icons.eot -------------------------------------------------------------------------------- /Resources/Public/Css/Fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfTYPO3/frontend_editing/25352da0585179bfefe44972c2bf9f64303b3bd0/Resources/Public/Css/Fonts/icons.ttf -------------------------------------------------------------------------------- /Resources/Public/Css/Fonts/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfTYPO3/frontend_editing/25352da0585179bfefe44972c2bf9f64303b3bd0/Resources/Public/Css/Fonts/icons.woff -------------------------------------------------------------------------------- /Resources/Public/Css/Fonts/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icons'; 3 | src: url('icons.eot?fgsxdn'); 4 | src: url('icons.eot?fgsxdn#iefix') format('embedded-opentype'), 5 | url('icons.ttf?fgsxdn') format('truetype'), 6 | url('icons.woff?fgsxdn') format('woff'), 7 | url('icons.svg?fgsxdn#icons') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="icon-"], [class*=" icon-"] { 13 | /* use !important to prevent issues with browser extensions that change fonts */ 14 | font-family: 'icons' !important; 15 | speak: none; 16 | font-style: normal; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | 22 | /* Better Font Rendering =========== */ 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | .icon-icons-arrow-double:before { 28 | content: "\e90e"; 29 | } 30 | .icon-icons-list:before { 31 | content: "\e909"; 32 | } 33 | .icon-icons-grid:before { 34 | content: "\e90a"; 35 | } 36 | .icon-icons-arrow-up:before { 37 | content: "\e90f"; 38 | } 39 | .icon-icons-arrow-down:before { 40 | content: "\e90b"; 41 | } 42 | .icon-icons-cancel:before { 43 | content: "\e90c"; 44 | } 45 | .icon-icons-save:before { 46 | content: "\e90d"; 47 | } 48 | .icon-icons-site-tree:before { 49 | content: "\e900"; 50 | } 51 | .icon-icons-back:before { 52 | content: "\e901"; 53 | } 54 | .icon-icons-workspace:before { 55 | content: "\e902"; 56 | } 57 | .icon-icons-tools-settings:before { 58 | content: "\e903"; 59 | } 60 | .icon-icons-lg-desktop:before { 61 | content: "\e904"; 62 | } 63 | .icon-icons-desktop:before { 64 | content: "\e905"; 65 | } 66 | .icon-icons-tablet:before { 67 | content: "\e906"; 68 | } 69 | .icon-icons-phone:before { 70 | content: "\e907"; 71 | } 72 | .icon-icons-page:before { 73 | content: "\e908"; 74 | } 75 | -------------------------------------------------------------------------------- /Resources/Public/Css/inline_editing.css: -------------------------------------------------------------------------------- 1 | *[contenteditable="true"] > span:before, 2 | *[contenteditable="true"] > span:after { 3 | content: none !important; } 4 | 5 | *[contenteditable="true"]:hover:not(.hover-disabled) { 6 | outline: 1px dotted rgba(255, 135, 0, 0.3); } 7 | 8 | *[contenteditable="true"]:focus { 9 | outline: 1px dotted #ff8700; } 10 | 11 | #typo3-previewInfo, 12 | #typo3-preview-info { 13 | display: none !important; } 14 | 15 | .t3-frontend-editing__hidden-element { 16 | opacity: 0.6; } 17 | 18 | .t3-frontend-editing__dropzone { 19 | display: block; 20 | height: 0; 21 | background-color: #ff8700; 22 | transition: height 0.2s; 23 | margin: 0; 24 | padding: 0; } 25 | 26 | .t3-frontend-editing__dropzone[data-allowed-tables="tt_content"] + .t3-frontend-editing__dropzone[data-allowed-tables="tt_content"] { 27 | display: none !important; } 28 | 29 | .dropzones-enabled .t3-frontend-editing__dropzone { 30 | height: 25px; 31 | border: solid 1px white; } 32 | .dropzones-enabled .t3-frontend-editing__dropzone.active { 33 | border: dashed 1px #737373; 34 | background-color: #5abc55; } 35 | .dropzones-enabled .t3-frontend-editing__dropzone.t3-frontend-editing__dropzone-hidden { 36 | display: none; 37 | visibility: hidden; } 38 | 39 | .t3-frontend-editing__ce { 40 | position: relative !important; 41 | min-height: 35px; } 42 | .t3-frontend-editing__ce:hover:not(.hover-disabled) { 43 | outline: 1px dotted rgba(140, 140, 140, 0.5); } 44 | .t3-frontend-editing__ce[draggable="true"]:hover { 45 | cursor: pointer; } 46 | 47 | .t3-frontend-editing__inline-actions { 48 | line-height: 1; 49 | display: none; 50 | position: absolute !important; 51 | bottom: 100%; 52 | left: -1px; 53 | color: #fff; 54 | background-color: rgba(81, 81, 81, 0.8); 55 | z-index: 16777271; 56 | text-align: right; } 57 | .t3-frontend-editing__inline-actions:before, .t3-frontend-editing__inline-actions:after, 58 | .t3-frontend-editing__inline-actions span:before, 59 | .t3-frontend-editing__inline-actions span:after, 60 | .t3-frontend-editing__inline-actions img:before, 61 | .t3-frontend-editing__inline-actions img:after { 62 | content: none !important; } 63 | .t3-frontend-editing__inline-actions .icon { 64 | position: relative; 65 | display: inline-block; 66 | overflow: hidden; 67 | white-space: nowrap; 68 | vertical-align: bottom; } 69 | .t3-frontend-editing__inline-actions .icon svg, 70 | .t3-frontend-editing__inline-actions .icon img { 71 | display: block; 72 | height: 100%; 73 | width: 100%; 74 | transform: translate3d(0, 0, 0); } 75 | .t3-frontend-editing__inline-actions .icon * { 76 | display: block; 77 | line-height: inherit; } 78 | .t3-frontend-editing__inline-actions .icon-markup { 79 | position: absolute; 80 | display: block; 81 | text-align: center; 82 | top: 5px; 83 | left: 5px; 84 | right: 5px; 85 | bottom: 5px; } 86 | .t3-frontend-editing__inline-actions .icon-overlay { 87 | position: absolute; 88 | bottom: 0; 89 | right: 0; 90 | height: 68.75%; 91 | width: 68.75%; 92 | text-align: center; } 93 | .t3-frontend-editing__inline-actions .icon-color { 94 | fill: currentColor; } 95 | .t3-frontend-editing__inline-actions .icon-spin .icon-markup { 96 | animation: icon-spin 2s infinite linear; } 97 | 98 | @keyframes icon-spin { 99 | 0% { 100 | transform: rotate(0deg); } 101 | 100% { 102 | transform: rotate(359deg); } } 103 | .t3-frontend-editing__inline-actions .icon-state-disabled .icon-markup { 104 | opacity: 0.5; } 105 | .t3-frontend-editing__inline-actions .icon-size-small { 106 | height: 26px; 107 | width: 26px; 108 | line-height: 26px; } 109 | .t3-frontend-editing__inline-actions .icon-size-default { 110 | height: 42px; 111 | width: 42px; 112 | line-height: 42px; } 113 | .t3-frontend-editing__inline-actions .icon-size-large { 114 | height: 58px; 115 | width: 58px; 116 | line-height: 58px; } 117 | .t3-frontend-editing__inline-actions .icon { 118 | background-color: rgba(255, 255, 255, 0.3); } 119 | .t3-frontend-editing__inline-actions .icon:not([class*="icon-flags"]) { 120 | cursor: pointer; } 121 | .t3-frontend-editing__inline-actions .icon:hover { 122 | background-color: rgba(215, 215, 215, 0.7); } 123 | .t3-frontend-editing__inline-actions .icon.icon-actions-move { 124 | background-color: #ff8700; 125 | cursor: grab; } 126 | 127 | .t3-frontend-editing__ce:hover .t3-frontend-editing__inline-actions:not(.hover-disabled) { 128 | display: inline-block !important; } 129 | -------------------------------------------------------------------------------- /Resources/Public/Icons/Extension.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfTYPO3/frontend_editing/25352da0585179bfefe44972c2bf9f64303b3bd0/Resources/Public/Icons/Extension.png -------------------------------------------------------------------------------- /Resources/Public/Icons/ext_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfTYPO3/frontend_editing/25352da0585179bfefe44972c2bf9f64303b3bd0/Resources/Public/Icons/ext_icon.png -------------------------------------------------------------------------------- /Resources/Public/Icons/visible_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Contrib/ckeditor-jquery-adapter.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. 3 | For licensing, see LICENSE.md or http://ckeditor.com/license 4 | */ 5 | (function(a){if("undefined"==typeof a)throw Error("jQuery should be loaded before CKEditor jQuery adapter.");if("undefined"==typeof CKEDITOR)throw Error("CKEditor should be loaded before CKEditor jQuery adapter.");CKEDITOR.config.jqueryOverrideVal="undefined"==typeof CKEDITOR.config.jqueryOverrideVal?!0:CKEDITOR.config.jqueryOverrideVal;a.extend(a.fn,{ckeditorGet:function(){var a=this.eq(0).data("ckeditorInstance");if(!a)throw"CKEditor is not initialized yet, use ckeditor() with a callback.";return a}, 6 | ckeditor:function(g,d){if(!CKEDITOR.env.isCompatible)throw Error("The environment is incompatible.");if(!a.isFunction(g)){var m=d;d=g;g=m}var k=[];d=d||{};this.each(function(){var b=a(this),c=b.data("ckeditorInstance"),f=b.data("_ckeditorInstanceLock"),h=this,l=new a.Deferred;k.push(l.promise());if(c&&!f)g&&g.apply(c,[this]),l.resolve();else if(f)c.once("instanceReady",function(){setTimeout(function(){c.element?(c.element.$==h&&g&&g.apply(c,[h]),l.resolve()):setTimeout(arguments.callee,100)},0)}, 7 | null,null,9999);else{if(d.autoUpdateElement||"undefined"==typeof d.autoUpdateElement&&CKEDITOR.config.autoUpdateElement)d.autoUpdateElementJquery=!0;d.autoUpdateElement=!1;b.data("_ckeditorInstanceLock",!0);c=a(this).is("textarea")?CKEDITOR.replace(h,d):CKEDITOR.inline(h,d);b.data("ckeditorInstance",c);c.on("instanceReady",function(d){var e=d.editor;setTimeout(function(){if(e.element){d.removeListener();e.on("dataReady",function(){b.trigger("dataReady.ckeditor",[e])});e.on("setData",function(a){b.trigger("setData.ckeditor", 8 | [e,a.data])});e.on("getData",function(a){b.trigger("getData.ckeditor",[e,a.data])},999);e.on("destroy",function(){b.trigger("destroy.ckeditor",[e])});e.on("save",function(){a(h.form).submit();return!1},null,null,20);if(e.config.autoUpdateElementJquery&&b.is("textarea")&&a(h.form).length){var c=function(){b.ckeditor(function(){e.updateElement()})};a(h.form).submit(c);a(h.form).bind("form-pre-serialize",c);b.bind("destroy.ckeditor",function(){a(h.form).unbind("submit",c);a(h.form).unbind("form-pre-serialize", 9 | c)})}e.on("destroy",function(){b.removeData("ckeditorInstance")});b.removeData("_ckeditorInstanceLock");b.trigger("instanceReady.ckeditor",[e]);g&&g.apply(e,[h]);l.resolve()}else setTimeout(arguments.callee,100)},0)},null,null,9999)}});var f=new a.Deferred;this.promise=f.promise();a.when.apply(this,k).then(function(){f.resolve()});this.editor=this.eq(0).data("ckeditorInstance");return this}});CKEDITOR.config.jqueryOverrideVal&&(a.fn.val=CKEDITOR.tools.override(a.fn.val,function(g){return function(d){if(arguments.length){var m= 10 | this,k=[],f=this.each(function(){var b=a(this),c=b.data("ckeditorInstance");if(b.is("textarea")&&c){var f=new a.Deferred;c.setData(d,function(){f.resolve()});k.push(f.promise());return!0}return g.call(b,d)});if(k.length){var b=new a.Deferred;a.when.apply(this,k).done(function(){b.resolveWith(m)});return b.promise()}return f}var f=a(this).eq(0),c=f.data("ckeditorInstance");return f.is("textarea")&&c?c.getData():g.call(f)}}))})(window.$); 11 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/LoadingScreen.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/FrontendEditing/LoadingScreen 16 | * Fade in and out the loading screen 17 | */ 18 | define(['jquery', './Utils/Logger'], function LoadingScreenFactory($, Logger) { 19 | 'use strict'; 20 | 21 | var log = Logger('FEditing:Component:LoadingScreen'); 22 | log.trace('--> LoadingScreenFactory'); 23 | 24 | return function LoadingScreen(element) { 25 | log.trace('LoadingScreen', element); 26 | 27 | var $loadingScreen = $(element); 28 | var loadingScreenLevel = 0; 29 | 30 | return { 31 | showLoadingScreen: function () { 32 | log.trace('showLoadingScreen', loadingScreenLevel); 33 | 34 | if (loadingScreenLevel === 0) { 35 | log.info('show loading screen'); 36 | 37 | $loadingScreen.fadeIn('fast'); 38 | } 39 | 40 | loadingScreenLevel++; 41 | log.debug('new loadingScreenLevel', loadingScreenLevel); 42 | }, 43 | hideLoadingScreen: function () { 44 | log.trace('--> LoadingScreenFactory'); 45 | loadingScreenLevel--; 46 | 47 | log.debug('new loadingScreenLevel', loadingScreenLevel); 48 | 49 | if (loadingScreenLevel <= 0) { 50 | log.info('hide loading screen'); 51 | 52 | loadingScreenLevel = 0; 53 | $loadingScreen.fadeOut('slow'); 54 | } 55 | }, 56 | getLoadingScreenLevel: function () { 57 | return loadingScreenLevel; 58 | } 59 | }; 60 | }; 61 | }); 62 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Notification.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/FrontendEditing/Notification 16 | * Notification: Notification (toastr) wrapper 17 | */ 18 | define([ 19 | './Contrib/toastr', 20 | './Utils/Logger' 21 | ], function NotificationFactory(toastr, Logger) { 22 | 'use strict'; 23 | 24 | var log = Logger('FEditing:Component:Widget:Notification'); 25 | log.trace('--> NotificationFactory'); 26 | 27 | var toastrOptions = { 28 | 'positionClass': 'toast-top-right', 29 | 'preventDuplicates': true 30 | }; 31 | 32 | return { 33 | success: function (message, title) { 34 | log.debug('success', title, message); 35 | 36 | toastr.success(message, title, toastrOptions); 37 | }, 38 | 39 | error: function (message, title) { 40 | log.debug('error', title, message); 41 | 42 | toastr.error(message, title, toastrOptions); 43 | }, 44 | 45 | warning: function (message, title) { 46 | log.debug('warning', title, message); 47 | 48 | toastr.warning(message, title, toastrOptions); 49 | }, 50 | 51 | info: function (message, title) { 52 | log.debug('info', title, message); 53 | 54 | toastr.info(message, title, toastrOptions); 55 | }, 56 | }; 57 | }); 58 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/ParentWindow.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'TYPO3/CMS/Backend/FormEngineLinkBrowserAdapter', 3 | 'TYPO3/CMS/Recordlist/ElementBrowser' 4 | ], function(FormEngineLinkBrowserAdapter, ElementBrowser) { 5 | const FormEngineLinkBrowserAdapterParentFunction = FormEngineLinkBrowserAdapter.getParent; 6 | const getParent = () => { 7 | // Accessing the frame using .contentWindow does not 8 | // work in Chrome or Edge. 9 | if ( 10 | typeof window.parent !== 'undefined' && 11 | typeof window.parent.document.list_frame !== 'undefined' && 12 | window.parent.document.list_frame.length > 0 13 | ) { 14 | var frame = window.parent.document.list_frame[window.parent.document.list_frame.length - 1]; 15 | frame = frame.contentWindow || frame; 16 | if (frame.parent.document.querySelector('.t3js-modal-iframe') !== null) { 17 | return frame; 18 | } 19 | } 20 | return null; 21 | } 22 | 23 | FormEngineLinkBrowserAdapter.getParent = () => { 24 | return getParent() || FormEngineLinkBrowserAdapterParentFunction(); 25 | } 26 | 27 | ElementBrowser.getParent = () => { 28 | ElementBrowser.opener = FormEngineLinkBrowserAdapter.getParent() 29 | return ElementBrowser.opener; 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Plugins/confighelper/README.md: -------------------------------------------------------------------------------- 1 | confighelper 2 | ============ 3 | 4 | Configuration Helper + HTML5 placeholder for CKEditor. 5 | See docs/install.html for full details and install instructions 6 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Plugins/confighelper/docs/install.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Configuration Helper plugin 6 | 7 | 8 | 9 | 10 |

      Configuration Helper Plugin for CKEditor

      11 | 12 |

      Introduction

      13 |

      This plugin tries to help setup CKEditor by providing additional configuration options to perform some 14 | kind of common tasks.

      15 |

      Currently if offers a "removeDialogFields" that allows to remove individual fields in the dialogs (versus removing whole tabs with 16 | removeDialogTabs, and "dialogFieldsDefaultValues" 17 | defines default values for dialog fields.

      18 | 19 |

      Author:

      20 |

      Alfonso Martínez de Lizarrondo

      21 | 22 |

      Version history:

      23 |
        24 |
      1. 1.0: 26-February-2012. First version.
      2. 25 |
      3. 1.1: 16-February-2012. Added placeholder.
      4. 26 |
      5. 1.2: 23-April-2012. Added hideDialogFields.
      6. 27 |
      7. 1.3: 1-December-2012. Compatibility with CKEditor 4.
      8. 28 |
      9. 1.4: 28-March-2013. Compatibility of the "placeholder" attribute with the inline mode of CKEditor 4.
      10. 29 |
      11. 1.5: 16-April-2013. Version 1.4 was broken in CKEditor 3.
      12. 30 |
      13. 1.6: 16-August-2013. Handle the setData method to update the "placeholder" status
      14. 31 |
      15. 1.7: 6-October-2013. Patch by bfavors to fix handling placeholder on initial load of editor
      16. 32 |
      17. 1.8: 9-March-2014. Check for IE11 by Russel Ward
        33 | Set caret into the empty paragraph correctly on first focus, patch by glanchow 34 |
      18. 35 |
      19. 1.8.1: 5-April-2014. Fix IE8 & IE9 problem with "inline textarea" if it's empty on start 36 |
      20. 37 |
      21. 1.8.2: 12-April-2014. Protect detection of empty content. Thanks to tanihito. 38 |
      22. 39 |
      23. 1.8.3: 30-November-2014. Force SCAYT to use the language that it's specified as the language for the contents.
        40 | Listen to the contentDom event to avoid problems when calling setData in WYSIWYG mode. Thanks to noam-si. 41 |
      24. 42 |
      25. 1.8.4: 25-November-2018: 43 | Catch errors from localStorage Thanks to nikgt
        44 | Fix UTF-8 issue
        45 | Fix missing assigment with tableProperties dialog
        46 | Add license file
        47 | Prevent editing the placeholder text if startupFocus is true. Thanks to Albince Paliakkara
        48 | Prevent leaking editor instances on destroy. Thanks to Ben Demboski 49 |
      26. 1.9.0: Removed compatibility with CKEditor 3.
        50 | Code clean up with EsLint
        51 | Safety check that requested dialog tabs exist before trying to work with them.
        52 | Check editor.enterMode in removePlaceholder. Thanks to Jules Achiel. 53 |
      27. 54 |
      28. 1.10: 16-June-2019
        55 | removePlaceholder adds carriage return in Firefox and IE. Thanks to Jules Achiel.
        56 | Do not add placeholder when CKE is readOnly. Thanks to Jules Achiel.
        57 |
      29. 58 |
      30. 1.10.1: 17-June-2019
        59 | Fix unterminated string error. 60 |
      31. 61 |
      62 | 63 |

      Installation

      64 |

      1. Copying the files

      65 |

      Extract the contents of the zip in you plugins directory, so it ends up like 66 | this
      67 | 68 |

      69 |
       70 | ckeditor\
       71 | 	...
       72 | 	images\
       73 | 	lang\
       74 | 	plugins\
       75 | 		...
       76 | 		confighelper\
       77 | 			plugin.js
       78 | 			docs\
       79 | 				install.html
       80 | 		...
       81 | 	skins\
       82 | 	themes\
       83 | 
      84 |

      2. Adding it to CKEditor

      85 |

      Now add the plugin in your config.js or custom js configuration 86 | file: 87 | config.extraPlugins='confighelper'; 88 |

      89 | 90 |

      3. Configuration

      91 |

      config.removeDialogFields

      92 |

      This entry is a string, the fields are defined as dialogName + ":" + tab + ":" + field. Fields are joined with semicolons. 93 | In order to learn the name of the parameters you can use the "Developer Tools plugin", launch that sample and open the dialog that you want to customize. 94 | Now a little popup with appear showing the info about that field, for example: 95 |

      Element Information
       96 | Dialog window name : image
       97 | Tab name : info
       98 | Element ID : txtBorder
       99 | Element type : text
      100 | 
      101 | so in order to remove the class attribute for images the config is: 102 |
      config.removeDialogFields="image:info:txtBorder";
      103 | removing another field 104 |
      config.removeDialogFields="image:info:txtBorder;image:info:txtHSpace";
      105 | 106 |

      config.dialogFieldsDefaultValues

      107 |

      This setting uses directly a JSON object as the configuration value, first an object that has the dialog names as properties, each property is 108 | a new object with the name of the tabs and finally each property name maps to the field name and it's value is the default value to use for the field.

      109 |

      An example might be much better as I might have messed up something in the description:

      110 |
      config.dialogFieldsDefaultValues =
      111 | {
      112 | 	image:
      113 | 		{
      114 | 			advanced:
      115 | 				{
      116 | 					txtGenClass : 'myClass',
      117 | 					txtGenTitle : 'Image title'
      118 | 				}
      119 | 		}
      120 | };
      121 | 
      122 | 123 |

      config.placeholder

      124 |

      This a text that will be shown when the editor is empty following the HTML5 placeholder attribute. When the user focus the editor, the content is 125 | cleared automatically.

      126 |

      The value can be set in the configuration or as an attribute of the replaced element

      127 |
      config.placeholder = 'Type here...';
      128 | 129 |

      config.hideDialogFields

      130 |

      This entry uses the same sintax that the 'removeDialogFields' option. The difference is that some fields can't be removed easily as other parts of the dialog 131 | might not be ready and might try to always use it, generating a javascript error. In other cases the layout might be broken if the field is removed instead of hidden.
      132 | In those cases it's possible to hide the fields using this entry, and the preview in the image dialog is an example of such a field.

      133 |
      config.hideDialogFields="image:info:htmlPreview";
      134 | 135 | 138 | 139 |

      Disclaimers

      140 |

      CKEditor is © CKSource.com

      141 | 142 | 143 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Plugins/confighelper/docs/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | font-size: 90%; 4 | } 5 | h1 { 6 | text-align:center; 7 | font-size:180%; 8 | } 9 | h2 { 10 | border-bottom:2px solid #CCC; 11 | margin:1em 0 0.4em 0; 12 | } 13 | h3 { 14 | margin-bottom:0.4em; 15 | } 16 | p { 17 | margin:0 0 1em 1em; 18 | text-align:justify; 19 | } 20 | ol { 21 | margin:0 0 1.2em 1em; 22 | padding:0; 23 | list-style-type:none; 24 | } 25 | ol li { 26 | margin:0.2em 0; 27 | } 28 | pre, code { 29 | font-size:100%; 30 | font-family:"Courier New", Courier, mono; 31 | background-color: #CCCCCC; 32 | border:1px solid #999; 33 | padding:0.2em 1em; 34 | margin: 0.4em 0; 35 | display:block; 36 | white-space: pre; 37 | overflow: auto; 38 | } 39 | form { 40 | margin:0 0 0 1em; 41 | } 42 | span.key { 43 | color: #006600; 44 | } 45 | #install { 46 | display:none 47 | } 48 | #languages ul { 49 | display:inline; 50 | list-style-type:none; 51 | margin:0; 52 | padding:0; 53 | } 54 | #languages li { 55 | display:inline; 56 | margin:0; 57 | padding:0; 58 | vertical-align:bottom; 59 | } -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Plugins/openlink/README.md: -------------------------------------------------------------------------------- 1 | # Open Link plugin 2 | 3 | The **Open Link** is a very simple plugin, extending context menu with a possibility to open link in a new tab. 4 | 5 | * extending context menu with a possibility to open link in a new tab, 6 | * allowing you to open link with a ctrl/cmd click, 7 | 8 | It also integrates with linked [image2](http://ckeditor.com/addon/image2) widgets. 9 | 10 | ## Browser Compatibility 11 | 12 | Basically the same as [CKEditor](http://docs.ckeditor.com/#!/guide/dev_browsers) with one exception: opening a link with ctrl click does not work in Internet Explorer / Edge browsers. Pull requests are welcome. 13 | 14 | ## Config Options 15 | 16 | There are few config options available, you need to define them in standard [CKEditor config](http://docs.ckeditor.com/#!/guide/dev_configuration) object. 17 | 18 | ### `config.openlink_modifier` 19 | 20 | Specifies what modifier key(s) should be pressed to open the link. It's based on `CKEDITOR.CTRL`, `CKEDITOR.SHIFT` and `CKEDITOR.ALT` members. 21 | 22 | You might also provide `0` as the value - it will cause any click to open the link, without need to press any modifier key. 23 | 24 | You can specify multiple modifiers, e.g. having `config.openlink_modifier = CKEDITOR.SHIFT + CKEDITOR.CTRL` would trigger the link with CTRL key or SHIFT key or both of them being pressed. 25 | 26 | Defaults to: `CKEDITOR.CTRL` 27 | 28 | ### `config.openlink_enableReadOnly` 29 | 30 | Determines whether this plugin feature should be available also in read-only mode. For backward compatibility reason this value is set to `false` by default. 31 | 32 | ``` 33 | config.openlink_enableReadOnly = true; // Allows opening links also while editor is in read-only mode. 34 | ``` 35 | 36 | Defaults to: `false` 37 | 38 | ### `config.openlink_target` 39 | 40 | Sets the target where new window should be open. 41 | 42 | ``` 43 | config.openlink_target = '_self'; // Will cause current page to be replaced by link click. 44 | ``` 45 | 46 | Defaults to: `'_blank'` 47 | 48 | ## Installation 49 | 50 | See the official [Plugin Installation Guide](http://docs.ckeditor.com/#!/guide/dev_plugins). 51 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Plugins/openlink/icons/hidpi/openLink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfTYPO3/frontend_editing/25352da0585179bfefe44972c2bf9f64303b3bd0/Resources/Public/JavaScript/Plugins/openlink/icons/hidpi/openLink.png -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Plugins/openlink/icons/openLink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriendsOfTYPO3/frontend_editing/25352da0585179bfefe44972c2bf9f64303b3bd0/Resources/Public/JavaScript/Plugins/openlink/icons/openLink.png -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Plugins/openlink/lang/bg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. 3 | * For licensing, see LICENSE.md or http://ckeditor.com/license 4 | */ 5 | CKEDITOR.plugins.setLang( 'openlink', 'bg', { 6 | menu: 'Отвори връзка' 7 | } ); -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Plugins/openlink/lang/de.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. 3 | * For licensing, see LICENSE.md or http://ckeditor.com/license 4 | */ 5 | CKEDITOR.plugins.setLang( 'openlink', 'de', { 6 | menu: 'Link öffnen' 7 | } ); 8 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Plugins/openlink/lang/en.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. 3 | * For licensing, see LICENSE.md or http://ckeditor.com/license 4 | */ 5 | CKEDITOR.plugins.setLang( 'openlink', 'en', { 6 | menu: 'Open link' 7 | } ); -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Plugins/openlink/lang/it.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. 3 | * For licensing, see LICENSE.md or http://ckeditor.com/license 4 | */ 5 | CKEDITOR.plugins.setLang( 'openlink', 'it', { 6 | menu: 'Apri collegamento' 7 | } ); 8 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Plugins/openlink/lang/pl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. 3 | * For licensing, see LICENSE.md or http://ckeditor.com/license 4 | */ 5 | CKEDITOR.plugins.setLang( 'openlink', 'pl', { 6 | menu: 'Otwórz odnośnik' 7 | } ); -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Plugins/openlink/lang/ru.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. 3 | * For licensing, see LICENSE.md or http://ckeditor.com/license 4 | */ 5 | CKEDITOR.plugins.setLang( 'openlink', 'ru', { 6 | menu: 'Открыть ссылку' 7 | } ); 8 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Scroller.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/FrontendEditing/Scroller 16 | * FrontendEditing.Scroller: Controller for basic scrolling 17 | */ 18 | define(['jquery', './Utils/Logger'], function createScrollerModule($, Logger) { 19 | 'use strict'; 20 | 21 | var log = Logger('FEditing:Utils:Scroller'); 22 | log.trace('--> createScrollerModule'); 23 | 24 | var framesPerSecond = 50; 25 | /*eslint-disable-next-line no-magic-numbers*/ 26 | var updateRate = 1000 / framesPerSecond; 27 | 28 | return function Scroller(target, scrollAreaTop, scrollAreaBottom) { 29 | log.info('create Scroller'); 30 | 31 | var $target = $(target); 32 | var $scrollTarget = $target; 33 | var isScrollTargetDocument = $target.is('iframe'); 34 | var $scrollAreaTop = $(scrollAreaTop); 35 | var $scrollAreaBottom = $(scrollAreaBottom); 36 | 37 | // Listening 38 | var enabled = false; 39 | // Scrolling 40 | var scrolling = false; 41 | var timeoutId = -1; 42 | var speed = 0; 43 | 44 | function stopScrolling() { 45 | log.info('create Scroller'); 46 | 47 | scrolling = false; 48 | if (timeoutId >= 0) { 49 | window.clearTimeout(timeoutId); 50 | timeoutId = -1; 51 | } 52 | } 53 | 54 | function startScrolling(newSpeed) { 55 | log.trace('startScrolling', newSpeed); 56 | 57 | if (!enabled) { 58 | return; 59 | } 60 | stopScrolling(); 61 | speed = newSpeed; 62 | 63 | if (speed !== 0) { 64 | log.debug('start Scrolling'); 65 | 66 | var checkFnc; 67 | var scrollY = $scrollTarget.scrollTop(); 68 | if (speed < 0) { 69 | checkFnc = checkScrollUpBound; 70 | checkScrollDownBound(scrollY + speed, true); 71 | } else { 72 | checkFnc = checkScrollDownBound; 73 | checkScrollUpBound(scrollY + speed, true); 74 | } 75 | 76 | scrolling = true; 77 | //share checkFnc variable 78 | var scroll = function () { 79 | if (scrolling) { 80 | var scrollY = $scrollTarget.scrollTop() + speed; 81 | log.debug('estimated scroll position', scrollY, speed); 82 | scrollY = checkFnc(scrollY); 83 | log.trace('new scroll position', scrollY, speed); 84 | $scrollTarget.scrollTop(scrollY); 85 | timeoutId = -1; 86 | if (scrolling) { 87 | timeoutId = window.setTimeout(scroll, updateRate); 88 | } 89 | } 90 | }; 91 | scroll(); 92 | 93 | } 94 | } 95 | 96 | function checkScrollUpBound(scrollY) { 97 | log.trace('checkScrollUpBound', scrollY); 98 | 99 | if (scrollY <= 0) { 100 | log.debug('up bound reached', scrollY); 101 | 102 | $scrollAreaTop.hide(); 103 | scrollY = 0; 104 | scrolling = false; 105 | } else { 106 | $scrollAreaTop.show(); 107 | } 108 | return scrollY; 109 | } 110 | 111 | function checkScrollDownBound(scrollY) { 112 | log.trace('checkScrollDownBound', scrollY); 113 | 114 | var maxScroll = getMaxScroll(); 115 | if (scrollY >= maxScroll) { 116 | log.debug( 117 | 'down bound reached [scrollY, maxScroll]', 118 | scrollY, 119 | maxScroll 120 | ); 121 | 122 | $scrollAreaBottom.hide(); 123 | scrollY = maxScroll; 124 | scrolling = false; 125 | } else { 126 | $scrollAreaBottom.show(); 127 | } 128 | return scrollY; 129 | } 130 | 131 | function enable() { 132 | log.info('enable'); 133 | 134 | enabled = true; 135 | scrolling = false; 136 | 137 | reload(); 138 | } 139 | 140 | function disable() { 141 | log.info('disable'); 142 | 143 | enabled = false; 144 | stopScrolling(); 145 | $scrollAreaBottom.hide(); 146 | $scrollAreaTop.hide(); 147 | } 148 | 149 | function reload() { 150 | log.debug('reload'); 151 | 152 | if (isScrollTargetDocument) { 153 | $scrollTarget = $target.contents(); 154 | if (!$scrollTarget || $scrollTarget.length === 0) { 155 | // seems more to be a restriction error 156 | // but as fact there is no reference of document 157 | throw new ReferenceError( 158 | 'Unable to access the document of the iframe.' 159 | ); 160 | } 161 | } 162 | if (getMaxScroll() <= 0) { 163 | $scrollAreaBottom.hide(); 164 | $scrollAreaTop.hide(); 165 | } else if (enabled) { 166 | var scrollY = $scrollTarget.scrollTop(); 167 | checkScrollUpBound(scrollY); 168 | checkScrollDownBound(scrollY); 169 | } 170 | } 171 | 172 | function getMaxScroll() { 173 | var scrollHeight; 174 | if (isScrollTargetDocument) { 175 | scrollHeight = $scrollTarget.height(); 176 | } else { 177 | scrollHeight = $target[0].scrollHeight; 178 | } 179 | var maxScroll = scrollHeight - $target[0].clientHeight; 180 | // round max scroll up so it match the height 181 | // eslint-disable-next-line no-magic-numbers 182 | var maxScroll = Math.round(maxScroll + 0.499); 183 | 184 | log.trace('getMaxScroll', maxScroll); 185 | 186 | return maxScroll; 187 | } 188 | 189 | return { 190 | reload: reload, 191 | enable: enable, 192 | disable: disable, 193 | startScrolling: startScrolling, 194 | stopScrolling: stopScrolling 195 | }; 196 | }; 197 | }); 198 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Storage.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/FrontendEditing/Storage 16 | * FrontendEditing.Storage: A wrapper class for using LocalStorage 17 | * has no dependencies to any other Frontend Editing related functionality 18 | * but is used in the main Frontend Editing app js. 19 | */ 20 | define([ 21 | './Contrib/immutable', 22 | './Utils/Logger' 23 | ], function createStorage(Immutable, Logger) { 24 | 'use strict'; 25 | 26 | var log = Logger('FEditing:localStorage'); 27 | log.trace('--> createStorage'); 28 | 29 | // TODO: use reducers and action with payload to create states to store 30 | 31 | var Storage = function (storageKey) { 32 | log.info('new Storage', storageKey); 33 | 34 | this.storageKey = storageKey; 35 | 36 | // Always empty the storage when it's constructed 37 | this.clear(); 38 | }; 39 | 40 | Storage.prototype = { 41 | addSaveItem: function (id, saveItem) { 42 | log.debug('add save item', id, saveItem); 43 | 44 | var items = this.getAllDataAsMap(); 45 | var saveItems = this._getSaveItems(items); 46 | 47 | saveItems = saveItems.set(id, saveItem); 48 | var addedItems = items.set('saveItems', saveItems); 49 | this._persistItems(addedItems); 50 | }, 51 | 52 | _getSaveItems: function (map) { 53 | var saveItems = map.get('saveItems'); 54 | if (!saveItems) { 55 | return Immutable.Map({}); 56 | } 57 | return Immutable.Map(saveItems); 58 | }, 59 | 60 | getSaveItems: function () { 61 | var items = this.getAllDataAsMap(); 62 | return this._getSaveItems(items); 63 | }, 64 | 65 | getAllData: function () { 66 | return JSON.parse(localStorage.getItem(this.storageKey)); 67 | }, 68 | 69 | getAllDataAsMap: function () { 70 | return Immutable.Map(this.getAllData()); 71 | }, 72 | 73 | addItem: function (key, item) { 74 | log.debug('add item', key, item); 75 | 76 | var items = this.getAllDataAsMap(); 77 | var addedItems = items.set(key, item); 78 | this._persistItems(addedItems); 79 | }, 80 | 81 | clear: function () { 82 | var items = this.getAllDataAsMap(); 83 | 84 | log.debug('clear saveItems', items); 85 | 86 | // Reset the saveItems but keep the rest of the states 87 | var addedItems = items.set('saveItems', null); 88 | 89 | log.trace('new item', addedItems); 90 | 91 | this._persistItems(addedItems); 92 | }, 93 | 94 | _persistItems: function (items) { 95 | log.trace('_persistItems', items); 96 | 97 | localStorage.setItem(this.storageKey, JSON.stringify(items)); 98 | }, 99 | 100 | isEmpty: function () { 101 | var isEmpty = this.getSaveItems() 102 | .isEmpty(); 103 | 104 | log.trace('isEmpty', isEmpty); 105 | 106 | return isEmpty; 107 | }, 108 | 109 | countSaveItems: function () { 110 | var count = this.getSaveItems() 111 | .count(); 112 | 113 | log.trace('countSaveItems', count); 114 | 115 | return count; 116 | } 117 | }; 118 | 119 | return Storage; 120 | }); 121 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Utils/IndexedDB.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/FrontendEditing/Utils/IndexedDB 16 | * Initialize dexie (IndexedDB wrapper) and returns opened dexie 17 | */ 18 | define(['../Contrib/dexie.min'], function createIndexedDB(Dexie) { 19 | 'use strict'; 20 | 21 | var db = new Dexie('FrontendEditing'); 22 | db.version(1) 23 | .stores({ 24 | logs: '++id, timestamp, name, url, level, channel, message, stack' 25 | }); 26 | 27 | db.open(); 28 | 29 | return db; 30 | }); 31 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Utils/Logger.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/FrontendEditing/Utils/Logger 16 | * Simple logger implementation as a thin anylog (ulog) layer 17 | */ 18 | define(['../Contrib/ulog/ulog'], function createLogger(ulog) { 19 | 'use strict'; 20 | 21 | return ulog; 22 | }); 23 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Utils/LoggerPersist.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/FrontendEditing/Utils/LoggerPersist 16 | * Logger extension to make logs persistable. 17 | * Side effect: 18 | * Create highorder ulog channel. 19 | * Creates 2 ulog outputs to persist 20 | * - persist: with indexedDb in use 21 | * - server: calling the sendCallback function 22 | * Control: 23 | * sendCallback = 24 | * config = { 25 | * highorder_log: 26 | * log_highorder: 27 | * log: 28 | * log_output: 29 | * log_drain: 30 | * } 31 | */ 32 | define([ 33 | '../Contrib/ulog/ulog', 34 | './IndexedDB', 35 | ], function addLoggerPersist(ulog, db) { 36 | 'use strict'; 37 | 38 | var _sendCallback = null; 39 | var configKeys = [ 40 | 'highorder_log', 41 | 'log_highorder', 42 | 'log', 43 | 'log_output', 44 | 'log_drain' 45 | ]; 46 | 47 | addPersistOutputs(); 48 | addHighorderChannel(); 49 | 50 | return { 51 | set config(cfg) { 52 | for (var key in configKeys) { 53 | if (cfg[key] && typeof cfg[key] === 'string') { 54 | ulog.set(key, cfg[key]); 55 | } 56 | } 57 | }, 58 | get config() { 59 | return ulog.config; 60 | }, 61 | 62 | set sendCallback(sendCallback) { 63 | if (typeof sendCallback === 'function') { 64 | _sendCallback = sendCallback; 65 | } 66 | }, 67 | get sendCallback() { 68 | return _sendCallback; 69 | }, 70 | }; 71 | 72 | /** 73 | * Adds the highorder ulog channel and override the output ulog channel 74 | * for levels above configure highorder_log. The new highorder ulog channel 75 | * can be configured as usual in ulog with log_highorder config. 76 | * Eg. log = debug and highorder_log = warn 77 | * -> warn and errors has highorder channel 78 | * -> info and debug has output channel 79 | * -> trace has drain channel 80 | */ 81 | function addHighorderChannel() { 82 | ulog.use({ 83 | channels: { 84 | highorder: { 85 | out: [ 86 | console, 87 | ], 88 | }, 89 | }, 90 | settings: { 91 | highorderLog: { 92 | config: 'highorder_log', 93 | prop: { 94 | default: 'none', 95 | }, 96 | }, 97 | highorder: { 98 | config: 'log_highorder', 99 | prop: { 100 | default: 'console', 101 | }, 102 | }, 103 | }, 104 | ext: function (logger) { 105 | function highorderEnabledFor(level) { 106 | var highorderLog = logger.highorderLog.toUpperCase(); 107 | return logger[highorderLog] >= logger[level.toUpperCase()]; 108 | } 109 | 110 | logger.highorderEnabledFor = highorderEnabledFor; 111 | }, 112 | after: function (logger) { 113 | // eslint-disable-next-line guard-for-in 114 | for (var level in this.levels) { 115 | if (logger.enabledFor(level)) { 116 | var channel = 'output'; 117 | if (logger.highorderEnabledFor(level)) { 118 | channel = 'highorder'; 119 | } 120 | logger[level] = logger.channels[channel].fns[level]; 121 | } 122 | } 123 | }, 124 | }); 125 | } 126 | 127 | /** 128 | * Adds ulog outputs used to persist log records. 129 | */ 130 | function addPersistOutputs() { 131 | ulog.use({ 132 | outputs: { 133 | server: serverOutput, 134 | persist: persistOutput 135 | }, 136 | }); 137 | } 138 | 139 | /** 140 | * Returns a function that add log entry in an indexedDB. 141 | */ 142 | function persistOutput() { 143 | return function persistLog(rec) { 144 | var logRecord = createLogRecord(rec); 145 | try { 146 | db.logs.add(logRecord); 147 | } catch (exception) { 148 | error( 149 | 'Unable to persist log record', 150 | exception, 151 | logRecord 152 | ); 153 | } 154 | }; 155 | } 156 | 157 | /** 158 | * Returns a function that call the sendCallback function if defined. 159 | */ 160 | function serverOutput() { 161 | return function sendLogToServer(rec) { 162 | var logRecord = createLogRecord(rec); 163 | if (_sendCallback) { 164 | try { 165 | _sendCallback(logRecord); 166 | } catch (exception) { 167 | error( 168 | 'Error occured during callback function', 169 | exception, 170 | logRecord 171 | ); 172 | } 173 | } else { 174 | warn( 175 | 'sendLogToServer failed cause _sendCallback is not defined', 176 | logRecord 177 | ); 178 | } 179 | }; 180 | } 181 | 182 | /** 183 | * Create a log record used to persisting 184 | * @param rec 185 | * @return {{ 186 | * timestamp: number, 187 | * url: string, 188 | * name: string, 189 | * level: number, 190 | * channel: string, 191 | * message: [], 192 | * stack: (*|string) 193 | * }} 194 | */ 195 | function createLogRecord(rec) { 196 | var error = new Error(); 197 | var url = window && window.location 198 | ? window.location.href : 'no window.location'; 199 | return { 200 | timestamp: Date.now(), 201 | url: url, 202 | name: rec.name, 203 | level: rec.level, 204 | channel: rec.channel, 205 | message: rec.message, 206 | stack: error.stack 207 | }; 208 | } 209 | 210 | /** 211 | * Log error in console directly to prevent an infinite loop. 212 | */ 213 | function error() { 214 | // eslint-disable-next-line no-console 215 | console.error(arguments); 216 | } 217 | 218 | /** 219 | * Log warn in console directly to prevent an infinite loop. 220 | */ 221 | function warn() { 222 | // eslint-disable-next-line no-console 223 | console.warn(arguments); 224 | } 225 | }); 226 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Utils/LoggerWindowError.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/FrontendEditing/Utils/LoggerWindowError 16 | * Add window and promises error handler to log them since nobody is in charge. 17 | */ 18 | define(['../Contrib/ulog/ulog'], function addLoggerWindowErrorHandler(ulog) { 19 | 'use strict'; 20 | 21 | var ulogger = ulog('FEditing:Main'); 22 | var eventListeners = [{ 23 | target: window, 24 | name: 'error', 25 | listener: errorExceptionHandler, 26 | }, { 27 | target: window, 28 | name: 'unhandledrejection', 29 | listener: unhandledRejectionHandler, 30 | }]; 31 | 32 | return { 33 | register: function () { 34 | eventListeners.forEach(function addEventListener(record) { 35 | record.target.addEventListener(record.name, record.listener); 36 | }); 37 | }, 38 | unregister: function () { 39 | eventListeners.forEach(function addEventListener(record) { 40 | record.target.removeEventListener(record.name, record.listener); 41 | }); 42 | } 43 | }; 44 | 45 | /** 46 | * Log global unhandled errors and prevent default output. 47 | * @return {boolean} 48 | */ 49 | function errorExceptionHandler() { 50 | try { 51 | ulogger.error(arguments); 52 | return false; 53 | } catch (exception) { /*let fallback to default error behaviour*/ 54 | } 55 | 56 | return true; 57 | } 58 | 59 | /** 60 | * Log unhandled rejection from promises and prevent default output. 61 | * @param event 62 | * @return {boolean} 63 | */ 64 | function unhandledRejectionHandler(event) { 65 | try { 66 | ulogger.error('Unhandled rejection occured.', event.reason); 67 | 68 | event.preventDefault(); 69 | return false; 70 | } catch (exception) { /*let fallback to default error behaviour*/ 71 | } 72 | 73 | return true; 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /Resources/Public/JavaScript/Utils/Translator.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/FrontendEditing/Utils/Translator 16 | * Simple language translator with mapping functionality 17 | */ 18 | define(['jquery', './Logger'], function createTranslatorFactory($, Logger) { 19 | 'use strict'; 20 | 21 | var log = Logger('FEditing:Utils:TranslatorFactory'); 22 | log.trace('--> createTranslatorFactory'); 23 | 24 | return function TranslatorFactory(labels, mappings) { 25 | log.debug('create Translator Factory'); 26 | 27 | var defaulSettigns = { 28 | translationLabels: { 29 | 'error.type.key_invalid': 'Invalid translation key: \'{0}\'', 30 | 'translator.error.namespace_not_found': 31 | 'Invalid namespace key: \'{0}\'', 32 | }, 33 | namespaceMappings: { 34 | translator: { 35 | translationKeyInvalid: 36 | 'error.type.key_invalid', 37 | namespaceMappingNotFound: 38 | 'translator.error.namespace_not_found', 39 | }, 40 | }, 41 | }; 42 | 43 | var translationLabels = {}; 44 | var namespaceMappings = {}; 45 | 46 | setTranslationLabels(labels); 47 | setNamespaceMappings(mappings); 48 | 49 | function setTranslationLabels(newTranslationLabels) { 50 | log.trace('setTranslationLabels', newTranslationLabels); 51 | 52 | translationLabels = $.extend( 53 | {}, 54 | defaulSettigns.translationLabels, 55 | newTranslationLabels 56 | ); 57 | 58 | log.debug('set new TranslationLabels', translationLabels); 59 | } 60 | 61 | function setNamespaceMappings(newNamespaceMappings) { 62 | log.trace('setNamespaceMappings', newNamespaceMappings); 63 | 64 | namespaceMappings = $.extend( 65 | true, 66 | {}, 67 | defaulSettigns.namespaceMappings, 68 | newNamespaceMappings 69 | ); 70 | 71 | log.debug('set new NamespaceMappings:', namespaceMappings); 72 | } 73 | 74 | function getNamespaceMapping(namespace) { 75 | if (!namespace) { 76 | var keys = Object.keys(translationLabels); 77 | var retVal = {}; 78 | for (var i = 0; i < keys.length; i++) { 79 | retVal[keys[i]] = keys[i]; 80 | } 81 | return retVal; 82 | } 83 | if (namespaceMappings[namespace]) { 84 | return namespaceMappings[namespace]; 85 | } 86 | throw new TypeError(_translate( 87 | namespaceMappings.translator.namespaceMappingNotFound, 88 | namespace 89 | )); 90 | } 91 | 92 | function _translate(key) { 93 | var s = translationLabels[key]; 94 | if (arguments.length > 1) { 95 | for (var i = 0; i < arguments.length - 1; i++) { 96 | var reg = new RegExp('\\{' + i + '\\}', 'gm'); 97 | s = s.replace(reg, arguments[i + 1]); 98 | } 99 | } 100 | 101 | log.debug('translation:', key, s); 102 | 103 | return s; 104 | } 105 | 106 | return { 107 | setTranslationLabels: setTranslationLabels, 108 | setNamespaceMappings: setNamespaceMappings, 109 | createTranslator: function (namespace) { 110 | log.info('create Translator:', namespace); 111 | 112 | //used to get keys 113 | return { 114 | /** 115 | * Gets the namespace mapping object to override 116 | * @returns {object} namespace to translation key mapping 117 | */ 118 | getKeys: function () { 119 | return $.extend({}, getNamespaceMapping(namespace)); 120 | }, 121 | /** 122 | * Get translation string by passed key argument and replace 123 | * curly bracket marker with passed rest arguments. 124 | * If first variable (after key argument) is an array of 125 | * strings this array will be used instead of functional 126 | * arguments. 127 | * If no translation for the key is available it will throw 128 | * an TypeError exception. 129 | * @param {string} key Server side translation key 130 | * @returns {string} translation 131 | */ 132 | translate: function (key) { 133 | log.debug('translate:', key); 134 | 135 | if (Array.isArray(arguments[1])) { 136 | var parameters = [key]; 137 | parameters.push.apply(parameters, arguments[1]); 138 | return this.translate.apply(this, parameters); 139 | } 140 | if (translationLabels[key]) { 141 | return _translate.apply(this, arguments); 142 | } 143 | throw new TypeError( 144 | _translate( 145 | namespaceMappings.translator 146 | .translationKeyInvalid 147 | ) 148 | ); 149 | } 150 | }; 151 | }, 152 | }; 153 | }; 154 | }); 155 | -------------------------------------------------------------------------------- /Resources/Public/Templates/Close.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Close 7 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "friendsoftypo3/frontend-editing", 3 | "type": "typo3-cms-extension", 4 | "description": "Enable editors to work with the content in the most intuitive way possible", 5 | "homepage": "https://extensions.typo3.org/extension/frontend_editing/", 6 | "support": { 7 | "issues": "https://github.com/FriendsOfTYPO3/frontend_editing/issues", 8 | "source": "https://github.com/FriendsOfTYPO3/frontend_editing", 9 | "docs": "https://docs.typo3.org/p/friendsoftypo3/frontend-editing/main/en-us/" 10 | }, 11 | "license": [ 12 | "GPL-2.0-or-later" 13 | ], 14 | "keywords": [ 15 | "TYPO3 CMS", 16 | "Frontend Editing" 17 | ], 18 | "require": { 19 | "typo3/cms-core": "^11.5", 20 | "typo3/cms-rte-ckeditor": "^11.5", 21 | "typo3/cms-viewpage": "^11.5" 22 | }, 23 | "require-dev": { 24 | "typo3/minimal": "^11.5", 25 | "nimut/testing-framework": "^6.0", 26 | "squizlabs/php_codesniffer": "^3.3", 27 | "phpunit/phpunit": "^8.5", 28 | "friendsofphp/php-cs-fixer": "^2.15", 29 | "helmich/typo3-typoscript-lint": "^2.0" 30 | }, 31 | "conflict": { 32 | "typo3/cms-feedit": "*", 33 | "typo3/class-alias-loader": "< 1.1.0" 34 | }, 35 | "replace": { 36 | "typo3-ter/frontend-editing": "self.version" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "TYPO3\\CMS\\FrontendEditing\\": "Classes/" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "TYPO3\\CMS\\FrontendEditing\\Tests\\": "Tests/", 46 | "TYPO3\\CMS\\Core\\Tests\\": ".build/vendor/typo3/cms/typo3/sysext/core/Tests/", 47 | "TYPO3\\CMS\\Fluid\\Tests\\": ".build/vendor/typo3/cms/typo3/sysext/fluid/Tests/" 48 | } 49 | }, 50 | "config": { 51 | "vendor-dir": ".build/vendor", 52 | "bin-dir": ".build/bin", 53 | "preferred-install": { 54 | "typo3/cms": "source" 55 | }, 56 | "allow-plugins": { 57 | "typo3/class-alias-loader": true, 58 | "typo3/cms-composer-installers": true 59 | } 60 | }, 61 | "scripts": { 62 | "ci:php:lint": "find *.php Classes/ Configuration/ Tests/ -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l", 63 | "ci:php:sniff": "php-cs-fixer fix --config=.php-cs-fixer.php -v --dry-run --using-cache=no --diff", 64 | "cs:php:fix": "php-cs-fixer fix --config=.php-cs-fixer.php -v --using-cache=no", 65 | "ci:ts:lint": "typoscript-lint -c Configuration/TsLint.yml --ansi -n --fail-on-warnings -vvv Configuration/TypoScript/", 66 | "ci:tests:unit": "phpunit -c .build/vendor/nimut/testing-framework/res/Configuration/UnitTests.xml Tests/Unit", 67 | "ci:tests:functional": "phpunit -c .build/vendor/nimut/testing-framework/res/Configuration/FunctionalTests.xml Tests/Functional", 68 | "ci:tests": [ 69 | "@ci:tests:unit", 70 | "@ci:tests:functional" 71 | ], 72 | "ci:dynamic": [ 73 | "@ci:tests" 74 | ], 75 | "ci:static": [ 76 | "@ci:php:lint", 77 | "@ci:php:sniff", 78 | "@ci:ts:lint" 79 | ], 80 | "ci": [ 81 | "@ci:static" 82 | ], 83 | "pre-autoload-dump": [ 84 | "mkdir -p .build/public/typo3conf/ext/", 85 | "test -L .build/public/typo3conf/ext/frontend_editing || ln -snvf ../../../../. .build/public/typo3conf/ext/frontend_editing" 86 | ], 87 | "post-autoload-dump": [ 88 | "@prepare-extension-test-structure" 89 | ], 90 | "prepare-extension-test-structure": [ 91 | "Nimut\\TestingFramework\\Composer\\ExtensionTestEnvironment::prepare" 92 | ], 93 | "docs:generate": [ 94 | "docker run --rm t3docs/render-documentation show-shell-commands > tempfile.sh; echo 'dockrun_t3rd makehtml' >> tempfile.sh; bash tempfile.sh; rm tempfile.sh" 95 | ] 96 | }, 97 | "extra": { 98 | "branch-alias": { 99 | "dev-master": "0.0.x-dev" 100 | }, 101 | "typo3/cms": { 102 | "extension-key": "frontend_editing", 103 | "cms-package-dir": "{$vendor-dir}/typo3/cms", 104 | "web-dir": ".build/public", 105 | "app-dir": ".build" 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ext_conf_template.txt: -------------------------------------------------------------------------------- 1 | # cat=basic/enable/20; type=string; label=LLL:EXT:frontend_editing/Resources/Private/Language/locallang_be.xlf:extension_management.content_editable_wrapper_tag_name 2 | contentEditableWrapperTagName = div 3 | 4 | # cat=features/enable/10; type=boolean; label=LLL:EXT:frontend_editing/Resources/Private/Language/locallang_be.xlf:extension_management.placeholders 5 | enablePlaceholders = 1 6 | 7 | # cat=features/enable/20; type=boolean; label=LLL:EXT:frontend_editing/Resources/Private/Language/locallang_be.xlf:extension_management.disable_loading_screen 8 | disableLoadingScreen = 0 9 | -------------------------------------------------------------------------------- /ext_emconf.php: -------------------------------------------------------------------------------- 1 | 'Frontend Editing', 5 | 'description' => 'Enable editors to work with the content in the most intuitive way possible', 6 | 'category' => 'fe', 7 | 'state' => 'stable', 8 | 'clearCacheOnLoad' => true, 9 | 'author' => 'TYPO3 Community', 10 | 'author_email' => 'typo3cms@typo3.org', 11 | 'author_company' => '', 12 | 'version' => '4.0.8', 13 | 'constraints' => [ 14 | 'depends' => [ 15 | 'typo3' => '11.5.0-11.5.99', 16 | 'rte_ckeditor' => '11.5.0-11.5.99', 17 | 'viewpage' => '11.5.0-11.5.99', 18 | ], 19 | 'conflicts' => [ 20 | 'feedit' => '', 21 | ], 22 | 'suggests' => [], 23 | ], 24 | ]; 25 | -------------------------------------------------------------------------------- /ext_localconf.php: -------------------------------------------------------------------------------- 1 | viewhelper by the one from EXT:frontend_editing 9 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['core'][] = 'TYPO3\\CMS\\FrontendEditing\\ViewHelpers'; 10 | 11 | // Exclude Frontend Editing parameters to prevent errors when $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['enforceValidation'] = true 12 | $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = 'fe_edit'; 13 | $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = 'show_hidden_items'; 14 | 15 | // Copy configuration array so we can have our own. 16 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['frontendTcaDatabaseRecord'] = 17 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['tcaDatabaseRecord']; 18 | 19 | // Add processor for frontend-related RTE configuration 20 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['frontendTcaDatabaseRecord'][ 21 | \TYPO3\CMS\FrontendEditing\Backend\Form\FormDataProvider\TcaFrontendRichtextConfiguration::class 22 | ] = [ 23 | 'before' => [ 24 | \TYPO3\CMS\Backend\Form\FormDataProvider\TcaText::class 25 | ] 26 | ]; 27 | 28 | // Add RTE presets for frontend use 29 | $GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['bronly'] = 'EXT:frontend_editing/Configuration/RTE/BrOnly.yaml'; 30 | $GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['listonly'] = 'EXT:frontend_editing/Configuration/RTE/ListOnly.yaml'; 31 | 32 | /** 33 | * Hooks 34 | */ 35 | // Register the edit panel view 36 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit'] = 37 | \TYPO3\CMS\FrontendEditing\EditingPanel\FrontendEditingPanel::class; 38 | 39 | // Modify every link to save unparsed links but allow editors to still browse the website 40 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typoLink_PostProc']['frontend_editing'] = 41 | \TYPO3\CMS\FrontendEditing\Hook\TypoLinkPostProcHook::class . '->modifyFinalLinkTag'; 42 | 43 | /** 44 | * Custom icons 45 | */ 46 | $iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class); 47 | 48 | $iconRegistry->registerIcon( 49 | 'ext-news-wizard-icon', 50 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 51 | ['source' => 'EXT:news/Resources/Public/Icons/plugin_wizard.svg'] 52 | ); 53 | $iconRegistry->registerIcon( 54 | 'grid-elements-2ColumnGrid', 55 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 56 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/2-column-grid.svg'] 57 | ); 58 | $iconRegistry->registerIcon( 59 | 'grid-elements-3ColumnGrid', 60 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 61 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/3-column-grid.svg'] 62 | ); 63 | $iconRegistry->registerIcon( 64 | 'grid-elements-4ColumnGrid', 65 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 66 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/4-column-grid.svg'] 67 | ); 68 | $iconRegistry->registerIcon( 69 | 'grid-elements-adv1ColumnGrid', 70 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 71 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/adv1-column-grid.svg'] 72 | ); 73 | $iconRegistry->registerIcon( 74 | 'grid-elements-adv2ColumnGrid', 75 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 76 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/adv2-column-grid.svg'] 77 | ); 78 | $iconRegistry->registerIcon( 79 | 'grid-elements-adv3ColumnGrid', 80 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 81 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/adv3-column-grid.svg'] 82 | ); 83 | $iconRegistry->registerIcon( 84 | 'grid-elements-adv4ColumnGrid', 85 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 86 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/adv4-column-grid.svg'] 87 | ); 88 | $iconRegistry->registerIcon( 89 | 'grid-elements-collapsibleGroup', 90 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 91 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/collapsibleGroup.svg'] 92 | ); 93 | $iconRegistry->registerIcon( 94 | 'grid-elements-collapsible', 95 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 96 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/collapsible.svg'] 97 | ); 98 | $iconRegistry->registerIcon( 99 | 'grid-elements-parallax', 100 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 101 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/parallax.svg'] 102 | ); 103 | $iconRegistry->registerIcon( 104 | 'grid-elements-simpleAccordion', 105 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 106 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/simpleAccordion.svg'] 107 | ); 108 | $iconRegistry->registerIcon( 109 | 'grid-elements-sliderContainer', 110 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 111 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/sliderContainer.svg'] 112 | ); 113 | $iconRegistry->registerIcon( 114 | 'grid-elements-tabGroup', 115 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 116 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/tabGroup.svg'] 117 | ); 118 | $iconRegistry->registerIcon( 119 | 'grid-elements-tab', 120 | \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, 121 | ['source' => 'EXT:theme_t3kit/Resources/Public/Icons/GridElements/tab.svg'] 122 | ); 123 | }; 124 | 125 | $boot(); 126 | unset($boot); 127 | -------------------------------------------------------------------------------- /ext_tables.php: -------------------------------------------------------------------------------- 1 | FrontendEditingModuleController::class . '::showAction', 17 | 'access' => 'user,group', 18 | 'name' => 'web_FrontendEditing', 19 | 'icon' => 'EXT:frontend_editing/Resources/Public/Icons/ext_icon.png', 20 | 'labels' => 'LLL:EXT:frontend_editing/Resources/Private/Language/locallang_mod.xlf', 21 | ] 22 | ); 23 | }; 24 | 25 | $boot(); 26 | unset($boot); 27 | -------------------------------------------------------------------------------- /ext_typoscript_setup.typoscript: -------------------------------------------------------------------------------- 1 | module.tx_frontendediting { 2 | settings { 3 | customRecords { 4 | tx_news_domain_model_news { 5 | } 6 | } 7 | } 8 | } 9 | --------------------------------------------------------------------------------