From 460ce50115e5f5a1183c3c410fd76636ee5c4716 Mon Sep 17 00:00:00 2001 From: nodiscc Date: Sun, 18 Jun 2017 06:29:15 +0200 Subject: doc: rename "datastore hacks" -> "various hacks", move example scripts to gist.github.com, remove obsolete GH wiki _Sidebar.md --- ...g-installation-over-SSH-and-serve-it-locally.md | 66 ------- .../Create-and-serve-multiple-Shaarlis-(farm).md | 57 ------- doc/md/Datastore-hacks.md | 25 --- doc/md/Download-CSS-styles-from-an-OPML-list.md | 154 ----------------- .../Example-patch---add-new-via-field-for-links.md | 189 --------------------- doc/md/Various-hacks.md | 33 ++++ doc/md/_Sidebar.md | 45 ----- 7 files changed, 33 insertions(+), 536 deletions(-) delete mode 100644 doc/md/Copy-an-existing-installation-over-SSH-and-serve-it-locally.md delete mode 100644 doc/md/Create-and-serve-multiple-Shaarlis-(farm).md delete mode 100644 doc/md/Datastore-hacks.md delete mode 100644 doc/md/Download-CSS-styles-from-an-OPML-list.md delete mode 100644 doc/md/Example-patch---add-new-via-field-for-links.md create mode 100644 doc/md/Various-hacks.md delete mode 100644 doc/md/_Sidebar.md (limited to 'doc/md') diff --git a/doc/md/Copy-an-existing-installation-over-SSH-and-serve-it-locally.md b/doc/md/Copy-an-existing-installation-over-SSH-and-serve-it-locally.md deleted file mode 100644 index 7583c9ea..00000000 --- a/doc/md/Copy-an-existing-installation-over-SSH-and-serve-it-locally.md +++ /dev/null @@ -1,66 +0,0 @@ -Example bash script: - -```bash -#!/bin/bash -#Description: Copy a Shaarli installation over SSH/SCP, serve it locally with php-cli -#Will create a local-shaarli/ directory when you run it, backup your Shaarli there, and serve it locally. -#Will NOT download linked pages. It's just a directly usable backup/copy/mirror of your Shaarli -#Requires: ssh, scp and a working SSH access to the server where your Shaarli is installed -#Usage: ./local-shaarli.sh -#Author: nodiscc (nodiscc@gmail.com) -#License: MIT (http://opensource.org/licenses/MIT) -set -o errexit -set -o nounset - -##### CONFIG ################# -#The port used by php's local server -php_local_port=7431 - -#Name of the SSH server and path where Shaarli is installed -#TODO: pass these as command-line arguments -remotehost="my.ssh.server" -remote_shaarli_dir="/var/www/shaarli" - - -###### FUNCTIONS ############# -_main() { - _CBSyncShaarli - _CBServeShaarli -} - -_CBSyncShaarli() { - remote_temp_dir=$(ssh $remotehost mktemp -d) - remote_ssh_user=$(ssh $remotehost whoami) - ssh -t "$remotehost" sudo cp -r "$remote_shaarli_dir" "$remote_temp_dir" - ssh -t "$remotehost" sudo chown -R "$remote_ssh_user":"$remote_ssh_user" "$remote_temp_dir" - scp -rq "$remotehost":"$remote_temp_dir" local-shaarli - ssh "$remotehost" rm -r "$remote_temp_dir" -} - -_CBServeShaarli() { - #TODO: allow serving a previously downloaded Shaarli - #TODO: ask before overwriting local copy, if it exists - cd local-shaarli/ - php -S localhost:${php_local_port} - echo "Please go to http://localhost:${php_local_port}" -} - - -##### MAIN ################# - -_main -``` - -This outputs: - -```bash -$ ./local-shaarli.sh -PHP 5.6.0RC4 Development Server started at Mon Sep 1 21:56:19 2014 -Listening on http://localhost:7431 -Document root is /home/user/local-shaarli/shaarli -Press Ctrl-C to quit. - -[Mon Sep 1 21:56:27 2014] ::1:57868 [200]: / -[Mon Sep 1 21:56:27 2014] ::1:57869 [200]: /index.html -[Mon Sep 1 21:56:37 2014] ::1:57881 [200]: /... -``` diff --git a/doc/md/Create-and-serve-multiple-Shaarlis-(farm).md b/doc/md/Create-and-serve-multiple-Shaarlis-(farm).md deleted file mode 100644 index d0d812a3..00000000 --- a/doc/md/Create-and-serve-multiple-Shaarlis-(farm).md +++ /dev/null @@ -1,57 +0,0 @@ -Example bash script (creates multiple shaarli instances and generates an HTML index of them) - -```bash -#!/bin/bash -set -o errexit -set -o nounset - -#config -shaarli_base_dir='/var/www/shaarli' -accounts='bob john whatever username' -shaarli_repo_url='https://github.com/shaarli/Shaarli' -ref="master" - -#clone multiple shaarli instances -if [ ! -d "$shaarli_base_dir" ]; then mkdir "$shaarli_base_dir"; fi - -for account in $accounts; do - if [ -d "$shaarli_base_dir/$account" ]; - then echo "[info] account $account already exists, skipping"; - else echo "[info] creating new account $account ..."; git clone --quiet "$shaarli_repo_url" -b "$ref" "$shaarli_base_dir/$account"; fi -done - -#generate html index of shaarlis -htmlhead=' - - - - - My Shaarli farm - - - -
-

My Shaarli farm

- -
- -' - - - -for account in $accounts; do accountlinks="$accountlinks\n
  • $account
  • "; done -if [ -d "$shaarli_base_dir/index.html" ]; then echo "[removing old index.html]"; rm "$shaarli_base_dir/index.html" ]; fi -echo "[info] generating new index of shaarlis" -echo -e "$htmlhead $accountlinks $htmlfooter" > "$shaarli_base_dir/index.html" -echo '[info] done.' -echo "[info] list of accounts: $accounts" -echo "[info] contents of $shaarli_base_dir:" -tree -a -L 1 "$shaarli_base_dir" -``` - -This script just serves as an example. More precise or complex (applying custom configuration, etc) automation is possible using configuration management software like [Ansible](https://www.ansible.com/) \ No newline at end of file diff --git a/doc/md/Datastore-hacks.md b/doc/md/Datastore-hacks.md deleted file mode 100644 index 78baa005..00000000 --- a/doc/md/Datastore-hacks.md +++ /dev/null @@ -1,25 +0,0 @@ -### Decode datastore content - -To display the array representing the data saved in `data/datastore.php`, use the following snippet: - -```php -$data = "tZNdb9MwFIb... "; -$out = unserialize(gzinflate(base64_decode($data))); -echo "
    "; // Pretty printing is love, pretty printing is life
    -print_r($out);
    -echo "
    "; -exit; -``` -This will output the internal representation of the datastore, "unobfuscated" (if this can really be considered obfuscation). - -Alternatively, you can transform to JSON format (and pretty-print if you have `jq` installed): -``` -php -r 'print(json_encode(unserialize(gzinflate(base64_decode(preg_replace("!.*/\* (.+) \*/.*!", "$1", file_get_contents("data/datastore.php")))))));' | jq . -``` - -### Changing the timestamp for a link - -* Look for `` in `tpl/editlink.tpl` (line 14) -* Replace `type="hidden"` with `type="text"` from this line -* A new date/time field becomes available in the edit/new link dialog. -* You can set the timestamp manually by entering it in the format `YYYMMDD_HHMMS`. diff --git a/doc/md/Download-CSS-styles-from-an-OPML-list.md b/doc/md/Download-CSS-styles-from-an-OPML-list.md deleted file mode 100644 index 26b7fb3e..00000000 --- a/doc/md/Download-CSS-styles-from-an-OPML-list.md +++ /dev/null @@ -1,154 +0,0 @@ -###Download CSS styles for shaarlis listed in an opml file -Example php script: - -```php - - - - -/** - * Source: https://github.com/Riduidel - * Download css styles for shaarlis listed in an opml file - */ -define("SHAARLI_RSS_OPML", "https://www.ecirtam.net/shaarlirss/custom/people.opml"); - -define("THEMES_TEMP_FOLDER", "new_themes"); - -if(!file_exists(THEMES_TEMP_FOLDER)) { - mkdir(THEMES_TEMP_FOLDER); -} - -function siteUrl($pathInSite) { - $indexPos = strpos($pathInSite, "index.php"); - if(!$indexPos) { - return $pathInSite; - } else { - return substr($pathInSite, 0, $indexPos); - } -} - -function createShaarliHashFromOPMLL($opmlFile) { - $result = array(); - $opml = file_get_contents($opmlFile); - $opmlXml = simplexml_load_string($opml); - $outlineElements = $opmlXml->xpath("body/outline"); - foreach($outlineElements as $site) { - $siteUrl = siteUrl((string) $site['htmlUrl']); - $result[$siteUrl]=((string) $site['text']); - } - return $result; -} - -function getSiteFolder($url) { - $domain = parse_url($url, PHP_URL_HOST); - return THEMES_TEMP_FOLDER."/".str_replace(".", "_", $domain); -} - -function get_http_response_code($theURL) { - $headers = get_headers($theURL); - return substr($headers[0], 9, 3); -} - -/** - * This makes the code PHP-5 only (particularly the call to "get_headers") - */ -function copyUserStyleFrom($url, $name, $knownStyles) { - $userStyle = $url."inc/user.css"; - if(in_array($url, $knownStyles)) { - // TODO add log message - } else { - $statusCode = get_http_response_code($userStyle); - if(intval($statusCode)<300) { - $styleSheet = file_get_contents($userStyle); - $siteFolder = getSiteFolder($url); - if(!file_exists($siteFolder)) { - mkdir($siteFolder); - } - if(!file_exists($siteFolder.'/user.css')) { - // Copy stylesheet - file_put_contents($siteFolder.'/user.css', $styleSheet); - } - if(!file_exists($siteFolder.'/README.md')) { - // Then write a readme.md file - file_put_contents($siteFolder.'/README.md', - "User style from ".$name."\n" - ."=============================" - ."\n\n" - ."This stylesheet was downloaded from ".$userStyle." on ".date(DATE_RFC822) - ); - } - if(!file_exists($siteFolder.'/config.ini')) { - // Write a config file containing useful informations - file_put_contents($siteFolder.'/config.ini', - "site_url=".$url."\n" - ."site_name=".$name."\n" - ); - } - if(!file_exists($siteFolder.'/home.png')) { - // And finally copy generated thumbnail - $homeThumb = $siteFolder.'/home.png'; - file_put_contents($siteFolder.'/home.png', file_get_contents(getThumbnailUrl($url))); - } - echo 'Theme have been downloaded from '.$url.' into '.$siteFolder - .'. It looks like
    '; - } - } -} - -function getThumbnailUrl($url) { - return 'http://api.webthumbnail.org/?url='.$url; -} - -function copyUserStylesFrom($urlToNames, $knownStyles) { - foreach($urlToNames as $url => $name) { - copyUserStyleFrom($url, $name, $knownStyles); - } -} - -/** - * Reading directory list, courtesy of http://www.laughing-buddha.net/php/dirlist/ - * @param directory the directory we want to list files of - * @return a simple array containing the list of absolute file paths. Notice that current file (".") and parent one("..") - * are not listed here - */ -function getDirectoryList ($directory) { - $realPath = realpath($directory); - // create an array to hold directory list - $results = array(); - // create a handler for the directory - $handler = opendir($directory); - // open directory and walk through the filenames - while ($file = readdir($handler)) { - // if file isn't this directory or its parent, add it to the results - if ($file != "." && $file != "..") { - $results[] = realpath($realPath . "/" . $file); - } - } - // tidy up: close the handler - closedir($handler); - // done! - return $results; -} - -/** - * Start in themes folder and look in all subfolders for config.ini files. - * These config.ini files allow us not to download styles again and again - */ -function findKnownStyles() { - $result = array(); - $subFolders = getDirectoryList("themes"); - foreach($subFolders as $folder) { - $configFile = $folder."/config.ini"; - if(file_exists($configFile)) { - $iniParameters = parse_ini_file($configFile); - array_push($result, $iniParameters['site_url']); - } - } - return $result; -} - -$knownStyles = findKnownStyles(); -copyUserStylesFrom(createShaarliHashFromOPMLL(SHAARLI_RSS_OPML), $knownStyles); - - -``` \ No newline at end of file diff --git a/doc/md/Example-patch---add-new-via-field-for-links.md b/doc/md/Example-patch---add-new-via-field-for-links.md deleted file mode 100644 index d84ef25a..00000000 --- a/doc/md/Example-patch---add-new-via-field-for-links.md +++ /dev/null @@ -1,189 +0,0 @@ -Example patch to add a new field ("via") for links, an input field to set the "via" property from the "edit link" dialog, and display the "via" field in the link list display. **Untested, use at your own risk** - -Thanks to @Knah-Tsaeb in https://github.com/sebsauvage/Shaarli/pull/158 - -``` -From e0f363c18e8fe67990ed2bb1a08652e24e70bbcb Mon Sep 17 00:00:00 2001 -From: Knah Tsaeb -Date: Fri, 11 Oct 2013 15:18:37 +0200 -Subject: [PATCH] Add a "via"/origin property for links, add new input in "edit link" dialog -Thanks to: -* https://github.com/Knah-Tsaeb/Shaarli/commit/040eb18ec8cdabd5ea855e108f81f97fbf0478c4 -* https://github.com/Knah-Tsaeb/Shaarli/commit/4123658eae44d7564d1128ce52ddd5689efee813 -* https://github.com/Knah-Tsaeb/Shaarli/commit/f1a8ca9cc8fe49b119d51b2d8382cc1a34542f96 - ---- - index.php | 43 ++++++++++++++++++++++++++++++++----------- - tpl/editlink.html | 1 + - tpl/linklist.html | 1 + - 3 files changed, 34 insertions(+), 11 deletions(-) - -diff --git a/index.php b/index.php -index 6fae2f8..53f798e 100644 ---- a/index.php -+++ b/index.php -@@ -436,6 +436,12 @@ if (isset($_POST['login'])) - // ------------------------------------------------------------------------------------------ - // Misc utility functions: - -+// Try to get just domain for @via -+function getJustDomain($url){ -+ $parts = parse_url($url); -+ return trim($parts['host']); -+ } -+ - // Returns the server URL (including port and http/https), without path. - // e.g. "http://myserver.com:8080" - // You can append $_SERVER['SCRIPT_NAME'] to get the current script URL. -@@ -799,7 +805,8 @@ class linkdb implements Iterator, Countable, ArrayAccess - $found= (strpos(strtolower($l['title']),$s)!==false) - || (strpos(strtolower($l['description']),$s)!==false) - || (strpos(strtolower($l['url']),$s)!==false) -- || (strpos(strtolower($l['tags']),$s)!==false); -+ || (strpos(strtolower($l['tags']),$s)!==false) -+ || (!empty($l['via']) && (strpos(strtolower($l['via']),$s)!==false)); - if ($found) $filtered[$l['linkdate']] = $l; - } - krsort($filtered); -@@ -814,7 +821,7 @@ class linkdb implements Iterator, Countable, ArrayAccess - $t = str_replace(',',' ',($casesensitive?$tags:strtolower($tags))); - $searchtags=explode(' ',$t); - $filtered=array(); -- foreach($this->links as $l) -+ foreach($this-> links as $l) - { - $linktags = explode(' ',($casesensitive?$l['tags']:strtolower($l['tags']))); - if (count(array_intersect($linktags,$searchtags)) == count($searchtags)) -@@ -905,7 +912,7 @@ function showRSS() - else $linksToDisplay = $LINKSDB; - $nblinksToDisplay = 50; // Number of links to display. - if (!empty($_GET['nb'])) // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links. -- { -+ { - $nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max($_GET['nb']+0,1) ; - } - -@@ -944,7 +951,12 @@ function showRSS() - // If user wants permalinks first, put the final link in description - if ($usepermalinks===true) $descriptionlink = '(Link)'; - if (strlen($link['description'])>0) $descriptionlink = '
    '.$descriptionlink; -- echo ''."\n\n"; -+ if(!empty($link['via'])){ -+ $via = '
    Origine => '.htmlspecialchars(getJustDomain($link['via'])).''; -+ } else { -+ $via = ''; -+ } -+ echo ''."\n\n"; - $i++; - } - echo ''; -@@ -980,7 +992,7 @@ function showATOM() - else $linksToDisplay = $LINKSDB; - $nblinksToDisplay = 50; // Number of links to display. - if (!empty($_GET['nb'])) // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links. -- { -+ { - $nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max($_GET['nb']+0,1) ; - } - -@@ -1006,11 +1018,16 @@ function showATOM() - - // Add permalink in description - $descriptionlink = htmlspecialchars('(Permalink)'); -+ if(isset($link['via']) && !empty($link['via'])){ -+ $via = htmlspecialchars('
    Origine => '.getJustDomain($link['via']).''); -+ } else { -+ $via = ''; -+ } - // If user wants permalinks first, put the final link in description - if ($usepermalinks===true) $descriptionlink = htmlspecialchars('(Link)'); - if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink; - -- $entries.=''.htmlspecialchars(nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($link['description']))))).$descriptionlink."\n"; -+ $entries.=''.htmlspecialchars(nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($link['description']))))).$descriptionlink.$via."\n"; - if ($link['tags']!='') // Adding tags to each ATOM entry (as mentioned in ATOM specification) - { - foreach(explode(' ',$link['tags']) as $tag) -@@ -1478,7 +1495,7 @@ function renderPage() - if (!startsWith($url,'http:') && !startsWith($url,'https:') && !startsWith($url,'ftp:') && !startsWith($url,'magnet:') && !startsWith($url,'?')) - $url = 'http://'.$url; - $link = array('title'=>trim($_POST['lf_title']),'url'=>$url,'description'=>trim($_POST['lf_description']),'private'=>(isset($_POST['lf_private']) ? 1 : 0), -- 'linkdate'=>$linkdate,'tags'=>str_replace(',',' ',$tags)); -+ 'linkdate'=>$linkdate,'tags'=>str_replace(',',' ',$tags), 'via'=>trim($_POST['lf_via'])); - if ($link['title']=='') $link['title']=$link['url']; // If title is empty, use the URL as title. - $LINKSDB[$linkdate] = $link; - $LINKSDB->savedb(); // Save to disk. -@@ -1556,7 +1573,8 @@ function renderPage() - $title = (empty($_GET['title']) ? '' : $_GET['title'] ); // Get title if it was provided in URL (by the bookmarklet). - $description = (empty($_GET['description']) ? '' : $_GET['description']); // Get description if it was provided in URL (by the bookmarklet). [Bronco added that] - $tags = (empty($_GET['tags']) ? '' : $_GET['tags'] ); // Get tags if it was provided in URL -- $private = (!empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0); // Get private if it was provided in URL -+ $via = (empty($_GET['via']) ? '' : $_GET['via'] ); -+ $private = (!empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0); // Get private if it was provided in URL - if (($url!='') && parse_url($url,PHP_URL_SCHEME)=='') $url = 'http://'.$url; - // If this is an HTTP link, we try go get the page to extract the title (otherwise we will to straight to the edit form.) - if (empty($title) && parse_url($url,PHP_URL_SCHEME)=='http') -@@ -1567,7 +1585,7 @@ function renderPage() - { - // Look for charset in html header. - preg_match('##Usi', $data, $meta); -- -+ - // If found, extract encoding. - if (!empty($meta[0])) - { -@@ -1577,7 +1595,7 @@ function renderPage() - $html_charset = (!empty($enc[1])) ? strtolower($enc[1]) : 'utf-8'; - } - else { $html_charset = 'utf-8'; } -- -+ - // Extract title - $title = html_extract_title($data); - if (!empty($title)) -@@ -1592,7 +1610,7 @@ function renderPage() - $url='?'.smallHash($linkdate); - $title='Note: '; - } -- $link = array('linkdate'=>$linkdate,'title'=>$title,'url'=>$url,'description'=>$description,'tags'=>$tags,'private'=>$private); -+ $link = array('linkdate'=>$linkdate,'title'=>$title,'url'=>$url,'description'=>$description,'tags'=>$tags,'via' => $via,'private'=>$private); - } - - $PAGE = new pageBuilder; -@@ -1842,6 +1860,9 @@ function buildLinkList($PAGE,$LINKSDB) - $taglist = explode(' ',$link['tags']); - uasort($taglist, 'strcasecmp'); - $link['taglist']=$taglist; -+ if(!empty($link['via'])){ -+ $link['via']=htmlspecialchars($link['via']); -+ } - $linkDisp[$keys[$i]] = $link; - $i++; - } -diff --git a/tpl/editlink.html b/tpl/editlink.html -index 4a2c30c..14d4f9c 100644 ---- a/tpl/editlink.html -+++ b/tpl/editlink.html -@@ -16,6 +16,7 @@ - Title

    - Description

    - Tags

    -+ Origine

    - {if condition="($link_is_new && $GLOBALS['privateLinkByDefault']==true) || $link.private == true"} - -  
    -diff --git a/tpl/linklist.html b/tpl/linklist.html -index ddc38cb..0a8475f 100644 ---- a/tpl/linklist.html -+++ b/tpl/linklist.html -@@ -43,6 +43,7 @@ - {$value.title|htmlspecialchars} -
    - {if="$value.description"}
    {$value.description}
    {/if} -+ {if condition="isset($value.via) && !empty($value.via)"}
    Origine => {$value.via|getJustDomain}
    {/if} - {if="!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()"} - {$value.localdate|htmlspecialchars} - permalink - - {else} --- -2.1.1 -``` \ No newline at end of file diff --git a/doc/md/Various-hacks.md b/doc/md/Various-hacks.md new file mode 100644 index 00000000..a4ae81f4 --- /dev/null +++ b/doc/md/Various-hacks.md @@ -0,0 +1,33 @@ +### Decode datastore content + +To display the array representing the data saved in `data/datastore.php`, use the following snippet: + +```php +$data = "tZNdb9MwFIb... "; +$out = unserialize(gzinflate(base64_decode($data))); +echo "
    "; // Pretty printing is love, pretty printing is life
    +print_r($out);
    +echo "
    "; +exit; +``` +This will output the internal representation of the datastore, "unobfuscated" (if this can really be considered obfuscation). + +Alternatively, you can transform to JSON format (and pretty-print if you have `jq` installed): +``` +php -r 'print(json_encode(unserialize(gzinflate(base64_decode(preg_replace("!.*/\* (.+) \*/.*!", "$1", file_get_contents("data/datastore.php")))))));' | jq . +``` + +### Changing the timestamp for a shaare + +* Look for `` in `tpl/editlink.tpl` (line 14) +* Replace `type="hidden"` with `type="text"` from this line +* A new date/time field becomes available in the edit/new link dialog. +* You can set the timestamp manually by entering it in the format `YYYMMDD_HHMMS`. + + +### See also + + * [Add a new custom field to shaares (example patch)](https://gist.github.com/nodiscc/8b0194921f059d7b9ad89a581ecd482c) + * [Download CSS styles for shaarlis listed in an opml file](https://gist.github.com/nodiscc/dede231c92cab22c3ad2cc24d5035012) + * [Copy an existing Shaarli installation over SSH, and serve it locally](https://gist.github.com/nodiscc/ed161c66e5b028b5299b0a3733d01c77) + * [Create multiple Shaarli instances, generate an HTML index of them](https://gist.github.com/nodiscc/52e711cda3bc47717c16065231cf6b20) \ No newline at end of file diff --git a/doc/md/_Sidebar.md b/doc/md/_Sidebar.md deleted file mode 100644 index fe0e4229..00000000 --- a/doc/md/_Sidebar.md +++ /dev/null @@ -1,45 +0,0 @@ -- [[Home]] -- Setup - - [[Download and Installation]] - - [[Upgrade and migration]] - - [[Server requirements]] - - [[Server configuration]] - - [[Server security]] - - [[Shaarli configuration]] - - [[Plugins]] -- Docker - - [[Docker 101]] - - [[Shaarli images]] - - [[Reverse proxy configuration]] - - [[Docker resources]] -- Usage - - [[Features]] - - [[Bookmarklet]] - - [[Browsing and Searching]] - - [[Firefox share]] - - [[RSS feeds]] - - [[REST API]] -- How To - - [[Backup, restore, import and export]] - - [[Copy an existing installation over SSH and serve it locally]] - - [[Create and serve multiple Shaarlis (farm)]] - - [[Download CSS styles from an OPML list]] - - [[Datastore hacks]] -- [[Troubleshooting]] -- Development: - - [[Development guidelines]] - - [[Continuous integration tools]] - - [[GnuPG signature]] - - [[Coding guidelines]] - - [[Directory structure]] - - [[3rd party libraries]] - - [[Plugin System]] - - [[Release Shaarli]] - - [[Versioning and Branches]] - - [[Security]] - - [[Static analysis]] - - [[Theming]] - - [[Unit tests]] -- About - - [[FAQ]] - - [[Community & Related software]] -- cgit v1.2.3