From fde2759722b2a4719353182aff49201c4ce82365 Mon Sep 17 00:00:00 2001 From: Sven Wappler Date: Mon, 16 Aug 2021 16:15:21 +0200 Subject: [PATCH] first commit --- Classes/Controller/BookmarksController.php | 109 ++++++ Classes/Model/Bookmark.php | 237 ++++++++++++ Classes/Model/Bookmarks.php | 241 +++++++++++++ Configuration/FlexForms/Bookmarks.xml | 54 +++ Configuration/TCA/Overrides/sys_template.php | 15 + Configuration/TCA/Overrides/tt_content.php | 45 +++ Configuration/TypoScript/constants.typoscript | 16 + Configuration/TypoScript/setup.typoscript | 34 ++ Documentation/AdministratorManual/Index.rst | 28 ++ Documentation/Changelog/2.0.0.rst | 39 ++ Documentation/Configuration/Index.rst | 97 +++++ Documentation/Images/screenshot-fe.png | Bin 0 -> 16333 bytes Documentation/Images/screenshot-styled-fe.png | Bin 0 -> 13366 bytes Documentation/Includes.txt | 14 + Documentation/Index.rst | 57 +++ Documentation/Introduction/Index.rst | 33 ++ Documentation/Settings.cfg | 17 + Documentation/Sitemap/Index.rst | 12 + Documentation/Support/Index.rst | 28 ++ LICENSE.txt | 339 ++++++++++++++++++ README.md | 28 ++ Resources/Private/.htaccess | 11 + Resources/Private/Language/locallang.xlf | 30 ++ Resources/Private/Layouts/Default.html | 23 ++ Resources/Private/Partials/ListItem.html | 15 + .../Private/Templates/Bookmarks/Index.html | 34 ++ .../Scripts/JavaScript/bookmark_pages.js | 162 +++++++++ composer.json | 37 ++ ext_emconf.php | 38 ++ ext_icon.png | Bin 0 -> 5636 bytes ext_localconf.php | 41 +++ ext_tables.sql | 7 + 32 files changed, 1841 insertions(+) create mode 100644 Classes/Controller/BookmarksController.php create mode 100644 Classes/Model/Bookmark.php create mode 100644 Classes/Model/Bookmarks.php create mode 100644 Configuration/FlexForms/Bookmarks.xml create mode 100644 Configuration/TCA/Overrides/sys_template.php create mode 100644 Configuration/TCA/Overrides/tt_content.php create mode 100644 Configuration/TypoScript/constants.typoscript create mode 100644 Configuration/TypoScript/setup.typoscript create mode 100644 Documentation/AdministratorManual/Index.rst create mode 100644 Documentation/Changelog/2.0.0.rst create mode 100644 Documentation/Configuration/Index.rst create mode 100644 Documentation/Images/screenshot-fe.png create mode 100644 Documentation/Images/screenshot-styled-fe.png create mode 100644 Documentation/Includes.txt create mode 100644 Documentation/Index.rst create mode 100644 Documentation/Introduction/Index.rst create mode 100644 Documentation/Settings.cfg create mode 100644 Documentation/Sitemap/Index.rst create mode 100644 Documentation/Support/Index.rst create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 Resources/Private/.htaccess create mode 100644 Resources/Private/Language/locallang.xlf create mode 100644 Resources/Private/Layouts/Default.html create mode 100644 Resources/Private/Partials/ListItem.html create mode 100644 Resources/Private/Templates/Bookmarks/Index.html create mode 100644 Resources/Public/Scripts/JavaScript/bookmark_pages.js create mode 100644 composer.json create mode 100644 ext_emconf.php create mode 100644 ext_icon.png create mode 100644 ext_localconf.php create mode 100644 ext_tables.sql diff --git a/Classes/Controller/BookmarksController.php b/Classes/Controller/BookmarksController.php new file mode 100644 index 0000000..759b3ab --- /dev/null +++ b/Classes/Controller/BookmarksController.php @@ -0,0 +1,109 @@ +view->assignMultiple([ + 'bookmark' => $bookmark->toArray() + ]); + } + + /** + * Adds the current page as bookmark and renders/returns updated list as html + * + * This is meant to be called by ajax (typoscript_rendering) + * + * @param array $localBookmarks + */ + public function bookmarkAction($localBookmarks = []) + { + // use the parameter directly and ignore chash because url is submitted by JS + $url = GeneralUtility::_GP('url'); + $url = $url ? $url : null; + + $bookmark = Bookmark::createFromCurrent($url); + + $bookmarks = new Bookmarks(); + $bookmarks->merge($localBookmarks); + $bookmarks->addBookmark($bookmark); + $bookmarks->persist(); + + $this->updateAndSendList($bookmarks); + } + + /** + * Remove a bookmark from list and renders/returns updated list as html + * + * This is meant to be called by ajax (typoscript_rendering) + * + * @param string $id + * @param array $localBookmarks + */ + public function deleteAction($id = '', $localBookmarks = []) + { + $bookmarks = new Bookmarks(); + $bookmarks->merge($localBookmarks); + if ($id) { + $bookmarks->removeBookmark($id); + $bookmarks->persist(); + } + $this->updateAndSendList($bookmarks); + } + + /** + * Action to get bookmark list + * + * @param array $localBookmarks + */ + public function listEntriesAction($localBookmarks = []) + { + $bookmarks = new Bookmarks(); + $bookmarks->merge($localBookmarks); + $this->updateAndSendList($bookmarks); + } + + /** + * This is for ajax requests + * + * @param Bookmarks $bookmarks + */ + public function updateAndSendList(Bookmarks $bookmarks) + { + // check if we bookmarked the current page + $bookmark = Bookmark::createFromCurrent(); + $isBookmarked = $bookmarks->bookmarkExists($bookmark); + + // build the ajax response data + $response = [ + 'isBookmarked' => $isBookmarked, + 'bookmarks' => $bookmarks->getBookmarksForLocalStorage() + ]; + + header('Content-Type: application/json'); + echo json_encode($response); + die(); + } +} diff --git a/Classes/Model/Bookmark.php b/Classes/Model/Bookmark.php new file mode 100644 index 0000000..bc49897 --- /dev/null +++ b/Classes/Model/Bookmark.php @@ -0,0 +1,237 @@ +id = $url['id']; + $this->title = $url['title']; + $this->url = $url['url']; + $this->pid = $url['pid']; + $this->parameter = $url['parameter']; + } else { + $this->id = md5($pid . ':' . $parameter); + $this->title = $title; + $this->url = $url; + $this->pid = $pid; + $this->parameter = $parameter; + } + } + + /** + * Create bookmark from the current TSFE page + * + * @param string url to bookmark, if null TYPO3_REQUEST_URL will be used - which is wrong when we're in ajax context, then we use HTTP_REFERER + * @return Bookmark + */ + public static function createFromCurrent($url = null) + { + if ($url === null) { + if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') { + //request is ajax + $url = GeneralUtility::getIndpEnv('HTTP_REFERER'); + } else { + $url = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'); + } + } + + $pid = self::getFrontend()->id; + $title = self::getCurrentPageTitle(); + + /* + + The idea was to store get parameters to make bookmark handling more flexible. + Unfortunately that didn't worked out. + + When we use ajax to trigger bookmarking the current page, we can pass the current url as parameter. + But the url doesn't have the parameters in it when you use speaking urls (realurl, simulatestatic, ...). + The problem is that there's no common api to decode urls and get the parameters. + + One solution would be to make the parameters available to the ajax javascript during page rendering. + + We skip all this and use a bit from the url for hashing and add the page id. + + */ + + $urlParts = parse_url($url); + $parameter = $urlParts['path'] . '?' . $urlParts['query'] . '#' . $urlParts['fragment']; + + return new self($url, $title, $pid, $parameter); + + /* + * So what is the idea of storing the pid and the get vars? + * + * This might makes sense if urls changed for the same page (realurl). + * With this information the new working url can be restored. + * + * Not sure which way is better ... + */ + // $parameter = (array)GeneralUtility::_GET(); + // unset($parameter['id']); + // // @todo remove cHash? + // ksort($parameter); + // $parameter = $parameter ? GeneralUtility::implodeArrayForUrl(false, $parameter) : ''; + // + // return new self($url, $title, $pid, $parameter); + } + + /** + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * @param string $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * @param string $url + */ + public function setUrl($url) + { + $this->url = $url; + } + + /** + * @return int + */ + public function getPid() + { + return $this->pid; + } + + /** + * @param int $pid + */ + public function setPid($pid) + { + $this->pid = $pid; + } + + /** + * @return string + */ + public function getParameter() + { + return $this->parameter; + } + + /** + * @param string $parameter + */ + public function setParameter($parameter) + { + $this->parameter = $parameter; + } + + /** + * Returns the bookmark data as array + * + * @return array + */ + public function toArray() + { + return [ + 'id' => $this->id, + 'title' => $this->title, + 'url' => $this->url, + 'pid' => $this->pid, + 'parameter' => $this->parameter, + ]; + } + + /** + * Get the current page title + * @return string + */ + protected static function getCurrentPageTitle() + { + return self::getFrontend()->altPageTitle? self::getFrontend()->altPageTitle : self::getFrontend()->page['title']; + } + + /** + * + * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController + */ + protected static function getFrontend() + { + return $GLOBALS['TSFE']; + } +} diff --git a/Classes/Model/Bookmarks.php b/Classes/Model/Bookmarks.php new file mode 100644 index 0000000..d21e23b --- /dev/null +++ b/Classes/Model/Bookmarks.php @@ -0,0 +1,241 @@ +getUser()->user) && $this->getUser()->user[$this->getUser()->userid_column]) { + $bookmarks = $this->getUser()->user[self::BOOKMARKS_COLUMN]; + $bookmarks = (array)GeneralUtility::xml2array($bookmarks); + foreach ($bookmarks as $bookmark) { + if (isset($bookmark['id'])) { + $this->bookmarks[$bookmark['id']] = new Bookmark($bookmark); + } + } + } + } + + /** + * persist bookmarks if needed + */ + public function __destruct() + { + $this->persist(); + } + + /** + * Get all Bookmarks + * + * @return array|Bookmark[] + */ + public function getBookmarks() + { + return (array)$this->bookmarks; + } + + /** + * clear all bookmarks + */ + public function clearBookmarks() + { + $this->bookmarks = []; + $this->changeFlag = true; + } + + /** + * Add a bookmark + * + * @param Bookmark $bookmark + */ + public function addBookmark(Bookmark $bookmark) + { + $this->bookmarks[$bookmark->getId()] = $bookmark; + $this->changeFlag = true; + } + + /** + * Get Bookmark by given id + * + * @param string $id + * @return Bookmark|mixed + */ + public function getBookmark($id) + { + return $this->bookmarks[$id]; + } + + /** + * Check if a given bookmark is stored already + * + * @param Bookmark $bookmark + * @return bool + */ + public function bookmarkExists(Bookmark $bookmark) + { + return isset($this->bookmarks[$bookmark->getId()]); + } + + /** + * Remove bookmark by given id + * + * @param string $id + */ + public function removeBookmark($id) + { + unset($this->bookmarks[$id]); + $this->changeFlag = true; + } + + /** + * persist bookmarks if needed + */ + public function persist() + { + if ($this->changeFlag && is_array($this->getUser()->user) && $this->getUser()->user[$this->getUser()->userid_column]) { + $bookmarks = []; + foreach ($this->bookmarks as $bookmark) { + $bookmarks[] = $bookmark->toArray(); + } + /* + * Why xml? + * + * Why not! You can even process it in the db if you like + * (And dooon't tell me json would be a good idea, or serialized php ... haaahaaaaaa) + */ + $bookMarksXml = GeneralUtility::array2xml($bookmarks); + + /** @var QueryBuilder $queryBuilder */ + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable($this->getUser()->user_table); + $queryBuilder + ->update($this->getUser()->user_table) + ->where( + $queryBuilder->expr()->eq( + $this->getUser()->userid_column, + $queryBuilder->createNamedParameter((int)$this->getUser()->user[$this->getUser()->userid_column], \PDO::PARAM_INT) + ) + ) + ->set(self::BOOKMARKS_COLUMN, $bookMarksXml) + ->execute(); + + $this->changeFlag = false; + } + } + + /** + * Get global frontend user + * @return \TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication + */ + protected function getUser() + { + return $GLOBALS['TSFE']->fe_user; + } + + /** + * @return array + */ + private function getAccessibleBookmarks() + { + $bookmarks = $this->getBookmarks(); + if (!$bookmarks) { + return []; + } + + // Create an array association the page uid with the bookmark id (uid => id) + $pageMap = array_flip(array_map(static function ($bookmark) { + return (int) $bookmark->getPid(); + }, $bookmarks)); + + // Get accessible pages + /** @var QueryBuilder $queryBuilder */ + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); + $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class)); + $pages = $queryBuilder + ->select('uid') + ->from('pages') + ->where($queryBuilder->expr()->in('uid', array_keys($pageMap))) + ->execute() + ->fetchAll(); + + // Collect accessible bookmarks + $accessibleBookmarks = []; + foreach ($pages as $page) { + if (isset($pageMap[$page['uid']])) { + $accessibleBookmarks[$pageMap[$page['uid']]] = $bookmarks[$pageMap[$page['uid']]]; + } + } + + // Order the bookmarks (same sequence as at the beginning) + return array_intersect_key($bookmarks, $accessibleBookmarks); + } + + /** + * Merge bookmarks into the current ones. + * + * @param $bookmarks + * @return array|Bookmark[] + */ + public function merge($bookmarks) + { + $bookmarksChanged = false; + foreach ($bookmarks as $id => $bookmark) { + if (!isset($this->bookmarks[$id])) { + $bookmarksChanged = true; + $this->bookmarks[$id] = new Bookmark($bookmark); + } + } + if ($bookmarksChanged) { + $this->persist(); + } + return $this->getBookmarks(); + } + + /** + * Get bookmarks for local storage in browser + */ + public function getBookmarksForLocalStorage(): array + { + $result = []; + foreach ($this->getAccessibleBookmarks() as $bookmark) { + $result[$bookmark->getId()] = $bookmark->toArray(); + } + return $result; + } +} diff --git a/Configuration/FlexForms/Bookmarks.xml b/Configuration/FlexForms/Bookmarks.xml new file mode 100644 index 0000000..a578307 --- /dev/null +++ b/Configuration/FlexForms/Bookmarks.xml @@ -0,0 +1,54 @@ + + + + 1 + + + + + + General + + array + + + + + + select + selectSingle + default + + + LLL:EXT:ws_bookmark_pages/Resources/Private/Language/locallang.xlf:default + default + + + LLL:EXT:ws_bookmark_pages/Resources/Private/Language/locallang.xlf:alternative + alternative + + + + + + + + + LLL:EXT:ws_bookmark_pages/Resources/Private/Language/locallang.xlf:ff.isComplementary.description + + check + 0 + + + LLL:EXT:ws_bookmark_pages/Resources/Private/Language/locallang.xlf:yes + + + + + + + + + + + diff --git a/Configuration/TCA/Overrides/sys_template.php b/Configuration/TCA/Overrides/sys_template.php new file mode 100644 index 0000000..1511f1f --- /dev/null +++ b/Configuration/TCA/Overrides/sys_template.php @@ -0,0 +1,15 @@ + + document.addEventListener('DOMContentLoaded', function(event) { + let button = document.querySelector('.frame-type-login [type="submit"]'); + if (button) { + button.addEventListener('click', function () { + localStorage.setItem('txBookmarkPagesReload', '1'); + }); + } + }); + +) diff --git a/Documentation/AdministratorManual/Index.rst b/Documentation/AdministratorManual/Index.rst new file mode 100644 index 0000000..b245b57 --- /dev/null +++ b/Documentation/AdministratorManual/Index.rst @@ -0,0 +1,28 @@ +.. include:: ../Includes.txt + + +.. _admin-manual: + +Administrator Manual +==================== + +Installation +------------ + +There are two ways to properly install the extension. + +1. Composer installation +^^^^^^^^^^^^^^^^^^^^^^^^ + +In case you use Composer to manage dependencies of your TYPO3 project, +you can just issue the following Composer command in your project root directory. + +.. code-block:: bash + + composer require wapplersystems/bookmark-pages + +2. Installation with Extension Manager +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Download and install the extension with the extension manager module. + diff --git a/Documentation/Changelog/2.0.0.rst b/Documentation/Changelog/2.0.0.rst new file mode 100644 index 0000000..7a20e1d --- /dev/null +++ b/Documentation/Changelog/2.0.0.rst @@ -0,0 +1,39 @@ +.. include:: ../Includes.txt + +.. highlight:: none + +==================================== +Changelog for release 2.0.0 +==================================== + +Features +======== +* [FEATURE] Enable multiple plugin use per page (22.04.2021, f335afc by roman) +* [FEATURE] Load bookmarks from browsers local storage (16.04.2021, 1c7511d by roman) +* [FEATURE] Cache main action, load list asynchronous (15.04.2021, d4e04e5 by roman) + +Bugfixes +======== +* [BUGFIX] Correct issue with empty list (22.04.2021, 1f87412 by Roman) +* [BUGFIX] Fix issue updating links (20.04.2021, 0a6e47e by roman) +* [BUGFIX] Fix issue with empty accessible bookmarks (20.04.2021, 1b169ab by roman) + +Breaking changes +================ +* [!!!][TASK] Enable local storage per default (27.04.2021, 2e460ca by roman) +* [!!!][TASK] Remove access restriction (18.04.2021, 28c0248 by roman) +* [!!!][TASK] Refactor js (16.04.2021, 9a09b96 by roman) + +Reference +========= + +.. highlight:: shell + +Generated by: + +git log 1.2.2..2e460cac --pretty="* %s (%cd, %h by %an)" --date=format:%d.%m.%Y --abbrev-commit --grep + +**Note:** The above list contains just commits marked with [FEATURE], [BUGFIX] and [!!!]. Complementary commits are +available at `Github `__. + + diff --git a/Documentation/Configuration/Index.rst b/Documentation/Configuration/Index.rst new file mode 100644 index 0000000..6a175f3 --- /dev/null +++ b/Documentation/Configuration/Index.rst @@ -0,0 +1,97 @@ +.. include:: ../Includes.txt + + +============================== +Configuration +============================== + +The plugin output doesn't look very cute after install. It is necessary to adopt it to your needs. Nevertheless it shouldn't be too hard. + +.. tip:: + + Have a look at the comments in the example template. + + +Make it work +============ + +Here are the needed steps described to make the plugin work. + +Additional work might be needed to adapt is to your needs. Usually this is limited to template work. + + +1. Include TypoScript +--------------------- + +Include the TypoScript in your template record or your site package. + + + +2. Include Plugin +----------------- + +Include the `Bookmark Pages` plugin as content element. This is just for testing and not usually what you want. + +Login and you should see some output. On the introduction package it looks like this: + + +.. figure:: ../Images/screenshot-fe.png + + The login box is not part of this extension. + +The plugin might be inserted more than once on a page. In that case mark `Is complementary` under `Plugin Options` +for all plugins except the first one. + + +Customization +============= + + +1. TypoScript and Templates +--------------------------- + +You may just copy the needed parts to your site package. + +The fluid template paths can be configured as usual using TypoScript. Have a look into the TypoScript template +(look for plugin.tx_wsbookmarkpages.view) + +The bookmarks might be stored in the browsers local storage allowing users to bookmark pages without being registered +on the site. Storing the bookmarks locally as well safes unnecessary server requests. The feature can be enabled +through the constants editor. + + +2. JavaScript and JQuery +------------------------ + +The provided example uses JQuery for ajax requests. JQuery is included by TypoScript. You might want to remove that with: + +.. code-block:: typoscript + + page.includeJSFooterlibs.bookmark_pages_jquery > + +If you don't use JQuery you have to adapt the JavaScript. +Have a look into Resources/Public/Scripts/JavaScript/bookmark_pages.js + + +3. Include in your page template +-------------------------------- + +The rendering of the bookmarks list might be something you want to include into your page template. This could be done +in a fluid template like this: + + +.. code-block:: html + + + + +Of course you want a bookmark button on every page. This can be done in fluid like this: + +.. code-block:: html + + + +You can place the snippet in any template not just the plugin templates. + diff --git a/Documentation/Images/screenshot-fe.png b/Documentation/Images/screenshot-fe.png new file mode 100644 index 0000000000000000000000000000000000000000..2311908c739f0c33cf42697f8a01b4ad2ae8b022 GIT binary patch literal 16333 zcmeHu2UL@3w{FybtRr?&>L@58A|g$?is;Z)nsi5`MnWhF9UKKw5u#M3ivrTZP!mE^ zP!TB+5F#X$&`AhL2?(Lw{rR8&wsY=X>)v(lz5iY7%vv+bBz)gn_WSH-Kl^zfT{XP2 zbGyKH6biLd=l4t3QK*d=_}6&rX81ov`iVDDsNYaJmoAw2CQtS*nVb0BTVJQzeOGUF zQt8mks&r~s@96EAd+~xgmU|*IJ^jLcz4oW?A8t!I6nT6?(e>8B7)4=wdAHaT0E8i=x$Co43I5R94MRjd_>BSo{q+KQ0}6HM9(EIa?{7>P_06qyJ5i{& zyF&%x7mobLFZ>@}rOn?;U1AbbZZ6$=3ktQkgL$0F#8)1ca~yv~(4d4%c`l~^je6fx z7=>cUgeLXuBCYZk+1*ZQ)&Isv>H!PmnF+SDzWtec3Gx98cXkS^m?$P05H#1w8oWt4 zQ&ZD2|Ej1~7ir(Ud-uvaHXrIGd(^v4R(Oq>*xC|nS4Lz6&fL6v@BV#mU%+&QMxME; zX(oK=`X&8Bard1>HNRqAJv}R-ehi7tF0Ac(trN3<@7|+HEm=-!L1sESIt9eJm8H4Q zEiIitW`;(_#ni8E)e4mA*HYpXmfze^iX<04y?%7ih7 z*~T%)4cb4)Nm(&`7A7s!m&ac{j*2q#^Q#cg`RlK%(^Yd(b@lbQk7n5AjvhT)>N9QI+1Z)%=FRVgHOskK`Y$q~qxZsgxHiR0=jP;Gb8*S( z3+A~+3Ml9rW_NHXVq#+4R@Lbf($WU_N}I^Y$ex;@%H_rBLI%T{z}DB-|2p3G3ODd} zgQ=MrtEUVtToaX_f9jVlTiBJ;HO5+6_l5YvqeL}{9z!2{X;guYC~WN>%&ZUw2BB+c z*frc3qkv(YgP$sT`@emA^Zh%vz_z+)NY<(O`AbRTEN{Q4uV;OUHkDa>#4eA+dWke> ztj>2xZ~V^c%wu(A6OM|BS*^rd+SsIQ+_Wj~*)wK&zwssA_ItnVkVki&B8&wOK9VrG zdUe1fl|++uY88^UEd4Yji$aaJZdL02l~fbNI|-}(=bwMV))k-9i+6Boel9CwXQK-< z+h?T4TsYauA==fRPHGuFD4X&^tYQnHp7}+puxfDTz}u{%D^Wr&+?vqPzY!LzXFp5z zkUd^C@Dh1zylL*r95p~~tuAbbRmvC#SGB&zgTpfGDvd&UON1t=)ed&Sd^0kRy7iRm zKVoQs#(YM;+?Myp&H8y&@~FsiDfj%Q|OS{C1aw0Wn% zYh|y|H$_E7Ei)*T+NZ5be*BpJU|zw;C}B2bl^HI2QbmQ*qFh+LkVyAXmAgVYthQmp z29>}cf4xo;WrfVQT~QC-HeeV=e|LY2g6Ht>rsn3PL-aW8+=5DCUVgsG?c29238E;} zuCsLpePMKnJB6TSZyKjMVDaRsQ~8}T0w!i=pB+mkCc8^)62*FdO+6rGM&?8i2(-{` z`z(cq;L(f&&};OpnP}l!MJKNkPru?6IS>CT2@$8 zbnTBn{;(`@jBHR2S!LUCd(edy9Cou%qt|X_AwKO(mz)=;dO1;U?a#1Qu*w=M-|G(! z2?z=*xc60^4*FP7&HhBCE}jdam%I5h!}u-LHDLTIbp{3DRu&d76y17W!@CIXZ6l+j zuHOgVVKlP>rWncwX&N^0UP|E7tniBt%iWpT+34{vF98-@eDc2*VO3%4H*VZm8nPa& zps4s6@a;RZUO+RrD*lv(XG2}V5)Q%({o^|`Q6q@r>E$)zt>QqU<5qrngoTEFr|mZ8kiMj&5f$ii^(%!A=OAz5S3)HJ7%nBDZMzb+#$H{nGa2upA>e zci1pknF+7+U+8u^aOlt*h_~^=kkveZS^#X^m3nyl*RC$t&Ro;S4e+Px4Z2ITp1`jc!m{*G{FYa`qT=lBQtyewSTb*|O^_9W)`6gV>@{dbJk8|Cf z=WDE2=MwiXxohOiODPcTZ`}))5s>Pfo_jKPEtF|1S2tH*1p9i(}5wjnpQCx zM(ui4mU?rzTHH$VS|Is+Z7=rXdew35LXp!~-CotRaj{*BC(h2~uQ}M{`o9;iDF2eu zO>CI%XuiJMU@kE~S0=fg9`mZ|^KyBb8f??yIyXCFaOp{llIt=j#{5&t_uF>L&@x%~ zV|Nf`cbwJZMwI0|rq*UmvT=#2-nN=i24)y-oA8ee5xpx-x%1x>_Y2pWjNsRe?2Sv$ zjztTkv_GEIW9^A~y=G-J!PD!cFtI0Zg%CGSpW`ITja@3z(2;3-@mS?poH;v0(ZRW= z$P3f3LgKpk%jEpti4SMFR}XjfSte>;uDy|z8@XgY92;wOEqlG^Iq`6#wr%0iBX`gF z<{E2LdHLMZ@(Y{FQ`!S`v{k&=GZx;n8Ov_UIm5HAab1ezuS#!m4L`;9PTZ*xf@w-4 z)BGrk4au92ncus5^0Yy;iJQ;36Z(4Tsk~;l=BI@v^VWkFg$?p4?9TdMC^zWo{ddm2 z{3bUd-F0^Ad@37XU7sT&t7Dnw(_QAS$23(hRk0`-X@-4WI(C~PEO(PFNuc$1Stb(P z)|B5K%WkqJJX2!-SnApDCRxRzsB6Bu6!PQvm1q-tpK+#wB&JYYx4n>-Ey*e70a#Gv z>RPm7Cr?v|@Qz$(=dX(ixt(Iib-HP6#>ZymYx(^_xQo$M0v+DOZCmCPhS{goVav@~ z91d|L%}fGqzjlDec^|!<`%ve4+3F}g)=-B|wLxNOR`1i-+5)2vG14!lXuBF`cWF)m zni?i5BIe#TvqJKF{4Fg{pi;}6EqI`GwbU`o78|eN^qTaR~;g7~5# zqv#iu^De8orc7hce_`Dgmh@VGVs)jC6Uf~O>*5&@Q6$Ff>ay$!cw(z=J>dVO%A#Rk z|L}>mayHpUTkBP@P*OhC&}QCO6NMr_!qOA+>*vs)!;LR9E+2=H7_H| zb`s55iOo$X+Ug-vkCUwVT`}X%7I7Lar`(DT{)`)lYsc&Z=)re^tMZE4wd*Hs^pb)a z-{vHVeso{XAzV;3l&Ywr)Y~UETz9yyRojidI42kU<55&pjVT2lh``6LLY=nv_m`GO z>$rwtR&!ie>VLuI6>A1hMJ5DII|^0bXhrq+q<2!6xfz__VeCC71C5$-yi z;x--IKR2NU@Vy$0FEzxq-#v5;?gv+Ev~c9^DB#17i%Q3?kH+}idIXWQBUG@bvr}^i zo@H5pz)pX>33*|}y3lyNAbWmcacuhiUZ5`rw^@A%OiuIV>;*XHJ#%^odICP6#lkLhhhMVdp#+k1`J4 zj(2UGi16V86s5DbDvA2Y)D_6#lWRxubjb;}BM&l9iZ;1-6I5P_D!5$8)CzaLodM?A z%C6vqB^h5gam-nh)3Di9U}*Mzp+m*HsNGH9#qB;EFqU0cX6_aJX!MTW7t%Tc)iUP$ zc8ykkB=jZZ`5iu%=#_B;Ib6XNr%m03R3*j=c<4z!Zoj{7h(X0b;@s$rm%sOwc+9zE zNP<3FnD|{!pcILBsBlBmh+J1p0JHqJ5)%ZAAr3y**k(`YjB$s)-5xRfTJceBrlp1Y z*MASi=qK4&1U%v$;~J;DSrDy|hFt7C!qCle{*`yE3|(6v-D>&$^z|m2w4i(o0@hl zLo+aDh{BO%d=&oVrRrE1st@PPgKlo?L=*FHStb-3jxX=b*onGZ841tdyKkTR11sPv zKOa6VW10u+R$NG{;JEuolr+xuk4gfRxK8rf6u6tf=n`Kn*VB_z>o^Pa=>1izrF}+{ zt{x)v_!w5)(;RJyB~KW*k02ADaDbm*>dcwHw8<^cW$W{n+`{NHdP@Cu%`>|(wY}f9 z$eFgpw&@y`m+RfJFKxHI@Yl@z#3^y^dq96OEL!IleEE^T#O>UwR$_GG=GA_K!FU?5t@Gc%zjW-_F`RRs zRRdKLOP3rm?W%M$#fOVeAlcE=CU@_4B1>O8#ZjDp0+`Kb%0hh}mN4DgR%$TY~(xKAmro7&$J!v7@ znvV>+G#QHP<{mCYdqi1!{wFrzZQNJ+aT>eBMV&ADNn+F8CK$-ehHB=B$)j($h!r2r~F(NA` z*OUOtf00F4slmDDg%&yd^Lb=>gd8H=4;N`jJCyzU4RAZBK#GWEO8x*R8#jI{lkQP< zxI-;oL(?WJr2lEw4h1FaMJi+wo}~8AHL^~~B^d3`d$auD@^n+DW}(hA0BGIX>q9D+ zyUJ7|Ri9>uMCx?{({*lMv$m;h8^^bXN$7D~7st3I3X;p>)$jAl2fqi2d)T!;K8<#jrzO|sxq&1)c1 ztXghtO|40RAYb;;g)!IfQ5{5OFT?$8h;c~V#@GGI5YCY$tU88o4zWthT8w@Sr0~sJ zmmY=#+1Y*`xNpz%5N$)9iUYXi!pMolXV$}9l~mwy)X64O;0$oWf z-BkRU@$uIs6+P;)RIa~%M+Fd+bV&ZoEf4lRRyXduB|XG9T!EpfCL_$6>z81|LwLGY zUQDNDE3kuSb{DhgvYX0NXMKBr@`};4P1c^2=AV2btK~NYe!6lRwn0^lp?0ZpDua!@16WH?NRFlYt?1aG>Q=YrD&e`fDc!xsU?R43m7xH!BvBix zze-=0SNF8KTDC9}v4F6@1?wM7*;z+nYDiM{!_|u1B2|RCU7#DG9a+DS8m9Tt#=Yff z?~kLM+PG%4UqDgOBKtjz4!xI0JfR$%ql=7e=*Z{bN~UA1pIp|2^*V4cAcN@PDWPi5 zH|f1)r3W#lq&jIRg_bcIIn^Hy#{sjM7m6rU&qd_@jaPPb^1A4OaXa}|#T6~=ZrgbZ$^r9* z`0`grMo}$`aOpU&YD37?UC+)gQpq@RMyq*0vMwajA=SMI^Q&$chbn{E@F@wWHe(Pq z=w2B`hdjz^C~91u{Hje@+=RNDw3QT8vCLhTFHjAzfI^^j*!u;-n3CLiYF!c&$CgJZ zCdc+$cS1D<*|Z&kAr4nS)Urm3AlmnoL>xVszA<*8kvofy1sO4*E2%>+W?I=qP6fch@*AAOMjX#V``Dg^zJiFKwCZTwfcv;zVH zxf(&sC(fO_G4wIg_~ONTlX%{$?NJcLv$m4x$(kXyudWcx*4I`QKtkuxNk)ecABLXJ z^LQCM;!CY?*ZJ|blhV@F{d(NPOeRwWJ8-vXtiqhV1vOy!8v~>4GnKq~mq>nsU9iQ- z$cRY8*RNm43rKxH^BC&3DbpY>S4%LxNUdIlUAv&nI8W9orgIQo59tIb$jg^1cTITr zy)u1q7C5&`+O@%;PQ%QNpuxa~)wj_@@@HKN*^y4l$}5!=#Ma7Kozm zs{V5>F{t`y1^ZxoVv$LKpDXQjTm!CXMOG`tfWtv@x&5IW=L}+E8K&$g6OQJC~vt&%K z3eq5frV%s_5VgKFS$RlNLRHl&-l2@b=`P9cZO8`^mrX0AJ(snI&e!ntbfHtK-{qzR z8FOuI?Qm+xmoMqCv}o4X9HLBIXlSVO!eloE&#}y=^~a5MC87U@N*AL9kwM(daiXR| zXT?05(AJ3!m~E86G3$QWDX6ILHP&kC=~>Jtsxj$L?@E%5j*X3d{r2toYTi_(u7N=( z^tWPTVqR%S9Bl0Gx3ddbEo+zy4-3mFEj59TP@(eta0VPZ_NTw#& zuMZ{;d8?S(*%kKmSX#tqLMtt=rlzkKHs{`?hUuFz3=g>Nh($I3SItExSZaE?o+R))YwVv>dL_NCkIP0E%iv{)B!Q ztUoMCipFL0dN+`p{bxS#<9>YqgWy$t*!aecH_!!>I&$RQ&}zWqR9uUr*N(rk>=&o| z?ZmD`eeJ6buxidLETn-ltl%@nIJnr&!CS<4GD!k*XV2cYFv~04Xj|c#sWsNv+G=*| zRz?FaH#b+_d;IE$_wT)3S5ty8s{WBEl+i97TG4~Lkk@O z=B*&;9W_g%g0O3eiHU}yukx5(w&%)}9HCnvpyrFMWJ4puDdO(T=n8Lf+PuFe$ZB$0 z_bL+eYin!JPSL4!7sSu2L8s5c!lI|juO#EuD+N6JIt!0Ni9STU0FyTeg~7co*ccW3 zzpR@TgdmLk!i5Xj1drN!x0XZ&=#eDcyLYdLfiw!AK7GoUrYS3XEogb(rKmQqFjQFC z^O8)}_j~?}Qw2IP$0cQD+lKfBtCXR$b`|yrX-fXNX$vci-}VdAIDtsgzH+6>u>=QG z^~0!o{^P}_rg8pUR;I8j-h27bqAKH-y*;8**i`uz^>SuMnuImhd$*I0|MJT(&?fBy zgTj6hk-kCBhlN(JnL%x6da_@xa4zA*6t~Fw$T3daR-I@n_J$sbj2V4qj&u+MJysui z#_OcoqJscs(Bt;Sr9w*;qI%HD5JR&h`<1K_)kp<1-pEMrV>?>RZK1O}5B)|sr!~DZ zE$f!=ps%KY9e8V|S!rTvnG2xZSB8ywZEhE2x=+IJj+ed@q1TeE6W!w2fpp`TbLAtw zKK-50s;*L@!nbc*#M5_8Dw2YDcml98k0kGr3b%-C1eDZ6>C&*G+xhsqpuGhh@hK1e zprGo})|9;T^xsIDuS`r##G8ue;dy1xk@E|ULiI(4myZHtc6Jt^u++IN!>qxVLL@$* z#~5kk0P9j5QzH3uT|oixt12umzK%2x;S89Mx%f|71$5q-8*SOXb*r-iqsJ=5DUIH0 zErvpIjxaFBfI!z?U6%aqxATBvU9h~k!S~yQRlI+PZg$6Cf7HW<6XBEc=R9cDyS%-- z;W2?=j=>PyJ36>aV`=D5Pme4^7AS*`SET?ZdL9lr8oq^K;lfmJ-VXlL<8a!MMs@QG zxz0j6O+twBODSeKB!KQaif;6B13)faUES;c{{DFlo+>HJfCQbDKJG{>4SK_Ekf*!> zt%G0}oY`t}POV=dB1_%qiZ*2Ig)wRxtu;lJ+n~92ZMud>T$t|f zcSUwON`sVAeHqxk7FnSK8pcS@Xn(Eqvr)FIi~t>2K_!egkDg9KJ3D^?hd~yzKD-IQ z3Dz)C*8TxJRAR#MHwFi`KhL}ndZCs^>fBiNt|Z6=k|$5<0!XK*;H{XW*mI@OqNLd$ zICwCtc71i4^V6n~2u<45z*!xbE|)_$H_}*o4K!eC#*JFuisZ0w-)_)~kW86loK3Nv z`P|s(qQI!KK(;B$_V3&DPioa^E2x)-`6C${+gpL*=jU(2ko0CxGt3GsGjZ%LobS$*l<-zn&*6uc zqlEmZqxg|Pkhq9e;dBn9O9*|nzA*zbZVO-dP)*8^VQK(xfs?|Q)7s4WyaW=zFcYsA zz*O`a)pz^$*9ENyzg`1nP-E?fy>f~nNtV>m`%LT#l4fPu!SaUTy|F%frKi z61++Qs*?|=uFQWjs#O)oiuGTZ%xQ1GPOEWfil6wvZx`!5$DsiO;xHGfoV6Bog!*C(t?+BAjRW=`5r_+iin75aWoDLtlGX~N14aaWnnenznWmiA$RWN zUyc;WY;M+%OGrRBMjwMOcm-I|fky$UO8|G0rDY=B3)>oCrUH2Sf@fUk~BY`+Jf*`s_RUYQu``8_FO0px)jmU@J4Lu}Nng9@A?elRLwX zAyb}&^a-2@v6d`WrKIrh+4FLwDWMXh3C0)1V8RahA*grN zJ#8$3#MT4*_I0oXR0H^ocx)EyDbyiz`8)<)z{TAQu82?%a^+x z0;m_PypjT!MiL;X+`S9~2nJWyq@h=^tBw;N75q{9?Ahy(0SlrYJ$iHvR#O4TG7;7Y zBzMl?@wQMrNNq+iMpIMMrZ(6HDg_uiEIno@sZZt3pqohglFp z*ogtf;O=4vAg3O^B65!U3H6-!kM@a(%@`)vcdTX20S3S9RPdX#_M6MKKvVzBu5hne z%1ZTTNg`wg_7+JTSFc|EdkkA{L8S*AWnd}Oz@-&^= zNl2>Z0*5XuDuQP}S$43Sbb2PiFKn3bOgxtu~ z{K!Dg&N>u+Il-&TE^rJezB3u^?d=Q=`>_@;E>HrbmQjIah%i4+58z9r7Ubq$pYE$h z_RFRw&;kI_7>q|&WIq)@tjA-9mS;7;AIz>2x9+ZVaed6 zp-Jfi;}(!}d;+Z=N?=sif@+H4hlU39`-gjwGINNaN&NQho8p*42odZx+=SW{ZK#K6 zUtVhAI6VrqAddQ$)KN@#u{NYz7OgPN5o~MVxvL0E%2wv6D{>0%r)}DBcj^&V?uMIN z!RB8N4J?$1qfi6yw8@9x5Q&BWi-=h!HDqmxR}IZLEMYfhGZcFXE32#A&r*eN^7AKy zWx(^u5c{V$wMMUf57+!udhA7d22u*>>sy-ueUF|CQod?F5Kd5?ARbAV)n%>- z?WRS17tw~z9RgZ1C0u79yo&@N4KM<~5f&Ea(o^aJIGEsIv3>jY_Qn_qt7d3T$HR}{ ziGzX;;kl3mAPXcz<-1t;f~RRl0DE-sL7+8^!_1NIDsL++b5Ct!Oe6%;xvw}3mA zVH}#IauB1Spa3ZJ%)#PuWF)TA9S%4ECX(z83?k7XfBf+h$!QQn5b2-GG-YIrploV` zWLOy(74^U>2I{35Gs&M_W^iPow`V8V`Zo@(LS5=UgC7Ede+;-coRIQE>$6QVO~t3; z;^XlXLP+rJ(tz3SAer0xX@kvitTjbsx*s44HUps0hS!yEA=Xq}>OIkZa41{<#ZiDw zP;4^2kiMAO57?Jej8xyjZ5Ht(GhTPvTc}yE2Ea%t3t$t2_f-EFjKJ8VU0vm!zx)P% zuTK-+|LuDlL_@40uYb)k7MXV6{ss~rnUe+@1iU|4*~@!v#cyPIm~3l7FLg0apd#Mx z?*n2CWRlDJJ-~I;`)bj&)9_NzbjZ*kjcW!L7L2z?Sbao9rP$b~&!3|mEc`(rQExrM z08h9mGb<|0s$;CvHh9T_uxONg?ix%S>QiD67*3H2hVEWlu)OS_qJqPPDCGa7%&?yT}z;Ym(!9loi2m_2P*1-b2yo4%C z*d=nvR;AV57({4*a!d^axraCdX%&z+on6p~=r2DBJcs(^$*-g#b8y^26@ygVA0q{` zefs>aKpLTe`8IEt=E_xBS=palEjcPxD>@@1!`i(*N<`h#{^Jvfz(fUfe4p>oP69JN zEv*owF$yu{bFAbt1kUupD{$RU?XNS?47IYNb2RZ%I{Y%WlV8%REGgbONaYT8$pE#Q zv^2X2>I>Yw5NHO#Bx&HsM#RN&2*Iv+X)AFZD8FEm(cZ(K_JoE1qcTBoL3QrTM(Be- z!B%d8^l==*7VK!$%5P3B_$62t2Jp}#-hJ?3*WlO%OhQ^3#5CCY$N}V& z;P)4(!`PDb7_6N zi)m(U-TV>xXH-u)PW5U?62h$kbc~cwSjq znQxY(o={fqR-ieSAQS;H$InqU3vH|YCm`4U-J1*@ zv}1wR45bM;ZK2SU(E&rU2?$vb!Qe?gu44%gfxQAVy!h&FbFlUj2Pi@7fVSc~R|q0F zZ8LEDi%TjgnP(fN#ft-f?^SR@@&cjArQ=So?{R@b3vutJX#`m>7Ft)d56SWgs|*VmTUbcy zEPt=x1NGbI%5wJ!a4x`w2>?}6e=xm_3LOr}1%TbE_(?3MFfY#-sp6u9CLSl( zhwnlI9fi*jofGsydO2K#(2*X>)tOs`+)uOzNX`SQ4p9;VIb@k1L7`B?EaQrU1X0T>!MN+ z?R44^Gx{7N+#mrA1$4q`o9M4g*vi#~r$@A`?F>PEE+aYCB*R6iX;{ePy({T!&?0#W zyet<2Q2+`&#MTeu68R%2dM7|B0bXkg^&jX%qYFC?Dtn2*_%A{@!~*)lGUvb*0yc2% zXcffg&(EKIMKlOG0EmJFa-`3dx`Jv2YO=DiVp73}u~}HV)qnR3TqHwZu)E7cC*ZWO z{owmYa2aP-*b)@jalWl2L-IamxohW1NWoKl1zW+XvnA9@;)$?oc@u)rU#`L0f#?Z-!)<1s4b2 zq^F;Uh7+>uYYU{>iYYq~LZNg40m&Qg{2+o2te-X+kssmW2#Cc{S^`@YQ1>rAd-g1H z0zrvm5kgj8(UV7+qtJKavS}bxNx==Czso9%!ybUuHHGwWD=)j?rA%nd9Mi$!a7g_E zH%b^E<=3CkVwSAlK#j2}Gb8cz6GRkkBa|6~9>|WxzEj#;EUh5?xF`8ou$$rV?L)5mm+O1NN1d zcMS;{H9UOs#KhZsUYM*4tMVIIlNLsk+*vu=t9x z`xpbxoEqHMVjMhe?Yo7z7gEnw3XTlS_jBi;iYjPzz>dF!KY_H(x&W@}zT_<)0?Tu~93T>k~~(s`k! z*~_pz15nb5J`}U-Ll!x33v+an$8ZzkB)dEP8zbmKa+7sN|T!V z*ttK?&5lIEUCy^YyD=KOuhdi_ZrUGwbuC*4KP4#rZ@NV|n#T%AX7KC15DNsNI-dzT|4}-_qdofepYLGqFj}tY6P{DtXs4O~98WLVp2w z3fvWwuY$=M+?d{09WNu|Y(>R5o{<@6^?DaFc*GV5gR?5?y@t91o{(N2zcz|xM8Qjh}7h-@IS`mN$*Cqwarxv-XA=LTh zL!2-=;*tr^;WxQzQ7GDQoDJ6ceXuHrfHc=L7r^8vDPZ!QUrc=G|Z0`cqP|D21;68gH>z-6k z@Kj>3KvG7CMM5szS!gkknfM}$Y#{|E0KYY0)z+D3;T;rfmLE;eK))?FK1p=B8thPw zIOM=GDnj9!Wsw?wKL*ntH`_{56~Z9bK*}Qd43sU$%hzwZWR|B@j#8=%$rtTwo7nBL zHBvcWbLB>ebY%9fmG#n$EQu7_5=-!BwTh}M04WP!ae3Xx_yK(Z{5W8mZ{H%x@6EaV z3NiAJ^~)WIv&aQA-3lh)7q<|PFvMQi9}-7IEQWUZ1~}DlrG|$Z7P(DaeB`&3f(E?f z#P1ey#4aUD82wGP(=b+74DVD~^W!{3?akxJ`$ijoY+549x{GLzElU!>kt^lQb0OJ3 zSv_q86H!jqY?di3@fklSw?a+DO`6S-gs`-)56r!md@!s$4+a@lwSy6T_W7yyD|O9=KrJW=f^s|U=!p66zamd9ZBrVfsLqrgPT!jj=&-M d|Mo9?yk$@C+Fr8>=72)!Xd7N4Uc7nlKLOdxT#*0( literal 0 HcmV?d00001 diff --git a/Documentation/Images/screenshot-styled-fe.png b/Documentation/Images/screenshot-styled-fe.png new file mode 100644 index 0000000000000000000000000000000000000000..9d87f6c32c69112030ecf12f320d2fcc3e2f82d3 GIT binary patch literal 13366 zcmcJW2UL^k*6$-09O{fB2vWwOC?KFnM?gWr&`Sc+f{iXkIzd2X6a_&E(xsO`AVhiz z0Thv5Lx^+`si8xN^!vm)-<*5CbLO1&ed}J=Qeu+#eV?+Qz4!mOH!ltKwOE;Wm>>`c zEA-}dV+e$S7Xmp{bo>~2NA#^*5BPEHu8!7q@Du#m(!d9UA3wR@H1~u+*gnz!9*PlX zI|JTi^n&W$VElT7ot^7U?V*D25Xi3(==Ezbzrn>3UoV*Le$#4K`+XF&>&@?m8g7T4 z{CfMUQdq0DNtT4<@$&^QW(%jzF@-INRp+MRCB=?kc<*)n>h)_cj$}zZF_cX|^7PH` zDE0aKt;_89h17rj>4@kuS@B(T)l|UpaDeC9gnvo^?!%l{^$`ff@cAoV1_a5~;-a6kKw3 zprx(N#MM=j@vGc@cco-Ww!@HdT5L>=aA?C>sB}`t+Un{=+OVd>1Ozh7QCw0oMWKib z2ng8P+CIEWf3B5{zrX+5%F3J2(9m|f5?zt#we@u!xO3MkOl4#A%CktMO+Wga=-qah z#BlimhC+W+ZyUKT@vufLGNpj;>(xE6KQb`sf`SJG&f)!-q%K0|NtN zqN394>#uT0XwuX|KDd(xUq33n8E7Hb7p%2m{G=g0MAVUqd4n*I>>4Jk%c_SAr_Q*I zpy$tC3&X$@PHK*WQENvEs7Xe*2tk*Az9XIRZE{jjN=l06!5MyoEmUJ*V8BCxcj@-s zi>JT2E5NGOrZlcP*_B5oxAa|3PHLNXjuMJtUyvRlCUDU_vyWlE2 z)f!{s<5RA#p@FL|z~OL@0+~af5zz*1V`FvA-cR=)!OE;2?<)mIsB-TzZdqPo?s#pW zn&0DD*Aw|EdTmvjvL#7#@QRB?WsM$K75Gp;QWy3lPEn^>7PYne`y13K2?P0M3j~fkj5YH)ejOrOR<^ww8z1NDEU*d<0FFm`pKDQkqCq%FXXp4Z5~S zF%$3EM!mgPLh&0^45am5bqT(g%ju$)R<;xAFrxEX+-LWq%lEn5yI)^hm$^oEND7pM zM(XlpldDg$oxw+D&n}D0IgX|ZM7>5=-751rmpxCZ+b~=Be$G~on+o+>U{$1nBU8^h zH8+>{=FQK^SNx5A@OzjxE)I_Kii+lE&z@b|*r=IbV-O=E7EQV z_J4E#9p=;JIiB?uf^^a=C#$j=3vyvb@{J?U$HY3e11&XgqCPS_f(r@(Thwg6{pNxl zJ1PHkMo6B0IGpwb?*v6j@_$4n+oY)lZfA=e6ot>5u}E&Jst%kr28(wrq)#!Un(duJ zphl;%wHV~_na=ytd3PJbwDt8(9UO84teV^udD)T`JvC2>n_d_YkdHRf7?gvja{e+; z?fH#REo4DRBAY#F$jg~j^_KFL>=IE6w=Z%MLEFdp?S&tHT7eGL{cLlPx_n2bb;&&z zQz*3O-!GP+Co(ejUi0NXN?a*c3puJza)uhW`Bl% zgmscL>ToZN3Ogi|M$#ohgCvV%>l#h8^s`%*+{Gl6ai1t$rqjS_@m$RqASLet~3)j}x?w47_ z#KoaEHoUgBw!m;i&z;k{cI_JYq<5gierj>i`hEqoD=XbPmwyu!)HoLMArP%?X!zz; z9Sg=wfw#4}`Hk294Oe}1Nq6+khm>@kqmUJRb4yDUaSrD0{{8}|PP)HHG?eaq5Qr50 zR8c zKVJOBC4YvrA20smlD|hmdN}-xOa3Zw{=+4I6*%cZ_wP^nAH~}LHf#E|e}Bqf%=#ZA z`ajOPvWh`#sN7|0X2$GWUBxJplB0S)c#mRpiiZcoDDcQ|rZag`>=0z!j)k|P8W@|n zyu8^NdDp&CLcj>|HJ@@$m)e092zk6h5Quht=7sQpfB;GZyIReCWTs|>>wJi`%b-l) zY9p6ZZ`MnTGAChZqKqSI)Nhg%YD`-i0kOZ0bQn_K@v{O5_1m=N;5NRuOC$LF0NT>C zoT+c$+{-}HqFv`4*E(F~Ny6Ev$vXGz!Z9!-VBI{jPFY~cIw0tZii#FnV1bJ;SsW~z z@=_Y1hzJQ)HvRsJN48ZZ5KLlztga3N&(BFq`<0Y$X3(WVdxwgSW_H;r#4n*zQ&UX~ zZl#|~ePo=To{q67!w5OfcBj*Cx3xAU3-%DkWe^FUx0I^)uaaRn-g>Nh-8~!EbAG$^A|1@bZ=_DI-UON6$>svt+szl)~T;M^xmz4plMx?(nm`*3h}+{o zBlnQwfV!;E6m(#A33W~Sks5`6{cm!TfMPp)ff zRx`Rv+SFQARaGhZt)y)(3VCV3m5~olguWdT2NXlbBcrGChCfc%1E2q-Ue~7#=oSvxVY3~N}Es7 z<2njf`^cZVFksTVCHaauXh`-1H+SCezaPoY$~v#CjPK?X+27wMZ}5TBI=%1T9)IbX z#f4$Ufrd#G&5c$(g7-B8i{Z8V{UP>NkvdvG870KZf2Gp!VpaX^i&m@a z)NdYD!)j{Z$3vLd+3W5nI5odHyJU+o!!7OmlMAh%m3vQ2Sc5}Lp4#FD zJG+!B#daFl6)q)s-@i}{0twnlrHnZ@a2@ohf=w541nyz!P%Kvk@68#@%QXZ-%)565 zCH7tO?7`ndFtEI@DPy@tf}{d-u$bqbfk0@D_gU;c<5rUnEE9^V_NJK5?@`y+JrAyU z&%>|?f<=S~ifn}f`JvhQfrhuhr=m+d#c+y7b$hKQlKsmniETo1>fXF@YY} zjpSk_ET`G{ys$q$KRMa-+V|j!=d^Ao(DxOh^Z^3z6d4(53e0*qHQx+Z2+Yw_69TDZ z-E)=TEwSqm+fW3KwJlac+?(L(FGr{xS0)8+-P>Xf){03&FBx(vcl zFsy|v9EaG$4EgzV$`8i7>w;rhU@?VNN{Y!pd`>^#!riclRsvsaWm?%pt;Pi=NICafJ& zwZI>|I9B2ETglv-$_@XcJ`v_Mi12a&L!^GnJp6cnCBF+vrMOVY@#9*=wq4g*mq*@j zerpQM**mANgQ|H>z^;r#gljIZVPu2y!Q$|Isg88wb1CPTEtV@D#`S?OS}>dL|3s?>sA?^MH<^FBl@8Smz%oT@}ep zza_g<{;J#TA>*mIx4!{BuoUG@?P?Nu8yEU=i~~|HD@we?3$kg8x*)gS&w_wWEP6#ci*^yv|HU%eY;aYSi=7266-YB=v%cw%w+1TVbGM z+Od(LTgf~yat}>&E|i>d{vj*PhYL$idR3FK>Epv1=4k7X8XbpmDn3n#bz*F1~2 z{U{MqwNgDnL6vs+>AkAB6QkUDFSOI2mCJ7pVZ)STGZY&FTJOtn;HSmI$&OxeoZ83( zbL>G~Q>eA^lLn0`N=|#LlYasZ;|M~{%KDDe8z2%n>b5Hzy zk6e3>4qmaGf8U(Be2JJ(KldUAtxz^T zZC*J^Qxze@a7|T=Wb}SMd+h+mKX@sd*|*x2PyUmNX+~C|Rz$d^AQRcgdR^{{iY|uF zR6q546UGLn0U!-7FQ@1Bn~|w+KW6GW>!~0)d?G-0Ecjs0dmsfUQ=geFwRqCg!+c{IY9w~NM8viBIi+L^crj8b zfx6ab$Uai?MZV{_g$Ngev7J)`u*)(xRhA2-;wWp@e1Xf3x&D@$d5z!a!g6^tvb(OB ze&xx2h#hh;FG1~Z-elaf9pxD@DJkYLWh0#3VTD{Za-GG)3QY5*r90Jb=7adWJs!e7 zd{5S{^IhIdYVfE^Fz|Qu_Y^b$d^+a(<#Z&7=Au9!KY8+Gdw0F7ENE|A{jdfjJAZ+o zR%E4Hs)Q?znT|s_n0Pa_UiX7=9rmbP6DSS3VK#pL>|qCdf5fgU4lNalV&OgdhxBr1 zyzBuY*nbps|1)*wZn;u9deo%1SOEh@|M_DOaVHhmS8ot&?ac$`dV`?}V8y}Yx0r-Wc%7Q1 z;f?@moWC=DL4Q^#eDL|Rg7*VMYft%QqbCjK8Qv9qis=pLUd10Dhs8RVIDhEhL)Pl( z*iH?V-Zgs^)mCj~T+zbpb)YugI^iYiF>Ie$?HUSvt0AIc)HFHnKp2Wy{iu~#d&ex@ zJ6Kr)S6^7yZkdS*-ZI=59EGpT=QwY5em}8%K$>qKsWJ1m*}tV~pa-LJsrLRpb*3s2 z0y+Ow%ls5#t?uxeT)=GYb6JvLaf+SmFji}+FnGZBTreK~u*tY|FCtx- zAsN+a=k)o#t2B3bYWU|12wcP}G*LbyPRx8yR!~I$u$p>EYgm*$cA6_Z{&5^%GL*lAsC1>h8+=t0Od z%kmuCkq<$mYn#$-Z42p4PlA}JG|_&!^+mQo9=?3v9Us%AYB(I6iphYoXdxHnM;Tvs zg36RYKpjZdhqJ7il#O2nfC!o_=;Tg+rl6$)TTYIs$bP))Y4uS zhpXLhfH`oJ2|A4vUXGjWc-cZNOPAol+?}ztp1>GCB@Ifju9wsqZp1a zjEH=wN#GPpv#q-ZTEo5zzQtQ-U@>?pKKvd)|?h`lz0R}yL&B5G(1X*n(Q zI&_Rx8-yAYOE0NHqiaVO3Mq?@rjORl`E>P6oa{Dj(biafkQb?1lvoEuqUTrgyy!WG zQ>ghIIMU;qM1|oJ3QmZj6Zv3tFD!U;Nd8J^JIpc6#EgJs61j&Q`C>wYP*7ZuWi}P3}Y6 z{*H*RGwnd6<`{XJiVxZOR;aSPaLVoT9B@($s;&)FzV)qCoCIxY?+>dyQWm_U=CrwO zA7-GKZ{C2U8=c`1kQiqP0)O{b6fp`i^aj3MeXkG*_K(r%behJ z1^3~@2eM1jmG8`u$I3wdKbLbE%-Y+baXD`RnKBZvOaQeAAy9OAoUfy!L;U!Rc?8Sd zIJLZtQw`j1b}Boe0AxXrezH>bjc`stT+uMf&iC)LfX|pXI5_0do?OeMJ7y10PwG^h zO{~&`o5uS3k+r*PI#bir^o#4W0r_~Fk`mP64LAzGCmQ2m)#9H24n(s}1rqPOY-(p` zm)9>5J#^OwQK^4`ps7s_@vTv%K~A4meWRaru$Nv20CGhmbm8K|@;f=!( zSMAl+RWe~3kegf}Vq*OW(vA~1TiT5&O7Iv3$G7E?XEJ)ME#}u*@qI393{|Hym_Xs; z;kV|fHvNiQ7wGaH4zP|o5f6~W=HgHsW_tOAf>29WTS3^hAk0Zq{g5GzB9=texm zcF=wbOo#PNRMf+PIUwf%A(-#U4E@;9APh}b_BEaS95FR9VT4IE^K3|w8O(Bu7ZRO& zkb^*!>c1YgHZUPK%^Nx;-fdzx!LFJ9l{Q$KCh*if2#G}7VjexlJueUM`?BOxs3e7} zN6c3P+K)hNB*#8DFtRW4gi9zGn(TkM!9EPvLA`Y;aRjc*qGs>$t79Si-C2?QR5atr zh^7g7vAaOPhtizqWji&{SFI24I6bHrgWX4E@kl+&B3DCyS23LJoX*+A{(BM8W_@NlBN;&Q|7GN8oxj(!l3?TbfL1=wMKToS9#D#llY$lx@ZIQ)&bky%jsw*cLa|?R z?D8w}xez?&PK=?jP%4}dR;M~mO?9qtwxX2}O1r-B;nV9+b8I^W&o6%N`#=QB#Qsy? z5J5T5E*Cztwnpu1$8Get>TQ!J+1D%~knh%@qUt#D@fq^#%X2UsLWS>&pP_SqfoREM zjQM!wMVMfdX%a-qvsrrOCDUG+n`LGuNl%1U{1Q!s zkGo@rz8!t$r0`B3go2}f0gmc8HhQ&Yzr^virq_cUtt9z3AJW7-@v+t5`?kIcq#Dia z-|Fr~bboy9k3Bt$ez(g&o6-9Tnz%vb1meXMcxonIHg6}xkTLoZ_ythE`{NEH)n$No zdX3i~0>BRnQcpObF=g%(>dAnfgvq*L>U;98dF##lrZUtJV4}@7K!(otzp+Wwpr1^j zUE{2$8w;_0b(<}NAiXz|exhDB=hz*kVpfA@*0}6%4>EaC2pVIlW zK=7`SO&~P}Acoql=`-GQy*I3@y?Wj$dfvZyI;_B)$JQq<3>Rn4Djte)s(LK`oD~YT z_Iyc8ZU^IQn}=0mron`M0yc+j9%Ddm;;e4m^jI&L34V`|Q~~*jTXESLoEx zBG8WnP{b650&|?2x+CWGiXNda)2o*4SI*R(g*>KYfSfxnIR#L+Y@IkKP;dh1oK9i; zYWzJxNxt#Zr)E%YgMSLH3CC>Vk4B};-Kdhc&I@(Wrz?8S8f*znP>&WD+CkT1CoLqtLZ#ZzNhpyF^3eu22!P4MZoC7 z&I0)g)P|UH-BsrDMKVw;-zsOV{eg6KY>5^KHAgBn2RlLip9Ik9p)> z9}V~eIfxo@H3L6ne3z`~q}y}4y$A4N3Hx7yLjhp&S&x`FpZAiW-16((vn^+D$*vqA zt>^uH1FNizwe8AHKMBuLZO7W?Jn|gM#e<$P^%jq;me!r{WAnBt`eDK!% z{QSa9eDsRv(&MxVcLgD+W~LGN=;+a-tt~A^4i2-nhnpH17#Qd!24J_d0LGeCxQ=}D zQUbwnqBd#D>9h<8yDt0+3qJT)nwdV!qgZ;WCEpZ_0_#4PL4IWg1&t{gnVD1W3iP%1 z_w&mItJr)E9oKLgJlJ*e{RFt1my&JCfXu_M*9RWVq&g7px15OJRWvp-G7`r4r!zNB zxK~;lgb}go%%H4T;Xix+{CRro3?g{X8(?&(F-UzzgLYgbY-($+Q|a6pKZ3~UXJw#& zyNda+GLcAx6Oi62pg^<4`BPk5wj(hoM!{V{XO)TV$Cn;{xyTa%8h46sT>zD2!~6HM z`2EJ87?Vpauwbc$gE$#MX7_mLikF|CU-R9f=Aj`=`_6YgU@tjNoG`{9K_d2ypMl;v z19@hl2%x*IToRbk2B6Mym=uzeH)AB~N@xh^8>r{wP`bbw4st4QvbAp>aIC_G@Ios^ zMc&A}YQd9FDM6MGK-y@}35eGlu!aq;X8I`_+)erpU*6vtBm`6dqYblea4M^IdnN&` zX!U02y<@7@c^>GPbmp$9_X9OCKl+f;;}A$FjU;zvvbR2S?WM@UVK@)|U04 z^MpqgNJK@{)YNFq!}O#$t+^XiDbv8cKtd>{rdHQ|#x{6IsD~LV2r6T>wY6Y9fsxWJ z%&;_k>^Dn8!(Z_uOF?R_&mON@0%HRf2v%JfX>x&{IX?}3r$1wBDJ%ICE0;<@W>!Vx zj|}xC;bJyPbqT543{%VRvpD0yU zy(X7yjc0Rdwt+~Ls<}WLHv>4q?j|bVof{0aWOqVUfKV1V#W{OUz+hdk$Og!L5}*Re zx`REdOW#lYy6SQ%05GZkUkL#pa**RFXfC7(iW@UqMOt9ky0Na05Pktx-6; z+0HjN;J)?V`YI!c5WVA$ZL)4{s!$a9eT6V|Aho7|A=KnvYymmLWhW*j8htEAeM7Tr zb6X>NaB1+blfb`1&aOq9;s7!X27fILsQVal`0Yw(^IcNlw_aVQ7H1E4e& zb{GJS)?cUu9wp)Ajy{lm-`}2E^lQx90qEO88dFiAI*PH7e4Nh;ac`)05 z)Ms<-?eq}e_QFmwvcxPvWwGvafI+=+e{?8%~MrxBuIj(RL3v2Ip#G()o0;%Ww zgO2X_w+*#jkSYlkJF+d>H-`S=y;J(iFdB1*iJi@myY3|LD;5fp$Jfd^wyjj_wFdl; z-jGc7Q?fqFhE9az3>}a#tl0M4H*T7=PpC^t}lKQduB!36LYi%4jLv;4XFd|IIRz) zoz_20KFe8FiY=(Fd)0GXit9Xkw2_*S(5ZmE-XodeAstiF3xxwxv%ZC(=zDM_=Ez39r5K0~%;|HX8$`gVO<{JkBfs5~KxC z)bqevV@E}Q`mO;s3*WP`P!O6U?9=rKw0!f5aPK)0U3NN{J+=c#ILe=7NZ7XSaQF=iaOay~qx!M?D!J>nrsD7q&N*ysG0v}%Okd2P@(_jZnp zM|S26y-Clfb}I|WIr>t8!`;C>8ePH)sbAz!SX=a=Ge;9f@0gLfzVx>x+YXBd9H7Bb zf{$bgz!*nm z#;LF49GW*`!h&ZU{r%$$E$$6Pom4_9WnLfR`jGvxWgrKhQUZ*w3q+Gx!^8nddh*i<(p=?w>>pL+M!4&;2pIrjtkUap-5qdw+vf z7q2~VvPNU(HQ(bpY|Kg_FoF&N;-}ufx(mVf4-m=j{X`coNq}~ z;wkYX$bCNNIQjyCxlbj(idSIWy`OMmlrOJLinUKXyq~Va?N}%x(W|@P`&ZsvC5+&s zot|5GOG?=obnI%oVTZGVRrYTeZGN!xb@<+nlPM3dq=q}?zJ2u|xvgzR8v+^k`H2_B zu|UBV>@om6u`Q!yDxSKGmh;ZnMggaS79DdXl{cL0!#eW$2P90{ZW=zjsz%!exL*>6 z`$OMdFRDm2yB#+&2W$LQ4bIF41E{VLv?04z)JMIG%D!#mCY+*AZ$A9t2bSr6;8bA6 zQqJ!}pszRl?rRZou)IgvLX~85jTROe|D~+^`JOJ=UOk4Ur7Rd2W*ElWT#~%yXxYSS zbUpj)h*-d1iKl@Ri%sFymU+SXnu**cI-pO_SXUjD!!rAk1OO0s)gAVI4^Ue1Zb8ao zcrkL`AL;bLF4`!GonHz$!*WbfO6Vk1`GkSkm@65$NdREQp+Ml<6euHt^210rIB&Q7 zypZa7BDJX6;WAiHCGir@$c0@700g5umJA=QaR-+A;6d*u)(vs`*N|&#e^)$oUz2MD zJx44lo$7u_STPib8o$Tgu;Dj=1EMHwe?JfL>^2DCH4*_-hxKC?+;M628Vc~>H5N@3MqerMqn2sAu zZI5%GTNH^SB$^D!0eeekfK-+KCV34fwS&-))6r`Xcj$l5Zv|Qd-kzlc#>;dk_P+*< zbhk7f={{m(kcx_JG&&0M8BRN@_^MJ(3O~57Vl@opqI4(rkF9a^%l>0g<;NwiFY9~m z?t1Yl_-`jty#7`_32S?mxE?{ruKID|m+4XFgaW-14=fVo27wy2y7_}KRCi*vG!M%= za}c)P)?d1J?;WVR7gi<~6%)0;@4(n82AU+a$D7Lr%5GDFRK~Kii`QJh z`qg&t(u>{=vO^q~>@f7&d7xUVF@VwbGeMcHU3yWp=#%h+gMF;qCa(uB`VTh7}8dgb$XUrn(L<6MM!jsD-VoU(((U$))H4`XaZKM{7- z$gXC7RS*|$`Rf7)b7v)nYi&y87poRFQ|E@&0rqw>QAY&~KE>{EfZ_vyGUM&w*9Z># zzlBdEobk;Mh6yITI>rjHG{1mk-)gBR<}TG&iU`w1@fOn-&ss*eeiV}?6&I` zXJkm8W7tczoF~{bP}Gw1Et5Nkg0{OpgJJ;aZl-npUzmu}f4q!y+v-xcf#Md(X%2K} zrY))`wZJiS1izaAlH1vq&8T_=)(g|OlW`&*Yeq92(Zn69dy!vr=2k$K>k);wcreD7# zuz(u(RZtUwp^uTI%%y6q(yr)g?!9@F^&g%0m1S9%rqz}RHDha(*^Xj_Oi%Q@skE}Tfsg}rq5X?^Ay+%e7+e>z14gR znLsLc^~78p8kgBmSSpRH?D6c0?W#V(>`_58MU>dPDzuq5AEo{|9mG-B$nr literal 0 HcmV?d00001 diff --git a/Documentation/Includes.txt b/Documentation/Includes.txt new file mode 100644 index 0000000..427bdb5 --- /dev/null +++ b/Documentation/Includes.txt @@ -0,0 +1,14 @@ +.. This is 'Includes.txt'. It is included at the very top of each and + every ReST source file in THIS documentation project (= manual). + +.. role:: aspect (emphasis) +.. role:: html(code) +.. role:: js(code) +.. role:: php(code) +.. role:: typoscript(code) + +.. role:: ts(typoscript) + :class: typoscript + +.. default-role:: code +.. highlight:: php diff --git a/Documentation/Index.rst b/Documentation/Index.rst new file mode 100644 index 0000000..ad29c61 --- /dev/null +++ b/Documentation/Index.rst @@ -0,0 +1,57 @@ +.. ================================================== +.. FOR YOUR INFORMATION +.. -------------------------------------------------- +.. -*- coding: utf-8 -*- with BOM. + +.. include:: Includes.txt + + +.. _start: + +============================================================= +Bookmark Pages +============================================================= + +.. only:: html + + :Classification: + bookmark_pages + + :Version: + |release| + + :Language: + en + + :Description: + Provides bookmarks functionality of local pages for logged in frontend users + + :Keywords: + bookmark page + + :Copyright: + 2016-2020 + + :Author: + René Fritz + + :License: + This document is published under the Open Publication License + available from http://www.opencontent.org/openpub/ + + :Rendered: + |today| + + The content of this document is related to TYPO3, + a GNU/GPL CMS/Framework available from `www.typo3.org `_. + + +.. toctree:: + :hidden: + + Sitemap/Index + Introduction/Index + Configuration/Index + AdministratorManual/Index + Support/Index + Links diff --git a/Documentation/Introduction/Index.rst b/Documentation/Introduction/Index.rst new file mode 100644 index 0000000..20902e9 --- /dev/null +++ b/Documentation/Introduction/Index.rst @@ -0,0 +1,33 @@ +.. include:: ../Includes.txt + + +Introduction +============ + +What does it do? +---------------- + +This is a TYPO3 plugin that provides bookmarks functionality of local pages. +For example this is very helpful for intranet websites. + +The plugin can be configured to use the browsers local storage allowing non registered users to use the feature too. +In case a website user likes to have the bookmarks available on different devices a frontend account might be used. +After logging in the bookmarks are saved to the server hence might be accessed from within any device. + +TypoScript and fluid templates are just examples. Adapt it to your needs. + +The extension was created by `René Fritz `__. The ownership has been transferred in +april 2021. + + +Screenshots +----------- + + +.. figure:: ../Images/screenshot-styled-fe.png + + The screenshot shows the functionality, the styling is not included. + + +You have a list with bookmarks which can be deleted and you can place a 'bookmark' button anywhere on the page. + diff --git a/Documentation/Settings.cfg b/Documentation/Settings.cfg new file mode 100644 index 0000000..eab4681 --- /dev/null +++ b/Documentation/Settings.cfg @@ -0,0 +1,17 @@ +# coding: utf-8 + +[general] +project = bookmark_pages +release = 2.0.1 +version = 2.0.1 + +[html_theme_options] +github_branch = master +github_repository = buepro/typo-bookmark_pages +project_contact = rb@buechler.pro +project_home = https://github.com/buepro/typo3-bookmark_pages +project_issues = https://github.com/buepro/typo3-bookmark_pages/issues +project_repository = https://github.com/buepro/typo3-bookmark_pages + +[extensions] +issue = https://github.com/buepro/typo3-bookmark_pages/issues/%s | Issue # diff --git a/Documentation/Sitemap/Index.rst b/Documentation/Sitemap/Index.rst new file mode 100644 index 0000000..85fa8c0 --- /dev/null +++ b/Documentation/Sitemap/Index.rst @@ -0,0 +1,12 @@ + +:template: sitemap.html + + +.. _Sitemap: + +======= +Sitemap +======= + +.. template 'sitemap.html' will insert the toctree as a sitemap here + below normal contents diff --git a/Documentation/Support/Index.rst b/Documentation/Support/Index.rst new file mode 100644 index 0000000..61d34e4 --- /dev/null +++ b/Documentation/Support/Index.rst @@ -0,0 +1,28 @@ +.. include:: ../Includes.txt + + +Support and Feedback +==================== + +The extension is provided as is. Please understand that I can't give an extensive amount of support time for free. + +Any feedback is always appreciated. + +So if you have questions or feedback there are several options: + +Bug Tracker +----------- + +For bug reports and feature requests: https://github.com/buepro/typo3-bookmark_pages/issues + +Commercial support +------------------ + +If you need commercial support, get in touch. + +Nice mails +---------- + +Some nice words fit every time - just drop me some kind words by mail to make me happy :-) + + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d7f1051 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,339 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..da45f01 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Bookmark Pages TYPO3 Extension + +This is a TYPO3 extension that provides bookmarks functionality. In case the use from the browsers local storage is +disabled just logged in frontend users can bookmark pages. + +The extension was created by [René Fritz](https://github.com/colorcube). The ownership has been transferred in +april 2021. + +## Usage + +TypoScript and fluid templates are just examples. Adapt it to your needs. + +Further information: https://docs.typo3.org/p/wapplersystems/bookmark-pages/master/en-us/ + +### Dependencies + +* TYPO3 10.4 + +### Installation + +#### Installation using Composer + +In your Composer based TYPO3 project root, just do `composer require wapplersystems/bookmark-pages`. + +#### Installation as extension from TYPO3 Extension Repository (TER) + +Download and install the extension with the extension manager module. + diff --git a/Resources/Private/.htaccess b/Resources/Private/.htaccess new file mode 100644 index 0000000..96d0729 --- /dev/null +++ b/Resources/Private/.htaccess @@ -0,0 +1,11 @@ +# Apache < 2.3 + + Order allow,deny + Deny from all + Satisfy All + + +# Apache >= 2.3 + + Require all denied + diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf new file mode 100644 index 0000000..73c89a9 --- /dev/null +++ b/Resources/Private/Language/locallang.xlf @@ -0,0 +1,30 @@ + + + +
+ + + Yes + + + Default + + + Alternative + + + Bookmark this page + + + Is complementary + + + In case the plugin is rendered more times on a page this option should be set for all instances except the first one. + + + List type + + + + diff --git a/Resources/Private/Layouts/Default.html b/Resources/Private/Layouts/Default.html new file mode 100644 index 0000000..6eaa733 --- /dev/null +++ b/Resources/Private/Layouts/Default.html @@ -0,0 +1,23 @@ + + +
+ +
+
+ +
+ + + +
+
+
+ diff --git a/Resources/Private/Partials/ListItem.html b/Resources/Private/Partials/ListItem.html new file mode 100644 index 0000000..a3a499a --- /dev/null +++ b/Resources/Private/Partials/ListItem.html @@ -0,0 +1,15 @@ + + +
  • + + For the content to be substituted correctly the text link needs to have the class `bookmark-link` + and the remove item the data attribute `data-remove` assigned to. + + {bookmark.title} + × +
  • + + diff --git a/Resources/Private/Templates/Bookmarks/Index.html b/Resources/Private/Templates/Bookmarks/Index.html new file mode 100644 index 0000000..f4001a4 --- /dev/null +++ b/Resources/Private/Templates/Bookmarks/Index.html @@ -0,0 +1,34 @@ + + + + + + + + Render alternative list + + + + Render default list + + you may put this into your page template: + + + + + + +

    Bookmarks

    +

    {f:translate(key: 'bookmarkThisPage')}

    + +
    + + diff --git a/Resources/Public/Scripts/JavaScript/bookmark_pages.js b/Resources/Public/Scripts/JavaScript/bookmark_pages.js new file mode 100644 index 0000000..332872c --- /dev/null +++ b/Resources/Public/Scripts/JavaScript/bookmark_pages.js @@ -0,0 +1,162 @@ +(function ($) { + const settings = { + _$bookmarks: null, + _$listItemTemplate: null, + // If set bookmarks are stored locally in localStorage + _storeLocal: false, + // Time in seconds during which the bookmarks are valid hence not queried from server + _localStorageTTL: 3600, + init () { + this._$bookmarks = $('#bookmarks'); + this._$listItemTemplate = $($('#bookmark-template').html().trim()); + // Assign settings defined by host + let pluginSettings = this._$bookmarks.data('settings'); + if (typeof pluginSettings !== 'object') { + return; + } + if (pluginSettings.storeLocal !== 'undefined') { + this._storeLocal = Boolean(parseInt(pluginSettings.storeLocal)); + } + if (pluginSettings.localStorageTTL !== 'undefined') { + this._localStorageTTL = parseInt(pluginSettings.localStorageTTL); + } + }, + get updateAjaxUri () { return this._$bookmarks.data('update-ajaxuri'); }, + get addAjaxUri () { return this._$bookmarks.data('add-ajaxuri') }, + get removeAjaxUri () { return this._$bookmarks.data('remove-ajaxuri') }, + get currentBookmark () { return this._$bookmarks.data('bookmark') }, + get $listItemTemplate () { return this._$listItemTemplate }, + get storeLocal() { return this._storeLocal; }, + get localStorageTTL() { return this._localStorageTTL } + } + + const storage = { + /** + * @return JSON object from bookmarks list held in local storage from browser + */ + get list() { + return JSON.parse(localStorage.getItem('txBookmarkPagesBookmarks')) ?? {}; + }, + + /** + * @param bookmarks Array from bookmarks + */ + set list(bookmarks) { + if (settings.storeLocal) { + localStorage.setItem('txBookmarkPagesBookmarks', JSON.stringify(bookmarks)); + localStorage.setItem('txBookmarkPagesTimestamp', Date.now()); + localStorage.setItem('txBookmarkPagesReload', '0'); + } + }, + + /** + * @return {boolean} + */ + get isOutdated() { + // Check storage age + let $expired = true; + let timestamp = localStorage.getItem('txBookmarkPagesTimestamp'); + if (timestamp) { + $expired = ((Date.now() - timestamp) / 1000) > settings.localStorageTTL; + } + // Check if a reload is requested + let $reloadRequested = Boolean(parseInt(localStorage.getItem('txBookmarkPagesReload'))); + return $expired || $reloadRequested; + }, + + containsBookmark (bookmark) { + return typeof this.list[bookmark.id] === 'object'; + } + } + + const assistant = { + updateLinks () { + if (storage.containsBookmark(settings.currentBookmark)) { + $('.bookmark-this-page').addClass('is-bookmarked'); + } else { + $('.bookmark-this-page').removeClass('is-bookmarked'); + } + }, + + /** + * Ajax callback function to update the bookmarks list and the links to bookmark a page. + * + * @param ajaxResult Object with properties `list` and `isBookmarked` + */ + listQueryHandler (ajaxResult) { + this.initList(ajaxResult.bookmarks); + storage.list = ajaxResult.bookmarks; + this.updateLinks(); + }, + ajax (url, data = {}) { + data = {...data, 'tx_wsbookmarkpages_bookmarks[localBookmarks]': storage.list} + $.ajax({ + url: url, + type: 'post', + data: data + }).done($.proxy(this.listQueryHandler, this)); + }, + initList (bookmarks) { + let $bookmarksList = $('.bookmarks-list'); + $bookmarksList.empty(); + Object.values(bookmarks).forEach(bookmark => { + let $item = settings.$listItemTemplate.clone(); + $('.bookmark-link', $item) + .attr('title', bookmark.title) + .attr('href', bookmark.url) + .text(bookmark.title); + $('.bookmark-ajax-submit', $item).data('remove', bookmark.id) + $bookmarksList.append($item); + }); + }, + initListFromStorage () { + let bookmarks = storage.list; + this.initList(bookmarks); + } + } + + const bookmarks = { + init () { + if (settings.storeLocal && !storage.isOutdated) { + assistant.initListFromStorage(); + assistant.updateLinks(); + } else { + assistant.ajax(settings.updateAjaxUri); + } + }, + add () { + assistant.ajax(settings.addAjaxUri); + }, + remove (removeID) { + assistant.ajax( + settings.removeAjaxUri, + {'tx_wsbookmarkpages_bookmarks[id]': removeID ?? settings.currentBookmark.id} + ); + } + } + + /** + * Bind event handlers and initialize the app when DOM is ready + */ + $(function () { + // doing it this way: + // $('.bookmark-ajax-submit').on('click', function (event) { + // would not work for initially hidden elements + $('.bookmark-pages').on('click', '.bookmark-ajax-submit', function (event) { + event.preventDefault(); + let $this = $(this), + removeID = $(this).data('remove'); + if ($this.hasClass('bookmark-this-page')){ + bookmarks.add(); + } else if ($this.hasClass('remove-this-page')) { + bookmarks.remove() + } else if (removeID) { + bookmarks.remove(removeID) + } + }); + + // Initialize the app + settings.init(); + bookmarks.init(); + }); +})(jQuery); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..af77f4d --- /dev/null +++ b/composer.json @@ -0,0 +1,37 @@ +{ + "name": "wapplersystems/bookmark-pages", + "type": "typo3-cms-extension", + "description": "A TYPO3 extension that provides bookmarks functionality of local pages.", + "keywords": [ + "TYPO3", + "extension", + "bookmarks" + ], + "license": "GPL-2.0-or-later", + "homepage": "", + "support": { + "issues": "" + }, + "require": { + "php": ">=7.0", + "typo3/cms-core": "^10.4" + }, + "require-dev": { + "roave/security-advisories": "dev-latest" + }, + "autoload": { + "psr-4": { + "WapplerSystems\\WsBookmarkPages\\": "Classes/" + } + }, + "replace": { + }, + "extra": { + "typo3/cms": { + "extension-key": "ws_bookmark_pages" + }, + "branch-alias": { + "dev-master": "2.x-dev" + } + } +} diff --git a/ext_emconf.php b/ext_emconf.php new file mode 100644 index 0000000..e7dd31e --- /dev/null +++ b/ext_emconf.php @@ -0,0 +1,38 @@ + 'Bookmark Pages', + 'description' => 'Provides bookmarks functionality of local pages for logged in frontend users.', + 'category' => 'plugin', + 'author' => 'René Fritz, Roman Büchler', + 'author_email' => 'r.fritz@colorcube.de, rb@buechler.pro', + 'author_company' => 'Colorcube, buechler.pro gmbh', + 'version' => '2.0.1', + 'state' => 'stable', + 'uploadfolder' => 0, + 'createDirs' => '', + 'modify_tables' => '', + 'clearCacheOnLoad' => 1, + 'constraints' => [ + 'depends' => [ + 'typo3' => '10.4.14-10.4.99', + 'typoscript_rendering' => '2.3.1-2.99.99', + ], + 'conflicts' => [], + 'suggests' => [ + 'news' => '*' + ], + ], + 'autoload' => [ + 'psr-4' => [ + 'WapplerSystems\\BookmarkPages\\' => 'Classes' + ] + ] +]; diff --git a/ext_icon.png b/ext_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..08329f13a518afce168a3fb88d8b38aecd0f92ef GIT binary patch literal 5636 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 k&-nlCY5t!LPJ@+GSo>0F8CHzX8$gGFj 'index, bookmark, delete, listEntries', + ], + // non-cacheable actions + [ + 'Bookmarks' => 'bookmark, delete, listEntries', + ] + ); + } else { + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'BookmarkPages', + 'Bookmarks', + [ + \WapplerSystems\BookmarkPages\Controller\BookmarksController::class => 'index, bookmark, delete, listEntries' + ], + [ + \WapplerSystems\BookmarkPages\Controller\BookmarksController::class => 'bookmark, delete, listEntries' + ] + ); + } +})(); diff --git a/ext_tables.sql b/ext_tables.sql new file mode 100644 index 0000000..f622475 --- /dev/null +++ b/ext_tables.sql @@ -0,0 +1,7 @@ +# +# Add field to table 'fe_users' +# +CREATE TABLE fe_users ( + tx_bookmarks_pages blob +); +