aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.editorconfig23
-rw-r--r--.gitattributes1
-rw-r--r--.github/mailmap2
-rw-r--r--AUTHORS9
-rw-r--r--CHANGELOG.md38
-rw-r--r--README.md2
-rw-r--r--application/ApplicationUtils.php15
-rw-r--r--application/History.php16
-rw-r--r--application/NetscapeBookmarkUtils.php16
-rw-r--r--application/PageBuilder.php9
-rw-r--r--application/Updater.php4
-rw-r--r--data/.htaccess12
-rw-r--r--doc/md/Upgrade-and-migration.md10
-rw-r--r--doc/md/docker/docker-101.md78
-rw-r--r--doc/md/index.md11
-rw-r--r--index.php6
-rw-r--r--tests/NetscapeBookmarkUtils/BookmarkImportTest.php81
-rw-r--r--tpl/default/includes.html14
-rw-r--r--tpl/default/js/shaarli.js6
-rw-r--r--tpl/default/page.footer.html6
-rw-r--r--tpl/default/tag.cloud.html2
21 files changed, 283 insertions, 78 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..4a6589a2
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,23 @@
1# EditorConfig: http://EditorConfig.org
2
3root = true
4
5[*]
6charset = utf-8
7end_of_line = lf
8insert_final_newline = true
9trim_trailing_whitespace = true
10indent_style = space
11indent_size = 4
12
13[*.{htaccess,html,xml}]
14indent_size = 2
15
16[*.php]
17max_line_length = 100
18
19[Dockerfile]
20max_line_length = 80
21
22[Makefile]
23indent_style = tab
diff --git a/.gitattributes b/.gitattributes
index dd0e573c..93900602 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -24,6 +24,7 @@ Dockerfile text
24*.min.js binary 24*.min.js binary
25 25
26# Exclude from Git archives 26# Exclude from Git archives
27.editorconfig export-ignore
27.gitattributes export-ignore 28.gitattributes export-ignore
28.github export-ignore 29.github export-ignore
29.gitignore export-ignore 30.gitignore export-ignore
diff --git a/.github/mailmap b/.github/mailmap
index 41d91e47..bbdb7908 100644
--- a/.github/mailmap
+++ b/.github/mailmap
@@ -11,3 +11,5 @@ Timo Van Neerden <fire@lehollandaisvolant.net> lehollandaisvolant <levoltigeurho
11VirtualTam <virtualtam@flibidi.net> <tamisier.aurelien@gmail.com> 11VirtualTam <virtualtam@flibidi.net> <tamisier.aurelien@gmail.com>
12VirtualTam <virtualtam@flibidi.net> <virtualtam+github@flibidi.net> 12VirtualTam <virtualtam@flibidi.net> <virtualtam+github@flibidi.net>
13VirtualTam <virtualtam@flibidi.net> <virtualtam@flibidi.org> 13VirtualTam <virtualtam@flibidi.net> <virtualtam@flibidi.org>
14Willi Eggeling <thewilli@gmail.com> <mail@wje-online.de>
15Willi Eggeling <thewilli@gmail.com> <thewilli@users.noreply.github.com>
diff --git a/AUTHORS b/AUTHORS
index 2181ec9d..105561c1 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,11 +1,13 @@
1 518 ArthurHoaro <arthur@hoa.ro> 1 537 ArthurHoaro <arthur@hoa.ro>
2 231 VirtualTam <virtualtam@flibidi.net> 2 252 VirtualTam <virtualtam@flibidi.net>
3 147 nodiscc <nodiscc@gmail.com> 3 148 nodiscc <nodiscc@gmail.com>
4 56 Sébastien Sauvage <sebsauvage@sebsauvage.net> 4 56 Sébastien Sauvage <sebsauvage@sebsauvage.net>
5 15 Florian Eula <eula.florian@gmail.com> 5 15 Florian Eula <eula.florian@gmail.com>
6 13 Emilien Klein <emilien@klein.st> 6 13 Emilien Klein <emilien@klein.st>
7 12 Nicolas Danelon <hi@nicolasmd.com.ar> 7 12 Nicolas Danelon <hi@nicolasmd.com.ar>
8 9 Willi Eggeling <thewilli@gmail.com>
8 8 Christophe HENRY <christophe.henry@sbgodin.fr> 9 8 Christophe HENRY <christophe.henry@sbgodin.fr>
10 6 B. van Berkum <dev@dotmpe.com>
9 5 Lucas Cimon <lucas.cimon@gmail.com> 11 5 Lucas Cimon <lucas.cimon@gmail.com>
10 4 Alexandre Alapetite <alexandre@alapetite.fr> 12 4 Alexandre Alapetite <alexandre@alapetite.fr>
11 4 David Sferruzza <david.sferruzza@gmail.com> 13 4 David Sferruzza <david.sferruzza@gmail.com>
@@ -37,6 +39,7 @@
37 1 Kevin Canévet <kevin@streamroot.io> 39 1 Kevin Canévet <kevin@streamroot.io>
38 1 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org> 40 1 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org>
39 1 Lionel Martin <renarddesmers@gmail.com> 41 1 Lionel Martin <renarddesmers@gmail.com>
42 1 Mark Gerarts <mark.gerarts@gmail.com>
40 1 Marsup <marsup@gmail.com> 43 1 Marsup <marsup@gmail.com>
41 1 Sbgodin <Sbgodin@users.noreply.github.com> 44 1 Sbgodin <Sbgodin@users.noreply.github.com>
42 1 TsT <tst2005@gmail.com> 45 1 TsT <tst2005@gmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 60262d56..120c5d22 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,44 @@ All notable changes to this project will be documented in this file.
4The format is based on [Keep a Changelog](http://keepachangelog.com/) 4The format is based on [Keep a Changelog](http://keepachangelog.com/)
5and this project adheres to [Semantic Versioning](http://semver.org/). 5and this project adheres to [Semantic Versioning](http://semver.org/).
6 6
7## [v0.9.2](https://github.com/shaarli/Shaarli/releases/tag/v0.9.2) - 2017-10-07
8
9**Major security issue fixed. Please update.**
10
11### Added
12- Tag search now supports wildcards `*`
13- New setting `privacy.force_login` which can be used with `privacy.hide_public_links` to redirect anonymous users to the login page.
14- New setting `general.default_note_title` used to override default `Note:` title prefix for notes.
15- Add a version hash for asset loading to prevent browser's cache issue
16
17### Changed
18- The "Remember me" checkbox is unchecked by default
19- The default value of the "Remember me" checkbox can be configured under `data/config.json.php`
20
21### Removed
22- Remove obsolete PHP magic quote support
23
24### Fixed
25- Generates a permalink URL if the URL is set to blank
26- Replace links to the old GitHub wiki with ReadTheDocs URIs
27- Use single quotes in the note bookmarklet
28- Daily page if there is no link
29- Bulk link deletion with a single link
30- HTTPS detection behind a reverse proxy
31- Travis tests environment and localization
32- Improve template paths robustness (trailing slash)
33- Robustness: safer gzinflate/zlib usage
34- Description links parsing with parenthesis (without Markdown)
35- Templates:
36 - Sort the tag cloud alphabetically
37 - Firefox social title
38 - Improved visited link color
39 - Fix jumpy textarea with long content in post edit
40
41### Security
42
43- Vulnerability introduced in v0.9.1 fixed.
44
7## [v0.9.1](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) - 2017-08-23 45## [v0.9.1](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) - 2017-08-23
8 46
9The documentation has been migrated to ReadTheDocs: 47The documentation has been migrated to ReadTheDocs:
diff --git a/README.md b/README.md
index 100ff46b..c1050027 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ _It is designed to be personal (single-user), fast and handy._
9[![](https://img.shields.io/badge/stable-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) 9[![](https://img.shields.io/badge/stable-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4)
10[![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli) 10[![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli)
11&bull; 11&bull;
12[![](https://img.shields.io/badge/latest-v0.9.1-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) 12[![](https://img.shields.io/badge/latest-v0.9.2-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.2)
13[![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli) 13[![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli)
14&bull; 14&bull;
15[![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli) 15[![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli)
diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php
index 123cc0b3..5643f4a0 100644
--- a/application/ApplicationUtils.php
+++ b/application/ApplicationUtils.php
@@ -221,4 +221,19 @@ class ApplicationUtils
221 221
222 return $errors; 222 return $errors;
223 } 223 }
224
225 /**
226 * Returns a salted hash representing the current Shaarli version.
227 *
228 * Useful for assets browser cache.
229 *
230 * @param string $currentVersion of Shaarli
231 * @param string $salt User personal salt, also used for the authentication
232 *
233 * @return string version hash
234 */
235 public static function getVersionHash($currentVersion, $salt)
236 {
237 return hash_hmac('sha256', $currentVersion, $salt);
238 }
224} 239}
diff --git a/application/History.php b/application/History.php
index 116b9264..5e3b1b72 100644
--- a/application/History.php
+++ b/application/History.php
@@ -16,6 +16,7 @@
16 * - UPDATED: link updated 16 * - UPDATED: link updated
17 * - DELETED: link deleted 17 * - DELETED: link deleted
18 * - SETTINGS: the settings have been updated through the UI. 18 * - SETTINGS: the settings have been updated through the UI.
19 * - IMPORT: bulk links import
19 * 20 *
20 * Note: new events are put at the beginning of the file and history array. 21 * Note: new events are put at the beginning of the file and history array.
21 */ 22 */
@@ -42,6 +43,11 @@ class History
42 const SETTINGS = 'SETTINGS'; 43 const SETTINGS = 'SETTINGS';
43 44
44 /** 45 /**
46 * @var string Action key: a bulk import has been processed.
47 */
48 const IMPORT = 'IMPORT';
49
50 /**
45 * @var string History file path. 51 * @var string History file path.
46 */ 52 */
47 protected $historyFilePath; 53 protected $historyFilePath;
@@ -122,6 +128,16 @@ class History
122 } 128 }
123 129
124 /** 130 /**
131 * Add Event: bulk import.
132 *
133 * Note: we don't store links add/update one by one since it can have a huge impact on performances.
134 */
135 public function importLinks()
136 {
137 $this->addEvent(self::IMPORT);
138 }
139
140 /**
125 * Save a new event and write it in the history file. 141 * Save a new event and write it in the history file.
126 * 142 *
127 * @param string $status Event key, should be defined as constant. 143 * @param string $status Event key, should be defined as constant.
diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php
index 2a10ff22..31796367 100644
--- a/application/NetscapeBookmarkUtils.php
+++ b/application/NetscapeBookmarkUtils.php
@@ -66,6 +66,7 @@ class NetscapeBookmarkUtils
66 * @param int $importCount how many links were imported 66 * @param int $importCount how many links were imported
67 * @param int $overwriteCount how many links were overwritten 67 * @param int $overwriteCount how many links were overwritten
68 * @param int $skipCount how many links were skipped 68 * @param int $skipCount how many links were skipped
69 * @param int $duration how many seconds did the import take
69 * 70 *
70 * @return string Summary of the bookmark import status 71 * @return string Summary of the bookmark import status
71 */ 72 */
@@ -74,14 +75,16 @@ class NetscapeBookmarkUtils
74 $filesize, 75 $filesize,
75 $importCount=0, 76 $importCount=0,
76 $overwriteCount=0, 77 $overwriteCount=0,
77 $skipCount=0 78 $skipCount=0,
79 $duration=0
78 ) 80 )
79 { 81 {
80 $status = 'File '.$filename.' ('.$filesize.' bytes) '; 82 $status = 'File '.$filename.' ('.$filesize.' bytes) ';
81 if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) { 83 if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
82 $status .= 'has an unknown file format. Nothing was imported.'; 84 $status .= 'has an unknown file format. Nothing was imported.';
83 } else { 85 } else {
84 $status .= 'was successfully processed: '.$importCount.' links imported, '; 86 $status .= 'was successfully processed in '. $duration .' seconds: ';
87 $status .= $importCount.' links imported, ';
85 $status .= $overwriteCount.' links overwritten, '; 88 $status .= $overwriteCount.' links overwritten, ';
86 $status .= $skipCount.' links skipped.'; 89 $status .= $skipCount.' links skipped.';
87 } 90 }
@@ -101,6 +104,7 @@ class NetscapeBookmarkUtils
101 */ 104 */
102 public static function import($post, $files, $linkDb, $conf, $history) 105 public static function import($post, $files, $linkDb, $conf, $history)
103 { 106 {
107 $start = time();
104 $filename = $files['filetoupload']['name']; 108 $filename = $files['filetoupload']['name'];
105 $filesize = $files['filetoupload']['size']; 109 $filesize = $files['filetoupload']['size'];
106 $data = file_get_contents($files['filetoupload']['tmp_name']); 110 $data = file_get_contents($files['filetoupload']['tmp_name']);
@@ -184,7 +188,6 @@ class NetscapeBookmarkUtils
184 $linkDb[$existingLink['id']] = $newLink; 188 $linkDb[$existingLink['id']] = $newLink;
185 $importCount++; 189 $importCount++;
186 $overwriteCount++; 190 $overwriteCount++;
187 $history->updateLink($newLink);
188 continue; 191 continue;
189 } 192 }
190 193
@@ -196,16 +199,19 @@ class NetscapeBookmarkUtils
196 $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']); 199 $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
197 $linkDb[$newLink['id']] = $newLink; 200 $linkDb[$newLink['id']] = $newLink;
198 $importCount++; 201 $importCount++;
199 $history->addLink($newLink);
200 } 202 }
201 203
202 $linkDb->save($conf->get('resource.page_cache')); 204 $linkDb->save($conf->get('resource.page_cache'));
205 $history->importLinks();
206
207 $duration = time() - $start;
203 return self::importStatus( 208 return self::importStatus(
204 $filename, 209 $filename,
205 $filesize, 210 $filesize,
206 $importCount, 211 $importCount,
207 $overwriteCount, 212 $overwriteCount,
208 $skipCount 213 $skipCount,
214 $duration
209 ); 215 );
210 } 216 }
211} 217}
diff --git a/application/PageBuilder.php b/application/PageBuilder.php
index 7a42400d..291860ad 100644
--- a/application/PageBuilder.php
+++ b/application/PageBuilder.php
@@ -49,7 +49,7 @@ class PageBuilder
49 49
50 try { 50 try {
51 $version = ApplicationUtils::checkUpdate( 51 $version = ApplicationUtils::checkUpdate(
52 shaarli_version, 52 SHAARLI_VERSION,
53 $this->conf->get('resource.update_check'), 53 $this->conf->get('resource.update_check'),
54 $this->conf->get('updates.check_updates_interval'), 54 $this->conf->get('updates.check_updates_interval'),
55 $this->conf->get('updates.check_updates'), 55 $this->conf->get('updates.check_updates'),
@@ -75,7 +75,11 @@ class PageBuilder
75 } 75 }
76 $this->tpl->assign('searchcrits', $searchcrits); 76 $this->tpl->assign('searchcrits', $searchcrits);
77 $this->tpl->assign('source', index_url($_SERVER)); 77 $this->tpl->assign('source', index_url($_SERVER));
78 $this->tpl->assign('version', shaarli_version); 78 $this->tpl->assign('version', SHAARLI_VERSION);
79 $this->tpl->assign(
80 'version_hash',
81 ApplicationUtils::getVersionHash(SHAARLI_VERSION, $this->conf->get('credentials.salt'))
82 );
79 $this->tpl->assign('scripturl', index_url($_SERVER)); 83 $this->tpl->assign('scripturl', index_url($_SERVER));
80 $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links? 84 $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links?
81 $this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly'])); 85 $this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly']));
@@ -89,6 +93,7 @@ class PageBuilder
89 $this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss'); 93 $this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss');
90 $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false)); 94 $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false));
91 $this->tpl->assign('token', getToken($this->conf)); 95 $this->tpl->assign('token', getToken($this->conf));
96
92 if ($this->linkDB !== null) { 97 if ($this->linkDB !== null) {
93 $this->tpl->assign('tags', $this->linkDB->linksCountPerTag()); 98 $this->tpl->assign('tags', $this->linkDB->linksCountPerTag());
94 } 99 }
diff --git a/application/Updater.php b/application/Updater.php
index 40a15906..72b2def0 100644
--- a/application/Updater.php
+++ b/application/Updater.php
@@ -398,7 +398,7 @@ class Updater
398 */ 398 */
399 public function updateMethodCheckUpdateRemoteBranch() 399 public function updateMethodCheckUpdateRemoteBranch()
400 { 400 {
401 if (shaarli_version === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') { 401 if (SHAARLI_VERSION === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') {
402 return true; 402 return true;
403 } 403 }
404 404
@@ -413,7 +413,7 @@ class Updater
413 $latestMajor = $matches[1]; 413 $latestMajor = $matches[1];
414 414
415 // Get current major version digit 415 // Get current major version digit
416 preg_match('/(\d+)\.\d+$/', shaarli_version, $matches); 416 preg_match('/(\d+)\.\d+$/', SHAARLI_VERSION, $matches);
417 $currentMajor = $matches[1]; 417 $currentMajor = $matches[1];
418 418
419 if ($currentMajor === $latestMajor) { 419 if ($currentMajor === $latestMajor) {
diff --git a/data/.htaccess b/data/.htaccess
index f601c1ee..1d49da37 100644
--- a/data/.htaccess
+++ b/data/.htaccess
@@ -1,10 +1,16 @@
1<IfModule version_module> 1<IfModule version_module>
2 <IfVersion >= 2.4> 2 <IfVersion >= 2.4>
3 Require all denied 3 Require all denied
4 <Files "user.css">
5 Require all granted
6 </Files>
4 </IfVersion> 7 </IfVersion>
5 <IfVersion < 2.4> 8 <IfVersion < 2.4>
6 Allow from none 9 Allow from none
7 Deny from all 10 Deny from all
11 <Files "user.css">
12 Allow from all
13 </Files>
8 </IfVersion> 14 </IfVersion>
9</IfModule> 15</IfModule>
10 16
diff --git a/doc/md/Upgrade-and-migration.md b/doc/md/Upgrade-and-migration.md
index b3a08764..7033cd41 100644
--- a/doc/md/Upgrade-and-migration.md
+++ b/doc/md/Upgrade-and-migration.md
@@ -14,7 +14,7 @@ Shaarli stores all user data under the `data` directory:
14- `data/ipbans.php` - banned IP addresses 14- `data/ipbans.php` - banned IP addresses
15- `data/updates.txt` - contains all automatic update to the configuration and datastore files already run 15- `data/updates.txt` - contains all automatic update to the configuration and datastore files already run
16 16
17See [Shaarli configuration](Shaarli configuration) for more information about Shaarli resources. 17See [Shaarli configuration](Shaarli-configuration) for more information about Shaarli resources.
18 18
19It is recommended to backup this repository _before_ starting updating/upgrading Shaarli: 19It is recommended to backup this repository _before_ starting updating/upgrading Shaarli:
20 20
@@ -27,7 +27,7 @@ As all user data is kept under `data`, this is the only directory you need to wo
27 27
28- backup the `data` directory 28- backup the `data` directory
29- install or update Shaarli: 29- install or update Shaarli:
30 - fresh installation - see [Download and installation](Download and installation) 30 - fresh installation - see [Download and installation](Download-and-installation)
31 - update - see the following sections 31 - update - see the following sections
32- check or restore the `data` directory 32- check or restore the `data` directory
33 33
@@ -35,11 +35,11 @@ As all user data is kept under `data`, this is the only directory you need to wo
35 35
36All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page. 36All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page.
37 37
38We recommend that you use the latest release tarball with the `-full` suffix. It contains the dependencies, please read [Download and installation](Download and installation) for `git` complete instructions. 38We recommend that you use the latest release tarball with the `-full` suffix. It contains the dependencies, please read [Download and installation](Download-and-installation) for `git` complete instructions.
39 39
40Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the content of the `data` directory! 40Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the content of the `data` directory!
41 41
42After upgrading, access your fresh Shaarli installation from a web browser; the configuration and data store will then be automatically updated, and new settings added to `data/config.json.php` (see [Shaarli configuration](Shaarli configuration) for more details). 42After upgrading, access your fresh Shaarli installation from a web browser; the configuration and data store will then be automatically updated, and new settings added to `data/config.json.php` (see [Shaarli configuration](Shaarli-configuration) for more details).
43 43
44## Upgrading with Git 44## Upgrading with Git
45 45
@@ -173,7 +173,7 @@ Total 3317 (delta 2050), reused 3301 (delta 2034)to
173 173
174#### Step 3: configuration 174#### Step 3: configuration
175 175
176After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli configuration) for more details). 176After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli-configuration) for more details).
177 177
178## Troubleshooting 178## Troubleshooting
179 179
diff --git a/doc/md/docker/docker-101.md b/doc/md/docker/docker-101.md
index b02dd149..a9c00b85 100644
--- a/doc/md/docker/docker-101.md
+++ b/doc/md/docker/docker-101.md
@@ -60,3 +60,81 @@ wheezy: Pulling from debian
60Digest: sha256:c584131da2ac1948aa3e66468a4424b6aea2f33acba7cec0b631bdb56254c4fe 60Digest: sha256:c584131da2ac1948aa3e66468a4424b6aea2f33acba7cec0b631bdb56254c4fe
61Status: Downloaded newer image for debian:wheezy 61Status: Downloaded newer image for debian:wheezy
62``` 62```
63
64Docker re-uses layers already downloaded. In other words if you have images based on Alpine or some Ubuntu version for example, those can share disk space.
65
66### Start a container
67A container is an instance created from an image, that can be run and that keeps running until its main process exits. Or until the user stops the container.
68
69The simplest way to start a container from image is ``docker run``. It also pulls the image for you if it is not locally available. For more advanced use, refer to ``docker create``.
70
71Stopped containers are not destroyed, unless you specify ``--rm``. To view all created, running and stopped containers, enter:
72```bash
73$ docker ps -a
74```
75
76Some containers may be designed or configured to be restarted, others are not. Also remember both network ports and volumes of a container are created on start, and not editable later.
77
78### Access a running container
79A running container is accessible using ``docker exec``, or ``docker copy``. You can use ``exec`` to start a root shell in the Shaarli container:
80```bash
81$ docker exec -ti <container-name-or-id> bash
82```
83Note the names and ID's of containers are listed in ``docker ps``. You can even type only one or two letters of the ID, given they are unique.
84
85Access can also be through one or more network ports, or disk volumes. Both are specified on and fixed on ``docker create`` or ``run``.
86
87You can view the console output of the main container process too:
88```bash
89$ docker logs -f <container-name-or-id>
90```
91
92### Docker disk use
93Trying out different images can fill some gigabytes of disk quickly. Besides images, the docker volumes usually take up most disk space.
94
95If you care only about trying out docker and not about what is running or saved, the following commands should help you out quickly if you run low on disk space:
96
97```bash
98$ docker rmi -f $(docker images -aq) # remove or mark all images for disposal
99$ docker volume rm $(docker volume ls -q) # remove all volumes
100```
101
102### Systemd config
103Systemd is the process manager of choice on Debian-based distributions. Once you have a ``docker`` service installed, you can use the following steps to set up Shaarli to run on system start.
104
105```bash
106systemctl enable /etc/systemd/system/docker.shaarli.service
107systemctl start docker.shaarli
108systemctl status docker.*
109journalctl -f # inspect system log if needed
110```
111
112You will need sudo or a root terminal to perform some or all of the steps above. Here are the contents for the service file:
113```
114[Unit]
115Description=Shaarli Bookmark Manager Container
116After=docker.service
117Requires=docker.service
118
119
120[Service]
121Restart=always
122
123# Put any environment you want in an included file, like $host- or $domainname in this example
124EnvironmentFile=/etc/sysconfig/box-environment
125
126# It's just an example..
127ExecStart=/usr/bin/docker run \
128 -p 28010:80 \
129 --name ${hostname}-shaarli \
130 --hostname shaarli.${domainname} \
131 -v /srv/docker-volumes-local/shaarli-data:/var/www/shaarli/data:rw \
132 -v /etc/localtime:/etc/localtime:ro \
133 shaarli/shaarli:latest
134
135ExecStop=/usr/bin/docker rm -f ${hostname}-shaarli
136
137
138[Install]
139WantedBy=multi-user.target
140```
diff --git a/doc/md/index.md b/doc/md/index.md
index 24ada6c7..2b7d0f00 100644
--- a/doc/md/index.md
+++ b/doc/md/index.md
@@ -22,6 +22,17 @@ It runs the latest development version of Shaarli and is updated/reset daily.
22 22
23Login: `demo`; Password: `demo` 23Login: `demo`; Password: `demo`
24 24
25Docker users can start a personal instance from an [autobuild image](https://hub.docker.com/r/shaarli/shaarli/). For example to start a temporary Shaarli at ``localhost:8000``, and keep session data (config, storage):
26```
27MY_SHAARLI_VOLUME=$(cd /path/to/shaarli/data/ && pwd -P)
28docker run -ti --rm \
29 -p 8000:80 \
30 -v $MY_SHAARLI_VOLUME:/var/www/shaarli/data \
31 shaarli/shaarli
32```
33
34A brief guide on getting starting using docker is given in [Docker 101](docker/docker-101).
35To learn more about user data and how to keep it across versions, please see [Upgrade and Migration](Upgrade-and-migration) documentation.
25 36
26## Features 37## Features
27 38
diff --git a/index.php b/index.php
index 43aab303..4068a828 100644
--- a/index.php
+++ b/index.php
@@ -88,7 +88,7 @@ try {
88 exit; 88 exit;
89} 89}
90 90
91define('shaarli_version', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE)); 91define('SHAARLI_VERSION', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE));
92 92
93// Force cookie path (but do not change lifetime) 93// Force cookie path (but do not change lifetime)
94$cookie = session_get_cookie_params(); 94$cookie = session_get_cookie_params();
@@ -840,7 +840,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
840 } 840 }
841 841
842 $data = array( 842 $data = array(
843 'search_tags' => implode(' ', $filteringTags), 843 'search_tags' => implode(' ', escape($filteringTags)),
844 'tags' => $tagList, 844 'tags' => $tagList,
845 ); 845 );
846 $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn())); 846 $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn()));
@@ -870,7 +870,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
870 } 870 }
871 871
872 $data = [ 872 $data = [
873 'search_tags' => implode(' ', $filteringTags), 873 'search_tags' => implode(' ', escape($filteringTags)),
874 'tags' => $tags, 874 'tags' => $tags,
875 ]; 875 ];
876 $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => isLoggedIn()]); 876 $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => isLoggedIn()]);
diff --git a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
index 5fc1d1e8..4961aa2c 100644
--- a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
+++ b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
@@ -132,8 +132,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
132 public function testImportInternetExplorerEncoding() 132 public function testImportInternetExplorerEncoding()
133 { 133 {
134 $files = file2array('internet_explorer_encoding.htm'); 134 $files = file2array('internet_explorer_encoding.htm');
135 $this->assertEquals( 135 $this->assertStringMatchesFormat(
136 'File internet_explorer_encoding.htm (356 bytes) was successfully processed:' 136 'File internet_explorer_encoding.htm (356 bytes) was successfully processed in %d seconds:'
137 .' 1 links imported, 0 links overwritten, 0 links skipped.', 137 .' 1 links imported, 0 links overwritten, 0 links skipped.',
138 NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) 138 NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
139 ); 139 );
@@ -161,8 +161,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
161 public function testImportNested() 161 public function testImportNested()
162 { 162 {
163 $files = file2array('netscape_nested.htm'); 163 $files = file2array('netscape_nested.htm');
164 $this->assertEquals( 164 $this->assertStringMatchesFormat(
165 'File netscape_nested.htm (1337 bytes) was successfully processed:' 165 'File netscape_nested.htm (1337 bytes) was successfully processed in %d seconds:'
166 .' 8 links imported, 0 links overwritten, 0 links skipped.', 166 .' 8 links imported, 0 links overwritten, 0 links skipped.',
167 NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) 167 NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
168 ); 168 );
@@ -283,8 +283,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
283 public function testImportDefaultPrivacyNoPost() 283 public function testImportDefaultPrivacyNoPost()
284 { 284 {
285 $files = file2array('netscape_basic.htm'); 285 $files = file2array('netscape_basic.htm');
286 $this->assertEquals( 286 $this->assertStringMatchesFormat(
287 'File netscape_basic.htm (482 bytes) was successfully processed:' 287 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
288 .' 2 links imported, 0 links overwritten, 0 links skipped.', 288 .' 2 links imported, 0 links overwritten, 0 links skipped.',
289 NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) 289 NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
290 ); 290 );
@@ -328,8 +328,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
328 { 328 {
329 $post = array('privacy' => 'default'); 329 $post = array('privacy' => 'default');
330 $files = file2array('netscape_basic.htm'); 330 $files = file2array('netscape_basic.htm');
331 $this->assertEquals( 331 $this->assertStringMatchesFormat(
332 'File netscape_basic.htm (482 bytes) was successfully processed:' 332 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
333 .' 2 links imported, 0 links overwritten, 0 links skipped.', 333 .' 2 links imported, 0 links overwritten, 0 links skipped.',
334 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) 334 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
335 ); 335 );
@@ -372,8 +372,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
372 { 372 {
373 $post = array('privacy' => 'public'); 373 $post = array('privacy' => 'public');
374 $files = file2array('netscape_basic.htm'); 374 $files = file2array('netscape_basic.htm');
375 $this->assertEquals( 375 $this->assertStringMatchesFormat(
376 'File netscape_basic.htm (482 bytes) was successfully processed:' 376 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
377 .' 2 links imported, 0 links overwritten, 0 links skipped.', 377 .' 2 links imported, 0 links overwritten, 0 links skipped.',
378 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) 378 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
379 ); 379 );
@@ -396,8 +396,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
396 { 396 {
397 $post = array('privacy' => 'private'); 397 $post = array('privacy' => 'private');
398 $files = file2array('netscape_basic.htm'); 398 $files = file2array('netscape_basic.htm');
399 $this->assertEquals( 399 $this->assertStringMatchesFormat(
400 'File netscape_basic.htm (482 bytes) was successfully processed:' 400 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
401 .' 2 links imported, 0 links overwritten, 0 links skipped.', 401 .' 2 links imported, 0 links overwritten, 0 links skipped.',
402 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) 402 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
403 ); 403 );
@@ -422,8 +422,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
422 422
423 // import links as private 423 // import links as private
424 $post = array('privacy' => 'private'); 424 $post = array('privacy' => 'private');
425 $this->assertEquals( 425 $this->assertStringMatchesFormat(
426 'File netscape_basic.htm (482 bytes) was successfully processed:' 426 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
427 .' 2 links imported, 0 links overwritten, 0 links skipped.', 427 .' 2 links imported, 0 links overwritten, 0 links skipped.',
428 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) 428 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
429 ); 429 );
@@ -442,8 +442,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
442 'privacy' => 'public', 442 'privacy' => 'public',
443 'overwrite' => 'true' 443 'overwrite' => 'true'
444 ); 444 );
445 $this->assertEquals( 445 $this->assertStringMatchesFormat(
446 'File netscape_basic.htm (482 bytes) was successfully processed:' 446 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
447 .' 2 links imported, 2 links overwritten, 0 links skipped.', 447 .' 2 links imported, 2 links overwritten, 0 links skipped.',
448 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) 448 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
449 ); 449 );
@@ -468,8 +468,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
468 468
469 // import links as public 469 // import links as public
470 $post = array('privacy' => 'public'); 470 $post = array('privacy' => 'public');
471 $this->assertEquals( 471 $this->assertStringMatchesFormat(
472 'File netscape_basic.htm (482 bytes) was successfully processed:' 472 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
473 .' 2 links imported, 0 links overwritten, 0 links skipped.', 473 .' 2 links imported, 0 links overwritten, 0 links skipped.',
474 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) 474 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
475 ); 475 );
@@ -489,8 +489,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
489 'privacy' => 'private', 489 'privacy' => 'private',
490 'overwrite' => 'true' 490 'overwrite' => 'true'
491 ); 491 );
492 $this->assertEquals( 492 $this->assertStringMatchesFormat(
493 'File netscape_basic.htm (482 bytes) was successfully processed:' 493 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
494 .' 2 links imported, 2 links overwritten, 0 links skipped.', 494 .' 2 links imported, 2 links overwritten, 0 links skipped.',
495 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) 495 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
496 ); 496 );
@@ -513,8 +513,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
513 { 513 {
514 $post = array('privacy' => 'public'); 514 $post = array('privacy' => 'public');
515 $files = file2array('netscape_basic.htm'); 515 $files = file2array('netscape_basic.htm');
516 $this->assertEquals( 516 $this->assertStringMatchesFormat(
517 'File netscape_basic.htm (482 bytes) was successfully processed:' 517 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
518 .' 2 links imported, 0 links overwritten, 0 links skipped.', 518 .' 2 links imported, 0 links overwritten, 0 links skipped.',
519 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) 519 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
520 ); 520 );
@@ -523,8 +523,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
523 523
524 // re-import as private, DO NOT enable overwriting 524 // re-import as private, DO NOT enable overwriting
525 $post = array('privacy' => 'private'); 525 $post = array('privacy' => 'private');
526 $this->assertEquals( 526 $this->assertStringMatchesFormat(
527 'File netscape_basic.htm (482 bytes) was successfully processed:' 527 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
528 .' 0 links imported, 0 links overwritten, 2 links skipped.', 528 .' 0 links imported, 0 links overwritten, 2 links skipped.',
529 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) 529 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
530 ); 530 );
@@ -542,8 +542,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
542 'default_tags' => 'tag1,tag2 tag3' 542 'default_tags' => 'tag1,tag2 tag3'
543 ); 543 );
544 $files = file2array('netscape_basic.htm'); 544 $files = file2array('netscape_basic.htm');
545 $this->assertEquals( 545 $this->assertStringMatchesFormat(
546 'File netscape_basic.htm (482 bytes) was successfully processed:' 546 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
547 .' 2 links imported, 0 links overwritten, 0 links skipped.', 547 .' 2 links imported, 0 links overwritten, 0 links skipped.',
548 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) 548 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
549 ); 549 );
@@ -569,8 +569,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
569 'default_tags' => 'tag1&,tag2 "tag3"' 569 'default_tags' => 'tag1&,tag2 "tag3"'
570 ); 570 );
571 $files = file2array('netscape_basic.htm'); 571 $files = file2array('netscape_basic.htm');
572 $this->assertEquals( 572 $this->assertStringMatchesFormat(
573 'File netscape_basic.htm (482 bytes) was successfully processed:' 573 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
574 .' 2 links imported, 0 links overwritten, 0 links skipped.', 574 .' 2 links imported, 0 links overwritten, 0 links skipped.',
575 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) 575 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
576 ); 576 );
@@ -594,8 +594,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
594 public function testImportSameDate() 594 public function testImportSameDate()
595 { 595 {
596 $files = file2array('same_date.htm'); 596 $files = file2array('same_date.htm');
597 $this->assertEquals( 597 $this->assertStringMatchesFormat(
598 'File same_date.htm (453 bytes) was successfully processed:' 598 'File same_date.htm (453 bytes) was successfully processed in %d seconds:'
599 .' 3 links imported, 0 links overwritten, 0 links skipped.', 599 .' 3 links imported, 0 links overwritten, 0 links skipped.',
600 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf, $this->history) 600 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf, $this->history)
601 ); 601 );
@@ -622,24 +622,19 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
622 'overwrite' => 'true', 622 'overwrite' => 'true',
623 ]; 623 ];
624 $files = file2array('netscape_basic.htm'); 624 $files = file2array('netscape_basic.htm');
625 $nbLinks = 2;
626 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); 625 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history);
627 $history = $this->history->getHistory(); 626 $history = $this->history->getHistory();
628 $this->assertEquals($nbLinks, count($history)); 627 $this->assertEquals(1, count($history));
629 foreach ($history as $value) { 628 $this->assertEquals(History::IMPORT, $history[0]['event']);
630 $this->assertEquals(History::CREATED, $value['event']); 629 $this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']);
631 $this->assertTrue(new DateTime('-5 seconds') < $value['datetime']);
632 $this->assertTrue(is_int($value['id']));
633 }
634 630
635 // re-import as private, enable overwriting 631 // re-import as private, enable overwriting
636 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); 632 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history);
637 $history = $this->history->getHistory(); 633 $history = $this->history->getHistory();
638 $this->assertEquals($nbLinks * 2, count($history)); 634 $this->assertEquals(2, count($history));
639 for ($i = 0 ; $i < $nbLinks ; $i++) { 635 $this->assertEquals(History::IMPORT, $history[0]['event']);
640 $this->assertEquals(History::UPDATED, $history[$i]['event']); 636 $this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']);
641 $this->assertTrue(new DateTime('-5 seconds') < $history[$i]['datetime']); 637 $this->assertEquals(History::IMPORT, $history[1]['event']);
642 $this->assertTrue(is_int($history[$i]['id'])); 638 $this->assertTrue(new DateTime('-5 seconds') < $history[1]['datetime']);
643 }
644 } 639 }
645} 640}
diff --git a/tpl/default/includes.html b/tpl/default/includes.html
index 0350ef66..80c08333 100644
--- a/tpl/default/includes.html
+++ b/tpl/default/includes.html
@@ -5,16 +5,16 @@
5<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" /> 5<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" />
6<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" /> 6<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" />
7<link href="img/favicon.png" rel="shortcut icon" type="image/png" /> 7<link href="img/favicon.png" rel="shortcut icon" type="image/png" />
8<link type="text/css" rel="stylesheet" href="css/pure.min.css" /> 8<link type="text/css" rel="stylesheet" href="css/pure.min.css?v={$version_hash}" />
9<link type="text/css" rel="stylesheet" href="css/grids-responsive.min.css"> 9<link type="text/css" rel="stylesheet" href="css/grids-responsive.min.css?v={$version_hash}">
10<link type="text/css" rel="stylesheet" href="css/pure-extras.css"> 10<link type="text/css" rel="stylesheet" href="css/pure-extras.css?v={$version_hash}">
11<link type="text/css" rel="stylesheet" href="css/font-awesome.min.css" /> 11<link type="text/css" rel="stylesheet" href="css/font-awesome.min.css?v={$version_hash}" />
12<link type="text/css" rel="stylesheet" href="inc/awesomplete.css#" /> 12<link type="text/css" rel="stylesheet" href="inc/awesomplete.css?v={$version_hash}#" />
13<link type="text/css" rel="stylesheet" href="css/shaarli.css" /> 13<link type="text/css" rel="stylesheet" href="css/shaarli.css?v={$version_hash}" />
14{if="is_file('data/user.css')"} 14{if="is_file('data/user.css')"}
15 <link type="text/css" rel="stylesheet" href="data/user.css#" /> 15 <link type="text/css" rel="stylesheet" href="data/user.css#" />
16{/if} 16{/if}
17{loop="$plugins_includes.css_files"} 17{loop="$plugins_includes.css_files"}
18 <link type="text/css" rel="stylesheet" href="{$value}#"/> 18 <link type="text/css" rel="stylesheet" href="{$value}?v={$version_hash}#"/>
19{/loop} 19{/loop}
20<link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle}"/> \ No newline at end of file 20<link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle}"/> \ No newline at end of file
diff --git a/tpl/default/js/shaarli.js b/tpl/default/js/shaarli.js
index 1c66ebbd..55656f80 100644
--- a/tpl/default/js/shaarli.js
+++ b/tpl/default/js/shaarli.js
@@ -275,8 +275,14 @@ window.onload = function () {
275 }; 275 };
276 function init () { 276 function init () {
277 function resize () { 277 function resize () {
278 /* Fix jumpy resizing: https://stackoverflow.com/a/18262927/1484919 */
279 var scrollTop = window.pageYOffset ||
280 (document.documentElement || document.body.parentNode || document.body).scrollTop;
281
278 description.style.height = 'auto'; 282 description.style.height = 'auto';
279 description.style.height = description.scrollHeight+10+'px'; 283 description.style.height = description.scrollHeight+10+'px';
284
285 window.scrollTo(0, scrollTop);
280 } 286 }
281 /* 0-timeout to get the already changed text */ 287 /* 0-timeout to get the already changed text */
282 function delayedResize () { 288 function delayedResize () {
diff --git a/tpl/default/page.footer.html b/tpl/default/page.footer.html
index 94f771a2..54b16e8a 100644
--- a/tpl/default/page.footer.html
+++ b/tpl/default/page.footer.html
@@ -27,6 +27,6 @@
27 <script src="{$value}#"></script> 27 <script src="{$value}#"></script>
28{/loop} 28{/loop}
29 29
30<script src="js/shaarli.js"></script> 30<script src="js/shaarli.js?v={$version_hash}"></script>
31<script src="inc/awesomplete.js#"></script> 31<script src="inc/awesomplete.js?v={$version_hash}#"></script>
32<script src="inc/awesomplete-multiple-tags.js#"></script> 32<script src="inc/awesomplete-multiple-tags.js?v={$version_hash}#"></script>
diff --git a/tpl/default/tag.cloud.html b/tpl/default/tag.cloud.html
index 96b357a3..68335c70 100644
--- a/tpl/default/tag.cloud.html
+++ b/tpl/default/tag.cloud.html
@@ -26,7 +26,7 @@
26 <input type="hidden" name="do" value="tagcloud"> 26 <input type="hidden" name="do" value="tagcloud">
27 <input type="text" name="searchtags" placeholder="{'Filter by tag'|t}" 27 <input type="text" name="searchtags" placeholder="{'Filter by tag'|t}"
28 {if="!empty($search_tags)"} 28 {if="!empty($search_tags)"}
29 value="{$search_tags}" 29 value="{$search_tags}"
30 {/if} 30 {/if}
31 autocomplete="off" data-multiple data-autofirst data-minChars="1" 31 autocomplete="off" data-multiple data-autofirst data-minChars="1"
32 data-list="{loop="$tags"}{$key}, {/loop}" 32 data-list="{loop="$tags"}{$key}, {/loop}"