first commit

This commit is contained in:
Bruno Charest 2026-01-13 15:35:11 -05:00
commit b00950d5e8
186 changed files with 25169 additions and 0 deletions

View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_size = 2
indent_style = space
trim_trailing_whitespace = true

20
example_Shaarli-Material/.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# Sublime Text files.
*.sublime-project
*.sublime-workspace
# Removes some useless build files.
material/dist/*
#!material/dist
material/opengraph.png
# Removes the user defined template.
material/extra.html
# Removes node modules.
node_modules
# Removes the HTML version of README.
README.html
# Removes VS Code config that isn't useful for others.
.vscode/settings.json

View File

@ -0,0 +1,206 @@
# Change Log
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## unreleased
### Added
- New pager on link list.
### Removed
- `v` prefix to the Shaarli version number in the footer.
### Fixed
- Wrong image path for errors.
## [v0.14.0](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.14.0) - 2024-12-24
### Added
- Support for Shaarli v0.14.0.
### Changed
- Update links on the Tools page.
- Move from ESLint standard to neostandard for linting JS.
### Removed
- Babel for transpilation.
- Core-JS for old-browser support.
## [v0.12.2](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.12.2) - 2024-12-23
### Added
- Support for Shaarli v0.12.2 and v0.13.0.
- Support for new core plugin ReadItLater.
### Removed
- Support for Internet Explorer.
## [v0.12.1](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.12.1) - 2021-02-10
### Added
- Support for Shaarli v0.12.1.
- Async metadata loading when adding a new link.
- Working with URL rewriting disabled.
- Highlight searched text in links and tags.
- Server page which shows you details about your server config.
- Weekly and Monthly Shaarli, in addition to the Daily.
- Support for customized tag separator.
- ESLint to ensure better JS code quality.
### Changed
- Replaced the QR Code JS library from [qrcodejs](https://davidshimjs.github.io/qrcodejs/) to [qrcode](https://github.com/soldair/node-qrcode).
- Improved overall localization.
- Build system from Gulp to Rollup.
- Redesign of error/warning/success notifications (after saving configuration for example).
### Fixed
- Page unique identification.
### Removed
- `config.MATERIAL_DATE_FROMNOW` option, in order to greatly reduce JS dependencies size.
- `moment.js` JS dependency.
- Dropped support for IE9 and IE10.
## [v0.12.0](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.12.0) - 2020-11-28
### Added
- Support for Shaarli v0.12.0.
- Support for new page URLs.
- `lang` attribute in `<html>` tag.
### Changed
- Jump from jQuery v1.12.4 to v3.4.1. It lowers browser support to IE9+.
- Other NPM dependencies updated.
### Fixed
- An issue where the login form couldn't be displayed.
- An issue where images where overflowing their container when markdown is enabled.
### Removed
- Firefox Social API support.
## [v0.11.0](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.11.0) - 2020-04-08
### Added
- Batch mode: option to select all links on the page.
- Batch mode: visibility settings.
- NPM dependencies updated.
## [v0.10.4](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.10.4) - 2019-04-16
### Added
- Support of `<del>` tag in Markdown.
### Fixed
- Unformatted date is now displayed instead of "Invalid date" in some cases when date format is not recognized.
### Changed
- Slight design refresh.
- Improved search overlay usability.
- Optimized fonts loading by making text readable while loading.
## [v0.10.3](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.10.3) - 2019-03-29
Be careful, one important change comes with this release: the `build` folder is not anymore part of the code repository. So if you use to update the theme with `git pull`, you now need to do
```bash
$ git pull
$ npm install
$ gulp build
```
This will install build tools and process files. I'll now attach ready-to-use built theme to each release, similarly to what is done for Shaarli.
**Known issue:** the build process outputs scary errors due to new version of *uncss* not understanding properly some links containing RainTPL markup. This doesn't prevent it from working properly so you can ignore.
### Changed
- Processed JS and CSS files are not anymore in code repository.
- Reorganisation of directories in order to keep `/material` directory clean.
- Updated dependencies.
- Updated Gulp to v4.
- Refreshed linklist page design.
### Removed
- Bower. Front-end dependencies are now also retrieved with NPM.
## [v0.10.2-patch.3](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.10.2-patch.3) - 2019-01-12
### Fixed
- Laggy popup animation.
- Missing feedback when deleting tag.
- Error on permalink page due to inexistant variables with Shaarli v0.10.2.
### Removed
- Useless resources.
## [v0.10.2-patch.2](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.10.2-patch.2) - 2018-11-06
### Fixed
- HTML class added in the wrong place.
- RainTPL escaping in linklist page.
- Open Graph description containing html tags.
## [v0.10.2](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.10.2) - 2018-11-04
### Added
- Supports Shaarli v0.10.2
- Thumbnail update page
- Keyboard shortcut "S" displays search overlay
### Fixed
- Thumbnails on link list and daily pages
- Daily previous link not disabled properly when on oldest day
## [v0.9.5](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.9.5) - 2018-02-08
### Changed
- Optimizes bookmarklet popup size and enables scrollbars
### Removed
- Redirector setting as it was removed from Shaarli core
### Fixed
- Typo in the bottom link counter.
## [v0.9.3](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.9.3) - 2018-01-08
### Added
- Supports Shaarli v0.9.3 with an important security fix
- New design for The Daily Shaarli
### Fixed
- Now properly applies MATERIAL_COLOR variable
## [v0.9.2](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.9.2) - 2017-10-23
### Added
- Unique version hash appended to JS and CSS files to avoid cache issue after an update
- *Remember me* setting is taken into account
### Changed
- Modified [referrer policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) to `cross-origin` instead of `origin-when-crossorigin`
### Fixed
- Links in footer
## [v0.9.1](https://github.com/kalvn/Shaarli-Material/releases/tag/v0.9.1) - 2017-10-08
### Added
- Tag list view
- Creation date when editing a link
- Filter in the toolbar to display only untagged links
- Batch link selection and deletion
- Icons from iconfont in toolbar, Tools page, floating add button, etc.
- 3rd party plugins and application on Tools page
- Icon for notes in link list
### Changed
- Plugin error design is better integrated
### Removed
- config.MATERIAL_COLOR, config.MATERIAL_COLOR_FOCUS and config.MATERIAL_COLOR_ACTIVE customization options. The first one is still present but only acts on a meta tag in `<head>`. They are replaced by `data/user.css` with an example in `user.example.css`.
### Fixed
- Auto-complete plugin for search field is now only initialized once

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 kalvn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,121 @@
# Shaarli Material Theme
Shaarli Material is a theme for [Shaarli](https://github.com/shaarli/Shaarli), the famous personal, minimalist, super-fast, database free, bookmarking service.
## Screenshots
![Shaarli Material Screenshot Home](https://raw.githubusercontent.com/kalvn/Shaarli-Material/master/screenshots/showcase.png)
[More screenshots](https://github.com/kalvn/Shaarli-Material/tree/master/screenshots).
## Compatibility
Shaarli Material follows the exact same versions numbers than Shaarli. It means that if you install Shaarli vX.Y.Z, you must use Shaarli Material vX.Y.Z.
Shaarli Material was tested and validated with **Shaarli 0.14.0**.
## Download
To download this theme, [visit this page](https://github.com/kalvn/Shaarli-Material/releases) and choose the most recent version matching the version of your Shaarli installation. Both use the same notation.
Versions suffixed by `-patch.x` include some bugfix so take those preferentially if they exist for the version that fits your Shaarli installation.
If you install an older version, please read the README.md file you'll find in the root folder rather than this one.
## Installation
Download the `material` folder into the `tpl` directory of your Shaarli installation next to the `default` directory.
Access your Shaarli and finish the setup process. Then, go into menu **Tools > Configure your Shaarli** and change the setting **Theme** to **Material**.
You can now enjoy your new Material theme.
## Customization
### Configuration
You can customize a few things using the `data/config.json.php` file of your Shaarli installation. If the file doesn't exist, just create it. Be careful to respect the JSON format notation (end lines with a comma except for the last item, just before the closing curly brace), otherwise you'll get errors.
Here are parameters you can set.
- `config.MATERIAL_PHP_DATE_PATTERN` (optional): Customizes the date format. Check this to know what to write: https://php.net/manual/function.strftime.php (ex: `"%d/%m/%Y"` will output for example '30/05/2015').
- `config.MATERIAL_NO_QRCODE` (optional): Removes the QR code control of the theme. To completely get rid of QR Codes, you of course need to disable the qrcode plugin as well.
- `config.MATERIAL_COLOR` (optional): Customizes the theme's colors. It's used for example on Android for notification bar. It will generate `<meta name="theme-color" content="YOURCOLORHERE">`.
Here is an example of what you can configure (in real life, there will be other parameters in the file, just add those to the different categories):
```json
{
"resource": {
"raintpl_tpl": "tpl\/material\/"
},
"config": {
"MATERIAL_PHP_DATE_PATTERN": "%d\/%m\/%Y %H:%M:%S",
"MATERIAL_NO_QRCODE": true,
"MATERIAL_COLOR": "#607D8B"
}
}
```
### Custom Open Graph image
[Open Graph](https://ogp.me/#metadata) is a protocol that enables web site developers to attach rich metadata that will be used when sharing a link on social medias for example.
Shaarli Material supports adding a custom image that will be used by default when no image is attached to a shared link.
To add yours, create a PNG image and save it as `tpl/material/opengraph.png`.
### Custom CSS
You can add your own CSS rules in file `data/user.css`. You'll find an example that shows how to change the whole theme color in `user.example.css`.
## Add custom resources
If you want to add your custom scripts or styles (for example analytics script), you must create a new template named `extra.html` in the *material* folder.
Then, anything you add in this file will be included at the end of the `<head>` tag.
This file is NOT commited on the repository, which allows you to update the theme without overriding this file.
## Plugins
As from Shaarli v0.6.0, you can install plugins to enrich your experience.
Most of them should work properly, although it's up to the plugin developer to ensure the code is as minimal as possible to integrates well in custom themes.
I tested all plugins available with Shaarli 0.6.0 and they all work well even though the display is a bit weird for some of them. I will keep monitoring the behavior of popular plugins in the future.
## Libraries used
This theme uses a few JavaScript libraries.
- [jQuery](http://jquery.com/)
- [Bootstrap](http://getbootstrap.com/)
- [awesomplete](http://leaverou.github.io/awesomplete/)
- [blazy](http://dinbror.dk/blazy/)
- [Sortable](http://rubaxa.github.io/Sortable/)
- [Salvattore](https://salvattore.js.org/)
- [Autosize](https://github.com/jackmoore/autosize/)
- [node-qrcode](https://github.com/soldair/node-qrcode)
## Demo
A read-only demo is available on my personal Shaarli : [https://links.kalvn.net](https://links.kalvn.net)
## Develop and debug
To tweak this theme, you'll need to install dependencies and to build JavaScript and CSS libraries. To do this, install [Node.js and NPM](https://nodejs.org) and run this from the root folder:
```bash
$ npm install
$ npm run dev
```
## Build for production
```bash
$ npm install
$ npm run build
```
------------------------------------------------------------------------------
You can download Shaarli via the Github project page: https://github.com/shaarli/Shaarli
Original project page: http://sebsauvage.net/wiki/doku.php?id=php:shaarli

View File

@ -0,0 +1,18 @@
import neostandard from 'neostandard';
export default [
...neostandard({
semi: true,
ts: false
}),
{
rules: {
'no-undef': 'off',
'n/no-callback-literal': 'off'
},
ignores: [
'node_modules/*',
'material/dist/*'
]
}
];

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="404"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div class="container">
<div class="text-center">
<img src="{$asset_path}/dist/img/sad_star.png#" alt="Nothing found">
</div>
<div class="nothing-found">{'Sorry, nothing to see here.'|t} That's a <strong>404</strong>.</div>
<p class="text-center">{$error_message}</p>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,62 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="addlink"}
{include="includes"}
</head>
<body onload="document.addform.post.focus();">
{include="page.header"}
<div id="headerform" class="page-add container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form method="GET" action="{$base_path}/admin/shaare" name="addform" class="form-add card">
<div class="card-title">{"Shaare a new link"|t}</div>
<div class="card-body">
<div class="form-entry">
<label for="post">{'URL or leave empty to post a note'|t}</label><br/>
<input type="text" name="post" id="post" class="autofocus" placeholder="Type a url...">
</div>
</div>
<div class="card-footer">
<button type="submit" class="button-raised button-primary pull-right">{'Add link'|t}</button>
<div class="clearfix"></div>
</div>
</form>
</div>
<div class="col-md-6 col-md-offset-3">
<button type="button" class="button pull-right button-batch-addform"><i class="mdi mdi-playlist-plus" aria-hidden="true"></i>&nbsp;{'BULK CREATION'|t}</button>
<div class="clearfix"></div>
<form class="card batch-addform hidden" method="POST" action="{$base_path}/admin/shaare-batch" name="batch-addform">
<div class="card-header">{"Shaare multiple new links"|t}</div>
<div class="card-body">
<div class="form-entry">
<label for="urls">{'Add one URL per line to create multiple bookmarks.'|t}</label>
<textarea name="urls" id="urls" rows="4"></textarea>
</div>
<div class="form-entry">
<label for="tags">{'Tags'|t}</label><br/>
<input type="text" name="tags" id="tags" class="lf_input" data-list="{loop="$tags"}{$key}, {/loop}" data-multiple data-autofirst autocomplete="off">
</div>
<input type="hidden" name="private" value="0">
<div class="form-entry">
<input type="checkbox" name="private" {if="$default_private_links"} checked="checked"{/if} id="lf_private" class="filled-in"/>
<label for="lf_private">{'Private'|t}</label>
</div>
</div>
<div class="card-footer">
<input type="hidden" name="token" value="{$token}">
<button type="submit" class="button-raised button-primary pull-right">{'Add links'|t}</button>
<div class="clearfix"></div>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="changepassword"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div class="container page-changepassword">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form method="POST" action="#" name="changepasswordform" id="changepasswordform" class="card">
<div class="card-title">{'Change password'|t}</div>
<div class="card-body">
<div class="form-entry">
<label for="oldpassword">{'Current password'|t}</label>
<input type="password" name="oldpassword" id="oldpassword" class="autofocus">
</div>
<div class="form-entry">
<label for="setpassword">{'New password'|t}</label>
<input type="password" name="setpassword" id="setpassword">
</div>
<input type="hidden" name="token" value="{$token}">
</div>
<div class="card-footer">
<button type="submit" name="Save" class="button-raised button-primary pull-right">{'Change'|t}</button>
<div class="clearfix"></div>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="changetag"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div class="container page-changetag">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form method="POST" action="" name="changetag" id="changetag" class="card">
<div class="card-title">{"Manage tags"|t}</div>
<div class="card-body">
<input type="hidden" name="token" value="{$token}">
<p>All modifications are case sensitive.</p>
<div class="form-entry">
<label for="fromtag">Modify this tag&#8230;</label>
<input type="text" name="fromtag" id="fromtag" value="{$fromtag}" class="autofocus"
autocomplete="off" data-multiple data-minChars="1"
data-list="{loop="$tags"}{$key}, {/loop}"/>
</div>
<div class="form-entry">
<label for="totag">&#8230;into this tag</label>
<input type="text" name="totag" id="totag">
</div>
<p>{'You can also edit tags in the'|t} <a href="{$base_path}/tags/list?sort=usage">{'tag list'|t}</a>.</p>
</div>
<div class="card-footer">
<button type="submit" id="button-delete" name="deletetag" class="button-raised button-alert pull-right" value="Delete tag">{'Delete tag'|t}</button>
<button type="submit" name="renametag" class="button-raised pull-right" value="Rename tag">{'Rename tag'|t}</button>
<div class="clearfix"></div>
</div>
</form>
</div>
<div class="col-md-6 col-md-offset-3">
<form method="POST" action="{$base_path}/admin/tags/change-separator" name="changeseparator" id="changeseparator" class="card">
<div class="card-header">{"Change tags separator"|t}</div>
<div class="card-body">
<p>
{'Your current tag separator is'|t} <code>{$tags_separator}</code>{if="!empty($tags_separator_desc)"} ({$tags_separator_desc}){/if}.
</p>
<div class="form-entry">
<label for="separator">{'New separator'|t}</label>
<input type="text" name="separator" id="separator" autocomplete="off"/>
<input type="hidden" name="token" value="{$token}">
</div>
</div>
<div class="card-footer">
<p>
{'Note that hashtags won\'t fully work with a non-whitespace separator.'|t}
</p>
<button type="submit" name="saveseparator" class="button-raised button-primary pull-right">{'Save'|t}</button>
<div class="clearfix"></div>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,242 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="configure"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div class="container page-configure">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<form method="POST" action="#" name="configform" id="configform" class="card">
<input type="hidden" name="token" value="{$token}"/>
<div class="card-title">Configuration</div>
<div class="card-body">
<div class="form-entry">
<label for="title">Shaarli {'title'|t}</label>
<input type="text" name="title" id="title" class="autofocus" size="50" value="{$title}" />
</div>
<div class="form-entry">
<label for="titleLink">{'Home link'|t}</label>
<input type="text" name="titleLink" id="titleLink" size="50" value="{$titleLink}">
<div class="sublabel">{'Default value'|t}: {$base_path}/</div>
</div>
<div class="form-entry">
<label for="theme">{'Theme'|t}</label>
<select name="theme" id="theme">
{loop="$theme_available"}
<option value="{$value}" {if="$value === $theme"}selected{/if}>
{$value|ucfirst}
</option>
{/loop}
</select>
</div>
<div class="form-entry">
<label for="theme">{'Description formatter'|t}</label>
<select name="formatter" id="formatter">
{loop="$formatter_available"}
<option value="{$value}" {if="$value === $formatter"}selected="selected"{/if}>
{$value|ucfirst}
</option>
{/loop}
</select>
</div>
<div class="form-entry">
<label for="language">{'Language'|t}</label>
<select name="language" id="language">
{loop="$languages"}
<option value="{$key}" {if="$key === $language"}selected{/if}>
{$value|ucfirst}
</option>
{/loop}
</select>
</div>
<div class="form-entry">
<label for="continent">{'Timezone'|t}</label>
<div class="row">
<div class="col-sm-6" id="timezone-continent">
<select name="continent" id="continent">
{loop="$continents"}
{if="$key !== 'selected'"}
<option value="{$value}" {if="$continents.selected === $value"}selected{/if}>
{$value}
</option>
{/if}
{/loop}
</select>
</div>
<div class="col-sm-6" id="timezone-city">
<select name="city" id="city">
{loop="$cities"}
{if="$key !== 'selected'"}
<option value="{$value.city}" {if="$cities.selected === $value.city"}selected{/if} data-continent="{$value.continent}">
{$value.city}
</option>
{/if}
{/loop}
</select>
</div>
</div>
<div class="sublabel">{'Continent'|t} &middot; {'City'|t}</div>
</div>
</div>
<div class="list-side-right">
<div class="list-body">
<div class="list-item">
<div class="list-item-content">
<div class="list-item-label">{'Disable session cookie hijacking protection'|t}</div>
<div class="list-item-sublabel">{'Check this if you get disconnected or if your IP address changes often'|t}</div>
</div>
<div class="list-item-side">
<div class="switch">
<label>
<input type="checkbox" name="disablesessionprotection" id="disablesessionprotection" {if="$session_protection_disabled"}checked{/if}/>
<span class="lever"></span>
</label>
</div>
</div>
</div>
<div class="list-item">
<div class="list-item-content">
<div class="list-item-label">{'Private links by default'|t}</div>
<div class="list-item-sublabel">{'All new links are private by default'|t}</div>
</div>
<div class="list-item-side">
<div class="switch">
<label>
<input type="checkbox" name="privateLinkByDefault" id="privateLinkByDefault" {if="$private_links_default"}checked{/if}/>
<span class="lever"></span>
</label>
</div>
</div>
</div>
<div class="list-item">
<div class="list-item-content">
<div class="list-item-label">{'RSS direct links'|t}</div>
<div class="list-item-sublabel">Enabling it will show a permalink in the description, and the feed item will be linked to the absolute URL. Disabling it swaps this behaviour around (permalink in title and link in description).</div>
</div>
<div class="list-item-side">
<div class="switch">
<label>
<input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks" {if="$enable_rss_permalinks"}checked{/if}/>
<span class="lever"></span>
</label>
</div>
</div>
</div>
<div class="list-item">
<div class="list-item-content">
<div class="list-item-label">{'Hide public links'|t}</div>
<div class="list-item-sublabel">{'Do not show any links if the user is not logged in'|t}</div>
</div>
<div class="list-item-side">
<div class="switch">
<label>
<input type="checkbox" name="hidePublicLinks" id="hidePublicLinks" {if="$hide_public_links"}checked{/if}/>
<span class="lever"></span>
</label>
</div>
</div>
</div>
<div class="list-item">
<div class="list-item-content">
<div class="list-item-label">{'Automatically retrieve description for new bookmarks'|t}</div>
<div class="list-item-sublabel">{'Shaarli will try to retrieve the description from meta HTML headers'|t}</div>
</div>
<div class="list-item-side">
<div class="switch">
<label>
<input type="checkbox" name="retrieveDescription" id="retrieveDescription" {if="$retrieve_description"}checked{/if}/>
<span class="lever"></span>
</label>
</div>
</div>
</div>
<div class="list-item">
<div class="list-item-content">
<div class="list-item-label">{'Check updates'|t}</div>
<div class="list-item-sublabel">{'Notify me when a new release is ready'|t}</div>
</div>
<div class="list-item-side">
<div class="switch">
<label>
<input type="checkbox" name="updateCheck" id="updateCheck" {if="$enable_update_check"}checked{/if}/>
<span class="lever"></span>
</label>
</div>
</div>
</div>
<div class="list-item">
<div class="list-item-content">
<div class="list-item-label">{'Enable thumbnails'|t}</div>
<div class="list-item-sublabel">
{if="! $gd_enabled"}
{'You need to enable the extension <code>php-gd</code> to use thumbnails.'|t}
{elseif="$thumbnails_enabled"}
<a href="{$base_path}/admin/thumbnails">{'Synchronize thumbnails'|t}</a>
{/if}
</div>
</div>
<div class="list-item-side">
<select name="enableThumbnails" id="enableThumbnails" class="align">
<option value="all" {if="$thumbnails_mode=='all'"}selected{/if}>
{'All'|t}
</option>
<option value="common" {if="$thumbnails_mode=='common'"}selected{/if}>
{'Only common media hosts'|t}
</option>
<option value="none" {if="$thumbnails_mode=='none'"}selected{/if}>
{'None'|t}
</option>
</select>
</div>
</div>
<div class="list-item">
<div class="list-item-content">
<div class="list-item-label">{'Enable REST API'|t}</div>
<div class="list-item-sublabel">{'Allow third party software to use Shaarli such as mobile application'|t}</div>
</div>
<div class="list-item-side">
<div class="switch">
<label>
<input type="checkbox" name="enableApi" id="enableApi" {if="$api_enabled"}checked{/if}/>
<span class="lever"></span>
</label>
</div>
</div>
</div>
</div>
</div>
<div class="list">
<div class="list-item">
<div class="form-entry">
<label for="apiSecret">{'API secret'|t}</label>
<input type="text" name="apiSecret" id="apiSecret" size="50" value="{$api_secret}" placeholder="Type a random string..." />
</div>
</div>
</div>
<div class="card-footer">
<button type="submit" name="Save" class="button-raised button-primary pull-right">{'Save'|t}</button>
<div class="clearfix"></div>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,116 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="daily"}
{include="includes"}
</head>
<body class="dark-toolbar">
{include="page.header"}
<div class="subheader is-dark">
<div class="container text-center">
<a class="button-inverse button-default{if="array_key_exists('day', $_GET) || (!array_key_exists('week', $_GET) && !array_key_exists('month', $_GET))"} active{/if}" href="{$base_path}/daily?day" title="{'Daily'|t}">{'Daily'|t}</a>
<a class="button-inverse button-default{if="array_key_exists('week', $_GET)"} active{/if}" href="{$base_path}/daily?week" title="{'Weekly'|t}">{'Weekly'|t}</a>
<a class="button-inverse button-default{if="array_key_exists('month', $_GET)"} active{/if}" href="{$base_path}/daily?month" title="{'Monthly'|t}">{'Monthly'|t}</a>
</div>
</div>
<div class="daily">
<div id="plugin_zone_start_picwall" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
<div class="daily-header">
<h1>{$localizedType} Shaarli</h1>
<p class="daily-header-subtitle">{function="t('All links of one :type in a single page.', '', 1, 'shaarli', [':type' => t($type)])"}</p>
<div class="container">
<div class="row">
<div class="col-xs-6">
<a {if="$previousday"}href="{$base_path}/daily?{$type}={$previousday}"{else}href="#"{/if} class="button" {if="!$previousday"}disabled{/if}>
<i class="mdi mdi-arrow-left"></i> {function="t('Previous :type', '', 1, 'shaarli', [':type' => t($type)], true)"}
</a>
</div>
<div class="col-xs-6">
<a {if="$nextday"}href="{$base_path}/daily?{$type}day={$nextday}"{else}href="#"{/if} class="button" {if="!$nextday"}disabled{/if}>
{function="t('Next :type', '', 1, 'shaarli', [':type' => t($type)], true)"} <i class="mdi mdi-arrow-right"></i>
</a>
</div>
</div>
<hr class="darker">
</div>
</div>
<h2 class="daily-title">{$dayDesc}</h2>
<div id="plugin_zone_about_daily" class="plugin_zone">
{loop="$daily_about_plugin"}
{$value}
{/loop}
</div>
{if="$linksToDisplay"}
<div class="daily-grid clearfix" data-columns>
{loop="$linksToDisplay"}
{$link=$value}
<div class="daily-item">
<div class="daily-card">
<a href="{$link.real_url}" class="daily-item-header ripple ripple-primary">
{$link.title}
</a>
{if="$thumbnails_enabled && !empty($link.thumbnail)"}
<div class="daily-item-image" style="background-image: url({$root_path}/{$link.thumbnail}#)"></div>
{/if}
{if="$link.formatedDescription"}
<div class="daily-item-body">
{$link.formatedDescription}
</div>
{/if}
<div class="daily-item-footer clearfix">
{if="!$hide_timestamps || $is_logged_in"}
<a href="{$base_path}/shaare/{$value.shorturl}" class="daily-item-footer-subtitle" title="{'Permalink'|t}">
{if="$type === 'week'"}
{function="strftime('%a %e %b, %H:%M', $link.timestamp)"}
{else}
{if="$type === 'month'"}
{function="strftime('%a %e %b %Y, %H:%M', $link.timestamp)"}
{else}
{function="strftime('%H:%M', $link.timestamp)"}
{/if}
{/if}
</a>
{/if}
{if="$link.tags"}
<div class="daily-item-tags">
{$tags=implode(', ', $link['taglist'])}
{$tags}
</div>
{/if}
<div>
{loop="$link.link_plugin"}
{$value}
{/loop}
</div>
</div>
</div>
</div>
{/loop}
</div>
{else}
<div>No articles on this day.</div>
{/if}
<div id="plugin_zone_end_picwall" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>{$localizedType} - {$title}</title>
<link>{$index_url}</link>
<description>{function="t('All links of one :type in a single page.', '', 1, 'shaarli', [':type' => t($type)])"}</description>
<language>{$language}</language>
<copyright>{$index_url}</copyright>
<generator>Shaarli</generator>
{loop="$days"}
<item>
<title>{$value.date_human} - {$title}</title>
<guid>{$value.absolute_url}</guid>
<link>{$value.absolute_url}</link>
<pubDate>{$value.date_rss}</pubDate>
<description><![CDATA[
{loop="$value.links"}
<h3><a href="{$value.url}">{$value.title}</a></h3>
<small>
{if="!$hide_timestamps"}{$value.created|format_date} &#8212; {/if}
<a href="{$index_url}shaare/{$value.shorturl}">{'Permalink'|t}</a>
{if="$value.tags"} &#8212; {$value.tags}{/if}
<br>
{$value.url}
</small><br>
{if="$value.thumbnail"}<img src="{$index_url}{$value.thumbnail}#" alt="thumbnail" />{/if}<br>
{if="$value.description"}{$value.description}{/if}
<br><hr>
{/loop}
]]></description>
</item>
{/loop}
</channel>
</rss><!-- Cached version of {$page_url} -->

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="editlinkbatch"}
{include="includes"}
</head>
<body class="page-edit-link-batch">
{include="page.header"}
<div id="progress-overlay" class="fullscreen hidden">
<div class="content-fullscreen">
<div class="container">
<div class="mbl row">
<div class="col-md-8 col-md-offset-2">
<div class="is-inverted">
<div class="progress-counter">
<span class="progress-current">0</span> / <span class="progress-total"></span>
</div>
<div class="progress-bar">
<div class="progress-actual"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="text-center">
<button type="submit" name="save_edit_batch" class="button-raised button-primary">{'Save all'|t}</button>
</div>
{loop="$links"}
{$index=$key}
{include="editlink"}
{/loop}
<div class="text-center">
<button type="submit" name="save_edit_batch" class="button-raised button-primary">{'Save all'|t}</button>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,99 @@
{if="empty($batch_mode)"}
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="editlink"}
{include="includes"}
</head>
<body {if="!empty($_GET['source']) && $_GET['source']=='bookmarklet'"}class="from-bookmarklet"{/if}>
{include="page.header"}
{else}
{ignore}Lil hack: when included in a loop in batch mode, `$value` is assigned by RainTPL with template vars.{/ignore}
{function="extract($value) ? '' : ''"}
{/if}
{$asyncLoadClass=$link_is_new && $async_metadata && empty($link.title) ? 'loading-wrapper' : ''}
{if="!isset($index)"}
{$index=""}
{/if}
<div id="editlinkform{$index}" class="editlinkform container page-edit">
<div class="row editlinkform-row">
<div class="col-md-6 col-md-offset-3 editlinkform-col">
<form method="post" name="linkform" class="card" action="{$base_path}/admin/shaare">
{if="isset($link.id)"}
<input type="hidden" name="lf_id" value="{$link.id}">
{/if}
<input type="hidden" name="token" value="{$token}">
<input type="hidden" name="source" value="{$source}">
{if="$http_referer"}
<input type="hidden" name="returnurl" value="{$http_referer}">
{/if}
<div class="card-header">
{if="!$link_is_new"}Edit a link{else}Add a new link{/if}
{if="!$link_is_new"}<span class="card-subheader"> - {'created on'|t} {$link.created|format_date}</span>{/if}
<button type="button" class="button-expand button-header visible-md visible-lg" title="Expand / reduce width"></button>
</div>
<div class="card-body">
<div class="form-entry">
<label for="lf_url{$index}">{'URL'|t}</label><br/>
<input type="text" name="lf_url" id="lf_url{$index}" value="{$link.url}" placeholder="Type a url...">
</div>
<div class="form-entry">
<label for="lf_title{$index}">{'Title'|t}</label><br/>
<div class="{$asyncLoadClass}">
<input type="text" name="lf_title" id="lf_title{$index}" {if="empty($batch_mode) && $link.title==''"}class="autofocus"{/if} value="{$link.title}" placeholder="Title...">
</div>
</div>
<div class="form-entry">
<label for="lf_description{$index}">{'Description'|t}</label><br/>
<div class="{$asyncLoadClass}">
<textarea name="lf_description" id="lf_description{$index}" {if="empty($batch_mode) && $link.description==''"}class="autofocus"{/if} placeholder="Describe the link..." rows="4">{$link.description}</textarea>
</div>
</div>
<div class="form-entry">
<label for="lf_tags{$index}">{'Tags'|t}</label><br/>
<div class="{$asyncLoadClass}">
<input type="text" id="lf_tags{$index}" name="lf_tags" {if="empty($batch_mode)"}class="autofocus"{/if} value="{$link.tags}" class="lf_input"
data-list="{loop="$tags"}{$key}, {/loop}" data-multiple autocomplete="off" />
</div>
</div>
{if="isset($edit_link_plugin)"}
{loop="$edit_link_plugin"}
<div class="form-entry">
{$value}
</div>
{/loop}
{/if}
<div class="form-entry">
<input type="checkbox" class="filled-in" {if="$link.private === true"}checked="checked"{/if} name="lf_private" id="lf_private{$index}"/>
<label for="lf_private{$index}">{'Private'|t}</label>
</div>
{if="$formatter==='markdown'"}
<div class="form-entry">
{'Description will be rendered with'|t}
<a href="http://daringfireball.net/projects/markdown/syntax" title="{'Markdown syntax documentation'|t}">
{'Markdown syntax'|t}
</a>.
</div>
{/if}
</div>
<div class="card-footer">
<button type="submit" name="save_edit" class="button-raised button-primary pull-right">{'Save'|t}</button>
{if="!$link_is_new"}
<a href="{$base_path}/admin/shaare/delete?id={$link.id}&amp;token={$token}"
name="delete_link" class="button-raised button-alert">{'Delete'|t}</a>
{/if}
{if="!empty($batch_mode)"}
<button type="button" name="cancel-batch-link" title="{'Remove this bookmark from batch creation/modification.'|t}" class="button pull-right ripple-primary">{'Cancel'|t}</button>
{/if}
<div class="clearfix"></div>
</div>
</form>
</div>
</div>
</div>
{if="empty($batch_mode)"}
{include="page.footer"}
{/if}
</body>
</html>

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="error"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="pageError" class="container text-center">
<h2>{$message}</h2>
<div>
<img src="{$asset_path}/dist/img/sad_star.png#" alt="">
</div>
{if="!empty($text)"}
<p>{$text}</p>
{/if}
{if="!empty($stacktrace)"}
<pre>
{$stacktrace}
</pre>
{/if}
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE NETSCAPE-Bookmark-file-1>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<!-- This is an automatically generated file.
It will be read and overwritten.
Do Not Edit! -->{ignore}The RainTPL loop is formatted to avoid generating extra newlines{/ignore}
<TITLE>{$pagetitle}</TITLE>
<H1>Shaarli export of {$selection} bookmarks on {$date}</H1>
<DL><p>{loop="links"}
<DT><A HREF="{$value.url}" ADD_DATE="{$value.timestamp}" PRIVATE="{$value.private}" TAGS="{$value.taglist}">{$value.title}</A>{if="$value.description"}{$eol}<DD>{$value.description}{/if}{/loop}
</DL><p>

View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="export"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="toolsdiv" class="container page-export">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form class="card" method="post" action="{$base_path}/admin/export">
<input type="hidden" name="token" value="{$token}">
<h1 class="card-header">{"Export Database"|t}</h1>
<div class="list-side-right">
<div class="list-body">
<div class="list-item no-border">
<div class="list-item-content">
<div class="list-item-label">{'Prepend note permalinks with this Shaarli instance\'s URL'|t}</div>
<div class="list-item-sublabel">{'Useful to import bookmarks in a web browser'|t}</div>
</div>
<div class="list-item-side">
<div class="switch">
<label>
<input type="checkbox" name="prepend_note_url" id="prepend_note_url"/>
<span class="lever"></span>
</label>
</div>
</div>
</div>
<div class="list-item">
<label class="list-item-content" for="selection-all">
<div class="list-item-label">Export all</div>
<div class="list-item-sublabel">Export all links</div>
</label>
<div class="list-item-side">
<input name="selection" class="with-gap" type="radio" value="all" id="selection-all" checked />
<label for="selection-all"></label>
</div>
</div>
<div class="list-item no-border">
<label class="list-item-content" for="selection-public">
<div class="list-item-label">Export public</div>
<div class="list-item-sublabel">Export public links only</div>
</label>
<div class="list-item-side">
<input name="selection" class="with-gap" type="radio" value="public" id="selection-public" />
<label for="selection-public"></label>
</div>
</div>
<div class="list-item no-border">
<label class="list-item-content" for="selection-private">
<div class="list-item-label">Export private</div>
<div class="list-item-sublabel">Export private links only</div>
</label>
<div class="list-item-side">
<input name="selection" class="with-gap" type="radio" value="private" id="selection-private" />
<label for="selection-private"></label>
</div>
</div>
</div>
</div>
<div class="card-footer clearfix">
<button type="submit" class="button-raised button-primary pull-right">{'Export'|t}</button>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{$pagetitle}</title>
<subtitle>Shaared links</subtitle>
{if="$show_dates"}
<updated>{$last_update}</updated>
{/if}
<link rel="self" href="{$self_link}#" />
<link rel="search" type="application/opensearchdescription+xml" href="{$index_url}open-search#"
title="Shaarli search - {$shaarlititle}" />
{loop="$plugins_feed_header"}
{$value}
{/loop}
<author>
<name>{$pagetitle}</name>
<uri>{$index_url}</uri>
</author>
<id>{$index_url}</id>
<generator>Shaarli</generator>
{loop="$links"}
<entry>
<title>{$value.title}</title>
{if="$usepermalinks"}
<link href="{$value.guid}#" />
{else}
<link href="{$value.url}#" />
{/if}
<id>{$value.guid}</id>
{if="$show_dates"}
<published>{$value.pub_iso_date}</published>
<updated>{$value.up_iso_date}</updated>
{/if}
<content type="html" xml:lang="{$language}"><![CDATA[{$value.description}]]></content>
{loop="$value.taglist"}
<category scheme="{$index_url}?searchtags=" term="{$value|strtolower}" label="{$value}" />
{/loop}
{loop="$value.feed_plugins"}
{$value}
{/loop}
</entry>
{/loop}
</feed>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{$pagetitle}</title>
<link>{$index_url}</link>
<description>Shaared links</description>
<language>{$language}</language>
<copyright>{$index_url}</copyright>
<generator>Shaarli</generator>
<atom:link rel="self" href="{$self_link}" />
<atom:link rel="search" type="application/opensearchdescription+xml" href="{$index_url}open-search#"
title="Shaarli search - {$shaarlititle}" />
{loop="$plugins_feed_header"}
{$value}
{/loop}
{loop="$links"}
<item>
<title>{$value.title}</title>
<guid isPermaLink="{if="$usepermalinks"}true{else}false{/if}">{$value.guid}</guid>
{if="$usepermalinks"}
<link>{$value.guid}</link>
{else}
<link>{$value.url}</link>
{/if}
{if="$show_dates"}
<pubDate>{$value.pub_iso_date}</pubDate>
<atom:modified>{$value.up_iso_date}</atom:modified>
{/if}
<description><![CDATA[{$value.description}]]></description>
{loop="$value.taglist"}
<category domain="{$index_url}?searchtags=">{$value}</category>
{/loop}
{loop="$value.feed_plugins"}
{$value}
{/loop}
</item>
{/loop}
</channel>
</rss>

View File

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="import"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="uploaddiv" class="container page-import">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form method="post" action="{$base_path}/admin/import" enctype="multipart/form-data" name="uploadform" id="uploadform" class="card">
<input type="hidden" name="token" value="{$token}">
<input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}">
<div class="card-title">{"Import Database"|t}</div>
<div class="card-body">
<p>Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...).</p>
<div class="form-entry">
<label for="filetoupload">File to upload</label>
<input type="file" id="filetoupload" name="filetoupload" class="autofocus"/>
<div class="sublabel">{'Maximum size allowed:'|t} <strong>{$maxfilesizeHuman}</strong></div>
</div>
<div class="form-entry">
<input type="checkbox" class="filled-in" name="overwrite" id="overwrite">
<label for="overwrite">{'Overwrite existing bookmarks'|t}</label>
</div>
</div>
<div class="list-side-right">
<div class="list-body">
<div class="list-item">
<label class="list-item-content" for="privacy-default">
<div class="list-item-label">Default</div>
<div class="list-item-sublabel">{'Use values from the imported file, default to public'|t}</div>
</label>
<div class="list-item-side">
<input name="privacy" class="with-gap" type="radio" value="default" id="privacy-default" checked />
<label for="privacy-default"></label>
</div>
</div>
<div class="list-item no-border">
<label class="list-item-content" for="privacy-private">
<div class="list-item-label">Private</div>
<div class="list-item-sublabel">{'Import all bookmarks as private'|t}</div>
</label>
<div class="list-item-side">
<input name="privacy" class="with-gap" type="radio" value="private" id="privacy-private" />
<label for="privacy-private"></label>
</div>
</div>
<div class="list-item no-border">
<label class="list-item-content" for="privacy-public">
<div class="list-item-label">Public</div>
<div class="list-item-sublabel">{'Import all bookmarks as public'|t}</div>
</label>
<div class="list-item-side">
<input name="privacy" class="with-gap" type="radio" value="public" id="privacy-public" />
<label for="privacy-public"></label>
</div>
</div>
<div class="list-item">
<div class="form-entry">
<label for="default-tags">{'Add default tags'|t}</label>
<input type="text" name="default_tags" id="default-tags" placeholder="Separate tags with comma...">
</div>
</div>
</div>
</div>
<div class="card-footer">
<button type="submit" name="import_file" class="button-raised button-primary pull-right">{'Import'|t}</button>
<div class="clearfix"></div>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,78 @@
<title>{$pagetitle}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="referrer" content="same-origin">
<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" />
<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" />
<link rel="apple-touch-icon" sizes="57x57" href="{$asset_path}/dist/img/favicons/apple-touch-icon-57x57.png#">
<link rel="apple-touch-icon" sizes="60x60" href="{$asset_path}/dist/img/favicons/apple-touch-icon-60x60.png#">
<link rel="apple-touch-icon" sizes="72x72" href="{$asset_path}/dist/img/favicons/apple-touch-icon-72x72.png#">
<link rel="apple-touch-icon" sizes="76x76" href="{$asset_path}/dist/img/favicons/apple-touch-icon-76x76.png#">
<link rel="icon" type="image/png" href="{$asset_path}/dist/img/favicons/favicon-32x32.png#" sizes="32x32">
<link rel="icon" type="image/png" href="{$asset_path}/dist/img/favicons/favicon-96x96.png#" sizes="96x96">
<link rel="icon" type="image/png" href="{$asset_path}/dist/img/favicons/favicon-16x16.png#" sizes="16x16">
<link rel="manifest" href="{$asset_path}/dist/img/favicons/manifest.json#">
<link rel="shortcut icon" href="{$asset_path}/dist/img/favicons/favicon.ico#">
<link rel="search" type="application/opensearchdescription+xml" href="{$base_path}/open-search#" title="Shaarli search - {$shaarlititle}"/>
<meta name="msapplication-TileColor" content="#603cba">
<meta name="msapplication-config" content="{$asset_path}/dist/img/favicons/browserconfig.xml#">
{if="$pageName === 'linklist' && !empty($links) && count($links) === 1"}
{$link=reset($links)}
<meta property="og:title" content="{$link.title}" />
<meta property="og:type" content="article" />
<meta property="og:url" content="{$index_url}shaare/{$link.shorturl}" />
{$ogDescription=isset($link.description_src) ? $link.description_src : $link.description}
<meta property="og:description" content="{function="mb_substr(strip_tags($ogDescription), 0, 300)"}{if="strlen($ogDescription) > 300"}...{/if}" />
{if="!empty($link.thumbnail)"}
<meta property="og:image" content="{$index_url}{$link.thumbnail}" />
{elseif="is_file('tpl/material/opengraph.png')"}
<meta property="og:image" content="{$index_url}tpl/material/opengraph.png" />
{/if}
{if="!$hide_timestamps || $is_logged_in"}
<meta property="article:published_time" content="{$link.created->format(DateTime::ATOM)}" />
{if="!empty($link.updated)"}
<meta property="article:modified_time" content="{$link.updated->format(DateTime::ATOM)}" />
{/if}
{/if}
{loop="link.taglist"}
<meta property="article:tag" content="{$value}" />
{/loop}
{else}
<meta property="og:title" content="{$pagetitle}" />
<meta property="og:type" content="website" />
{if="is_file('tpl/material/opengraph.png')"}
<meta property="og:image" content="{$index_url}tpl/material/opengraph.png" />
{/if}
{/if}
<link type="text/css" rel="stylesheet" href="{$asset_path}/dist/lib.css?v={$version_hash}#" />
<link type="text/css" rel="stylesheet" href="{$asset_path}/dist/bundle.css?v={$version_hash}#" />
{if="$conf->get('config.MATERIAL_COLOR')"}
{$themeColor=$conf->get('config.MATERIAL_COLOR')}
<meta name="theme-color" content="{$themeColor}">
{else}
<meta name="theme-color" content="#2196f3">
{/if}
{loop="$plugins_includes.css_files"}
<link type="text/css" rel="stylesheet" href="{$root_path}/{$value}?v={$version_hash}#"/>
{/loop}
{if="is_file('data/user.css')"}
<link type="text/css" rel="stylesheet" href="{$root_path}/data/user.css#" />
{/if}
{$tagSeparator=str_replace("'", "\\'", $tags_separator)}
<script>
var shaarli = {
source: '{$source}',
basePath: '{$base_path}',
rootPath: '{$root_path}',
assetPath: '{$asset_path}',
datePattern: '{$conf->get('config.MATERIAL_DATE_PATTERN')}',
isAuth: {if="$is_logged_in"}true{else}false{/if},
pageName: '{$pageName}',
asyncMetadata: {if="isset($async_metadata) && $async_metadata"}true{else}false{/if},
tagsSeparator: '{$tagSeparator}'
};
</script>
{if="file_exists('tpl/material/extra.html')"}
{include="extra"}
{/if}

View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="install"}
{include="includes"}
</head>
<body>
<div id="install" class="page-install">
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form method="post" action="{$base_path}/install" name="installform" id="installform" class="card">
<div class="card-title">Welcome to your Shaarli</div>
<div class="card-body">
<p>{'It looks like it\'s the first time you run Shaarli. Please configure it.'|t}</p>
<div class="form-entry">
<label for="setlogin">{'Username'|t}</label>
<input type="text" name="setlogin" id="setlogin" class="autofocus"/>
</div>
<div class="form-entry">
<label for="setpassword">{'Password'|t}</label>
<input type="password" id="setpassword" name="setpassword"/>
</div>
<div class="form-entry">
<label for="continent">{'Timezone'|t}</label>
<div class="row">
<div class="col-sm-6" id="timezone-continent">
<select name="continent" id="continent">
{loop="$continents"}
{if="$key !== 'selected'"}
<option value="{$value}" {if="$continents.selected === $value"}selected{/if}>
{$value}
</option>
{/if}
{/loop}
</select>
</div>
<div class="col-sm-6" id="timezone-city">
<select name="city" id="city">
{loop="$cities"}
{if="$key !== 'selected'"}
<option value="{$value.city}" {if="$cities.selected === $value.city"}selected{/if} data-continent="{$value.continent}">
{$value.city}
</option>
{/if}
{/loop}
</select>
</div>
</div>
</div>
<div class="form-entry">
<label for="title">{'Shaarli title'|t}</label>
<input type="text" name="title" id="title" placeholder="{'My links'|t}"/>
</div>
<div class="form-entry">
<input type="checkbox" class="filled-in" name="updateCheck" id="updateCheck" checked="checked">
<label for="updateCheck">{'Notify me when a new release is ready'|t}</label>
</div>
<div class="form-entry">
<input type="checkbox" class="filled-in" name="enableApi" id="enableApi">
<label for="enableApi">{'Enable REST API'|t}</label>
<div class="sublabel">{'Allow third party software to use Shaarli such as mobile application'|t}</div>
</div>
</div>
<div class="card-footer">
<button type="submit" name="Save" class="button-raised button-primary pull-right">{'Install'|t}</button>
<div class="clearfix"></div>
</div>
</form>
</div>
</div>
</div>
{include="server.requirements"}
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,152 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="linklist"}
{include="includes"}
</head>
<body>
{include="page.header"}
{$dateFormat=!empty($conf->get('config.MATERIAL_PHP_DATE_PATTERN')) ? $conf->get('config.MATERIAL_PHP_DATE_PATTERN') : '%c'}
{$qrCodeDisabled=!empty($conf->get('config.MATERIAL_NO_QRCODE')) ? $conf->get('config.MATERIAL_NO_QRCODE') : false}
<div id="linklist" class="container">
{loop="$plugin_start_zone"}
{$value}
{/loop}
{include="linklist.paging"}
{if="count($links)==0"}
<div class="text-center">
<img src="{$asset_path}/dist/img/sad_star.png#" alt="Nothing found" />
</div>
<div class="nothing-found">Sorry... We found nothing{if="!empty($search_term)"} for <strong>{$search_term}</strong>{/if}{if="!empty($search_tags)"}{$exploded_tags=explode(' ', $search_tags)} tagged <strong>{loop="$exploded_tags"} {$value}{/loop}</strong>{/if}.</div>
{elseif="!empty($search_term) or !empty($search_tags) or !empty($visibility) or $untaggedonly"}
<div id="searchcriteria">
{function="sprintf(t('%s result', '%s results', $result_count), $result_count)"}
{if="!empty($search_term)"} {'for'|t} <strong>{$search_term}</strong>{/if}
{if="!empty($search_tags)"}{$exploded_tags=tags_str2array($search_tags, $tags_separator)} {'tagged'|t} <i>
{loop="$exploded_tags"}
<a href="{$base_path}/remove-tag/{function="urlencode($value)"}" class="link-tag-filter" title="{'Remove tag'|t}">{$value}&nbsp;
<span class="remove">&#x2715;</span>
</a>
{/loop}
</i>
{/if}
{if="!empty($visibility)"}
{'with status'|t}
<strong>
{$visibility|t}
</strong>
{/if}
{if="$untaggedonly"}
<strong>
{'without any tag'|t}
</strong>
{/if}
</div>
{/if}
<div class="links-list">
{ignore}Set translation here, for performances{/ignore}
{$strPrivate=t('Private')}
{$strEdit=t('Edit')}
{$strDelete=t('Delete')}
{$strFold=t('Fold')}
{$strEdited=t('Edited: ')}
{$strPermalink=t('Permalink')}
{$strPermalinkLc=t('permalink')}
{$strAddTag=t('Add tag')}
{$strToggleSticky=t('Toggle sticky')}
{$strSticky=t('Sticky')}
{ignore}End of translations{/ignore}
{loop="$links"}
<div id="{$value.id}" class="link-outer{if="$value.class"} {$value.class}{/if}" data-id="{$value.id}">
<div class="link-overlay"></div>
<div class="link-inner">
<div class="link-header">
<div class="row">
<div class="col-sm-8">
<a class="link-title" href="{$value.real_url}">
{if="strpos($value.url, $value.shorturl) !== false"}
<i class="mdi mdi-note"></i>
{/if}
{$value.title_html}
</a>
<a href="{$value.real_url}" class="link-url"><span title="Real URL">{$value.real_url}</span></a>
</div>
<div class="col-sm-4">
<div class="link-date">
{if="!$hide_timestamps || $is_logged_in"}
<span title="Permalink - {function="strftime($dateFormat, $value.timestamp)"}"><a href="{$base_path}/shaare/{$value.shorturl}" class="link-actual-date">{function="strftime($dateFormat, $value.timestamp)"}</a></span>
{else}
<span title="Short link here"><a href="{$base_path}/shaare/{$value.shorturl}">Permalink</a></span>
{/if}
{loop="$value.link_plugin"}
<span class="link-plugin">{$value}</span>
{/loop}
{ignore}
{/ignore}
</div>
</div>
</div>
</div>
<div class="link-content">
<div>
{if="$thumbnails_enabled && $value.thumbnail !== false"}
<div class="thumb{if="$value.thumbnail === null"} hidden{/if}" {if="$value.thumbnail === null"}data-async-thumbnail="1"{/if}>
<a href="{$value.real_url}">
{ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore}
<img data-src="{$root_path}/{$value.thumbnail}#" class="b-lazy link-thumbnail"
src="#"
alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" />
</a>
</div>
{/if}
{if="$value.description"}
<div class="link-description">{$value.description}</div>
{/if}
</div>
</div>
<div class="link-footer is-flex">
<div class="link-tag-list is-flex-grown">
{if="$value.tags"}
{loop="$value.taglist"}
<span class="link-tag" title="Find links with the same tag"><a href="{$base_path}/add-tag/{$value1.taglist_urlencoded.$key2}">{$value1.taglist_html.$key2}</a></span>
{/loop}
{/if}
</div>
<div class="link-actions is-flex-end">
{if="!$qrCodeDisabled"}
<a href="#" data-permalink="{$value.real_url}" title="Show link QR Code" class="qrcode"><i class="mdi mdi-qrcode"></i></a>
{/if}
{if="$is_logged_in"}
<a href="{$base_path}/admin/shaare/delete?id={$value.id}&amp;token={$token}" title="{$strDelete}" class="button-delete"><i class="mdi mdi-delete"></i></a>
<a href="{$base_path}/admin/shaare/{$value.id}" title="{$strEdit}"><i class="mdi mdi-pencil"></i></a>
<a href="{$base_path}/admin/shaare/{$value.id}/pin?token={$token}" title="{$strToggleSticky}" {if="isset($value.sticky) && $value.sticky"}class="is-pinned"{/if}><i class="mdi mdi-pin"></i></a>
{else}
{if="isset($value.sticky) && $value.sticky"}
<span title="{$strSticky}"><i class="mdi mdi-pin"></i></span>
{/if}
{/if}
</div>
</div>
</div>
</div>
{/loop}
</div>
{include="linklist.paging"}
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,33 @@
{if="$page_max > 1"}
{if="$is_logged_in"}
{$total="$linkcount"}
{else}
{if="empty($privateLinkcount)"}
{$total="$linkcount"}
{else}
{$total="$linkcount" - "$privateLinkcount"}
{/if}
{/if}
{$from=("$page_current" - 1) * "$links_per_page" + 1}
{$to=min($total, ("$page_current" - 1) * "$links_per_page" + "$links_per_page")}
<div class="paging clearfix">
<div class="paging-links">
<div class="paging-current"><strong>{$from}-{$to}</strong> of {$total}</div>
<!--<div class="paging-current">p<span class="hidden-xs">age</span> {$page_current} / {$page_max}</div>-->
<div>{if="$next_page_url"}<a href="{$next_page_url}" class="paging-newer" title="Newer"><i class="mdi mdi-arrow-left"></i></a>{/if}</div>
<div>{if="$previous_page_url"}<a href="{$previous_page_url}" class="paging-older" title="Older"><i class="mdi mdi-arrow-right"></i></a>{/if}</div>
</div>
<div>
{loop="$action_plugin"}
<div class="paging_privatelinks">
<a
{loop="$value.attr"}
{$key}="{$value}"
{/loop}>
{$value.html}
</a>
</div>
{/loop}
</div>
</div>
{/if}

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="loginform"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="headerform" class="page-login container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form method="post" name="loginform" class="card">
<div class="card-title">{'Login'|t}</div>
<div class="card-body">
<div class="form-entry">
<label for="login">{'Username'|t}</label><br/>
<input type="text" name="login" id="login" {if="empty($username)"}class="autofocus"{/if} tabindex="1" {if="!empty($username)"}value="{$username}"{/if}>
</div>
<div class="form-entry">
<label for="password">{'Password'|t}</label><br/>
<input type="password" name="password" id="password" {if="!empty($username)"}class="autofocus"{/if} tabindex="2" >
</div>
<div class="form-entry">
<input type="checkbox" class="filled-in" name="longlastingsession" id="longlastingsession" tabindex="3"
{if="$remember_user_default"}checked="checked"{/if}>
<label for="longlastingsession">{'Remember me'|t}</label>
</div>
</div>
<div class="card-footer">
<button type="submit" class="button-raised button-primary pull-right" tabindex="4">{'Login'|t}</button>
<input type="hidden" name="token" value="{$token}">
{if="$returnurl"}<input type="hidden" name="returnurl" value="{$returnurl}">{/if}
<div class="clearfix"></div>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Shaarli search - {$pagetitle}</ShortName>
<Description>Shaarli search - {$pagetitle}</Description>
<Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" />
<Url type="application/atom+xml" template="{$serverurl}feed/atom?searchterm={searchTerms}"/>
<Url type="application/rss+xml" template="{$serverurl}feed/rss?searchterm={searchTerms}"/>
<InputEncoding>UTF-8</InputEncoding>
<Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer>
<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE
QVRIx5WWaWxU5xWG3++7986dfYYZb+MN2xiMDRiDFePUiQsNoiwpUNpAmhInJVEqpa0oQUlbJVKq
olaiqpLKUtOKhAJRm1BKRVWctuykpFjAgPcFx/uMl5mxPTOeuXPv3O3rjyiV0lIpfX+dc36c55xf
70vwP9TZ2fFpSQCwT5u6unX4f0QeNLx27RoMQwfRveTd11T23M+8S9w+Z3NRma1W4Hk6/nEimFpM
Xnun9Xpmz1MPY+feOhBi/fwAAOjq7iJEqmQqCZf5i7NvyNZ/bJPYgAjCiJc2Zmhyw68SM/T1+NlK
uf61BPoH+tHU1PT5ACMjI8RXvACvpZ5NT0+fmrG+2TKqtDLV0BgA2AUfXS3+UtfDX2ixCf73E+oA
rat92CTkv9fRBwEkSaLDt/JZR/v0Q7qjb8dQ5hjSqmYSOCkzBbogL+ij2RN8bik9wK88Al9tH4tG
ow88lvb19yEyPwfGGLq6OungYD9fUlosrqwoQVVVUeOU8qE/mU0ZTq6KNvreNort+5hugkayQUgY
qQld/u6qnVRhkciscOdOkNy5E0RnZ+e/AbwsZxAaHyORZA+prW01CTlnGppOqAcwUnCmlDAkAyin
Dapb2t7lNeRijpwvTGlJROXugoKS+upz/S19Kj9lJjxXGY1VU49tGevt7WOCSMHTeAXclePsQts9
Jq9oLR7rPVkHxpUYkK2c07ZDiieRNcAx3ZlNphcnsxbiMuEsXFSTZpabp+VVS17UNSV/8n7+gN75
+C1DM6VEjkgatiz/5IOCAheiUdeyr+198keKZXLzTKYjMDk/ZzGJhkV9AiPSdWaYIAY4U7TYNJMR
pugMqgHcXTiJqDK8ycMv2+TPWyWtKFw3KEdtJxNz8u8+/PNYIqeUgY/Oz+Z7q5X3gtqvG7qip8yM
HqdZg5kGgwGACoQQQkEMQ2DMIFnGE04xCRQTZFaexUT6jEEImJ2njjxx9fr13hfqfULTQ4apHept
lxf4mrqS3Tek0w1toTc1K6WcXfAwnyWH8kSkIueEhdhNrzUAv16fSCUzNwR3vr/G2lKWojMqbxF4
FWlLPDvBR+RBNpTsNqfkV7htuUe/UVq456qdzzvFh2KjdSH0I6ODs1ps9NHcw2jMfRYcEQyOCiox
aWR0fOQWVLFNUuOnrXJxfIPnpXLRKzocLpsJanhUU/bfjJ4gfwm/ys3JGX1cuS3UBvauLa/MBe9z
5c/xGRdSKpiVmhhN98JK/w4DGgg4uLh8u1NfPhWbmzszeH3G1rxv3dL7+qXyGaXHJi46s4QQophp
TKSDkHWGlAqmGCbAGAUA3sY5LlRZv3w44Dhnm0iFzbnsGfJR5E+EEsppTBVXuBptL5b8YQMlwnO+
Z2wtea4cl8Rc3KXJ4zShRm0CFaCaGhSDwGSMlbtLhSr7FnVhKhscC4+AO3L0x5PSFBdfE9i40SE6
xLSWRCIbJ6phIqEysqvk+2aJ0vhB1NK353zyaL3GS76NgRbVYykkt2OXuaQqMwuxkk0FT+OJpS+z
piXPRP1KzRvz4dRvO68PaWCMEQAIDcbqFiLJtyaSA/Ef3NnMGttgvHS7mUUWw7cTMemtY/cPsw3n
oe2+4mGXpt7VGWPJ6zOn1V2Xfaz5AxgvBzexodmeGWmCbWWTDOGpSZIIqeAe2/IYvnXgWbomUj2T
lNlwUP7bV64lzuRIeoodqDxi1OKRs/e0i08dH/6JPWtk6KKaZd3xj2jAXkY3BvZGK1xrhZvRNktP
fNA0ubQrxyxV4jHpSkWoXJPKYqBetxeRpgsMWwE5b/bxaYxUDSWmzCLbStqQuy02LY4/cWLsqH8i
HYNbyEeJczkJSzH2855DwsXp932F9hXEKQSQ1UHa568iRAa3uFyeNVolMBmeIrzT4cTXm37KMuvA
xb8XXTqiDELRgKg8h/dGWwP9iSDaIzcYJSCbA/uNR3N2sNahV/hbsSA70vEdW66tCOOpj8FRkIgU
Q5ybLUopiTzeTpFWUoTXdR3knwQMzLANOyQX50fWhDktLeAX3UcIA2EEQHPgi6TBsrWzaLGm7emC
H7bY+GPlV6YumiEpDhslxGBgROApz+yyhROzduIEzwngDh48iD3jO0nBRAUUXtHcoq+ZWbI5EWWa
WTieFjoKsaVoN92T88J4vlz+asXqpb+hEcdotbdueZ47vyiqhpA2UoRQwpoLtpNHxR0382jR8RzT
k8xYMyCMMfR33Uc8rxdrA9tpuHPyS7pPOTRPZ1arepa4rUsybiWnnU9a39ZXxdur+XoGAGMdoXLi
M789axnfNxDr8omiDcvF1f3OhPf18efjF/nfz6PGvv6zlrlwWwYBgcBZbKH4WKHODLHEURDxbHDF
QWGePftHUlZWjsy8guYnH2EgwORfZ5cuavEqgGhLnL6+sycvRP1Fbux/fheIn3wCCN4N/qdPMwA4
2fYOeoe7kc6kcfCbh8n+r7YwAOjs6QCl9DNx5t7dew+MOf8CcuqqoLxlhwgAAAAASUVORK5CYII=
</Image>
</OpenSearchDescription>

View File

@ -0,0 +1,31 @@
<div id="footer" class="container">
<div>
<b><a href="https://github.com/shaarli/Shaarli">Shaarli</a></b>
{if="$is_logged_in"} v{$version}{/if}
- {'The personal, minimalist, super fast, database-free, bookmarking service'|t} {'by the Shaarli community'|t}
{if="$is_logged_in"} - <a href="{$root_path}/doc/html/index.html" rel="nofollow">{'Documentation'|t}</a>{/if}
- Theme by <a href="https://github.com/kalvn">kalvn</a>
</div>
<div>
{loop="$plugins_footer.text"}
{$value}
{/loop}
</div>
{loop="$plugins_footer.endofpage"}
{$value}
{/loop}
{if="$newVersion"}
<div id="newversion"><span id="version_id">&#x25CF;</span> Shaarli {$newVersion} <a href="https://github.com/shaarli/Shaarli/releases">{'is available'|t}</a>.</div>
{/if}
{if="isset($versionError) && $versionError"}
<div id="newversion">
Error: {$versionError}
</div>
{/if}
<input type="hidden" name="token" value="{$token}" id="token" />
</div>
<script src="{$asset_path}/dist/bundle.js?v={$version_hash}#"></script>
{loop="$plugins_footer.js_files"}
<script src="{$root_path}/{$value}?v={$version_hash}#"></script>
{/loop}

View File

@ -0,0 +1,273 @@
<!--
{$isCalledFromBookmarklet=array_key_exists('source', $_GET) && in_array($_GET['source'], array('bookmarklet'))}
{$displayAddNewLinkIcon=(($is_logged_in || $openshaarli) && in_array($pageName, array('linklist', 'tools', 'tag.cloud', 'tag.list', 'picwall', 'daily')))}
{$plugins=$conf->get('general.enabled_plugins')}
{$filterClass=(($visibility==='private'||$visibility==='public'||$untaggedonly)?'has-filters':'')}
-->
{if="$isCalledFromBookmarklet"}
{ignore} When called as a popup from bookmarklet, do not display menu. {/ignore}
{else}
<div class="header-main container-fluid">
<div class="row">
<div class="col-lg-3 is-flex">
<a href="{$titleLink}" class="header-brand ripple">{$shaarlititle}</a>
<a href="#" class="icon-unfold hidden-lg ripple" title="Show/hide menu"><i class="mdi mdi-chevron-down"></i></a>
</div>
<div class="col-lg-6 header-middle">
<div class="header-nav">
<div class="col-xs-6 col-sm-3 text-center">
<a href="{$base_path}/tags/cloud" class="toolbar-link button-inverse ripple">{'Tag cloud'|t}</a>
</div>
<div class="col-xs-6 col-sm-3 text-center">
<a href="{$base_path}/picture-wall?{function="ltrim($searchcrits, '&')"}" class="toolbar-link button-inverse ripple">{'Picture wall'|t}</a>
</div>
<div class="col-xs-6 col-sm-3 text-center">
<a href="{$base_path}/daily" class="toolbar-link button-inverse ripple">{'Daily'|t}</a>
</div>
<div class="col-xs-6 col-sm-3 text-center">
<button class="toolbar-link button-inverse ripple" id="button-search">{'Search'|t}</button>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="header-buttons">
{if="isset($plugins_header.buttons_toolbar) ||isset($plugins_header.fields_toolbar)"}
<div class="toolbar-button-container">
<button type="button" class="icon-header popup-trigger ripple" data-popup="popup-plugin" title="More">
<i class="mdi mdi-dots-vertical"></i>
</button>
<div id="popup-plugin" class="popup popup-plugin hidden">
<div class="popup-title">Plugins<button class="popup-close"><i class="mdi mdi-close"></i></button></div>
<div class="popup-body">
{loop="$plugins_header.buttons_toolbar"}
<ul>
<li>
<a
{loop="$value.attr"}
{$key}="{$value}"
{/loop}>
{$value.html}
</a>
</li>
</ul>
{/loop}
{loop="$plugins_header.fields_toolbar"}
<form class="popup-content-area"
{loop="$value.attr"}
{$key}="{$value}"
{/loop} >
{loop="$value.inputs"}
<input
{loop="$value"}
{$key}="{$value}"
{/loop}>
{/loop}
</form>
{/loop}
</div>
</div>
</div>
{/if}
{if="$is_logged_in"}
<a href="{$base_path}/admin/logout" class="icon-header popup-trigger ripple" title="{'Logout'|t}">
<i class="mdi mdi-logout"></i>
</a>
<a href="{$base_path}/admin/tools" class="icon-header ripple" title="{'Tools'|t}">
<i class="mdi mdi-settings"></i>
</a>
{elseif="$openshaarli"}
<a href="{$base_path}/admin/tools" class="icon-header ripple" title="{'Tools'|t}">
<i class="mdi mdi-settings"></i>
</a>
{else}
<a href="{$base_path}/login" class="icon-header popup-trigger ripple" title="{'Login'|t}">
<i class="mdi mdi-account"></i>
</a>
{/if}
<div class="toolbar-button-container">
<button type="button" class="icon-header popup-trigger ripple" data-popup="popup-rss" title="{'RSS Feed'|t}">
<i class="mdi mdi-rss"></i>
</button>
<div id="popup-rss" class="popup popup-rss hidden">
<div class="popup-title">{'RSS Feed'|t}<button class="popup-close"><i class="mdi mdi-close"></i></button></div>
<div class="popup-body">
<ul>
<li><a href="{$base_path}/feed/{$feed_type}?{$searchcrits}" class="ripple">{'RSS Feed'|t}</a></li>
<li><a href="{$base_path}/daily-rss?day" class="ripple" title="1 RSS entry per day">Daily Feed</a></li>
<li><a href="{$base_path}/daily-rss?week" class="ripple" title="1 RSS entry per day">Weekly Feed</a></li>
<li><a href="{$base_path}/daily-rss?month" class="ripple" title="1 RSS entry per day">Monthly Feed</a></li>
</ul>
</div>
</div>
</div>
{if="$is_logged_in && $pageName === 'linklist'"}
<div class="toolbar-button-container">
<button type="button" class="icon-header ripple batch-trigger" title="Select multiple links for deletion">
<i class="mdi mdi-checkbox-marked-outline"></i>
</button>
</div>
{/if}
<div class="toolbar-button-container">
{if="$pageName === 'linklist'"}
<button type="button" class="header-button-filter popup-trigger icon-header ripple {$filterClass}" data-popup="popup-filter" title="{'Filters'|t}">
<i class="mdi mdi-filter"></i>
</button>
<div id="popup-filter" class="popup popup-filter hidden">
<div class="popup-title">{'Filters'|t}<button class="popup-close"><i class="mdi mdi-close"></i></button></div>
<div class="popup-body">
<h2>{'Links per page'|t}</h2>
<ul class="filters-links-per-page">
<li><a href="{$base_path}/links-per-page?nb=20" class="ripple {if="$links_per_page == 20"}is-bold{/if}">20 links</a></li>
<li><a href="{$base_path}/links-per-page?nb=50" class="ripple {if="$links_per_page == 50"}is-bold{/if}">50 links</a></li>
<li><a href="{$base_path}/links-per-page?nb=100" class="ripple {if="$links_per_page == 100"}is-bold{/if}">100 links</a></li>
</ul>
<form method="get" action="{$base_path}/links-per-page" class="popup-content-area">
<label for="filter-nb">Custom value</label>
<input type="text" id="filter-nb" name="nb" placeholder="Type a number..." value="{$links_per_page}"/>
</form>
<h2>{'Filters'|t}</h2>
<div class="list-side-right">
<div class="list-body">
{if="$is_logged_in"}
<div class="list-item">
<div class="list-item-content">
<div class="list-item-label">{'Only private links'|t}</div>
</div>
<div class="list-item-side">
<div class="switch">
<label data-url="{$base_path}/admin/visibility/private">
<input type="checkbox" name="input-visibility-private" id="input-visibility-private" {if="$visibility==='private'"}checked{/if}/>
<span class="lever"></span>
</label>
</div>
</div>
</div>
<div class="list-item">
<div class="list-item-content">
<div class="list-item-label">{'Only public links'|t}</div>
</div>
<div class="list-item-side">
<div class="switch">
<label data-url="{$base_path}/admin/visibility/public">
<input type="checkbox" name="input-visibility-public" id="input-visibility-public" {if="$visibility==='public'"}checked{/if}/>
<span class="lever"></span>
</label>
</div>
</div>
</div>
{/if}
<div class="list-item">
<div class="list-item-content">
<div class="list-item-label">{'Untagged links'|t}</div>
</div>
<div class="list-item-side">
<div class="switch">
<label data-url="{$base_path}/untagged-only">
<input type="checkbox" name="input-untaggedonly" id="input-untaggedonly" {if="$untaggedonly"}checked{/if}/>
<span class="lever"></span>
</label>
</div>
</div>
</div>
{if="in_array('readitlater', $plugins)"}
<div class="list-item">
<div class="list-item-content">
<div class="list-item-label">Links to read later</div>
</div>
<div class="list-item-side">
<div class="switch">
<label data-url="{$base_path}/plugin/readitlater/toggle-filter">
<input type="checkbox" name="input-readlater" id="input-readlater" {if="$readitlaterOnly"}checked{/if}/>
<span class="lever"></span>
</label>
</div>
</div>
</div>
{/if}
</div>
</div>
</div>
</div>
{/if}
</div>
</div>
</div>
</div>
</div>
{if="$displayAddNewLinkIcon"}
<a href="{$base_path}/admin/add-shaare" class="button-floating ripple">
<i class="icon-add-link mdi mdi-plus"></i>
</a>
{/if}
<form id="hidden-tag-form" class="hidden" method="GET" name="tagfilter" action="{$base_path}/">
<input type="hidden" name="searchtags" id="tagfilter_value" value=""/>
</form>
<div class="notifications">
{if="!empty($plugin_errors) && $is_logged_in"}
{loop="plugin_errors"}
<div class="notification is-danger">
<button class="notification-close"><i class="mdi mdi-close"></i></button>
{$value}
</div>
{/loop}
{/if}
{if="!empty($global_errors)"}
{loop="global_errors"}
<div class="notification is-danger">
<button class="notification-close"><i class="mdi mdi-close"></i></button>
{$value}
</div>
{/loop}
{/if}
{if="!empty($global_warnings)"}
{loop="global_warnings"}
<div class="notification is-warning">
<button class="notification-close"><i class="mdi mdi-close"></i></button>
{$value}
</div>
{/loop}
{/if}
{if="!empty($global_successes)"}
{loop="global_successes"}
<div class="notification is-success">
<button class="notification-close"><i class="mdi mdi-close"></i></button>
{$value}
</div>
{/loop}
{/if}
</div>
{/if}
<div id="search-overlay" class="fullscreen hidden">
<div class="content-fullscreen">
<div class="container">
<div class="mbl row">
<form method="get" action="{$base_path}/" id="form-search" class="col-md-8 col-md-offset-2">
<div class="search-field">
<input type="search" id="searchform_value" class="input-big" name="searchterm"
value="{if="isset($search_type)"}{if="$search_type=='fulltext'"}{$search_crits}{elseif="$search_type=='tags'"}{loop="$search_crits"}{$value} {/loop}{else}{/if}{/if}"
placeholder="Search..." autocomplete="off" data-multiple data-minChars="1"
data-list="{loop="$tags"}{$key}, {/loop}" />
<div class="search-overlay-actions">
<button type="button" id="button-filter" class="button-raised ripple ripple-primary">
<i class="visible-xs mdi mdi-tag-multiple"></i>
<span class="hidden-xs"><i class="mdi mdi-magnify"></i>{'tags'|t}</span>
</button>
<button type="submit" id="button-search" class="button-raised button-primary ripple">
<i class="mdi mdi-magnify"></i>
<span class="hidden-xs">{'search'|t}</span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div id="overlay" class="overlay hidden"></div>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="custom"}
{include="includes"}
</head>
<body>
{include="page.header"}
You body goes here...
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html class="dark"{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="picwall"}
{include="includes"}
</head>
<body class="dark dark-toolbar">
{include="page.header"}
{if="count($linksToDisplay) === 0 && $is_logged_in"}
<div>
{'There is no cached thumbnail.'|t}
<a href="{$base_path}/admin/thumbnails">{'Try to synchronize them.'|t}</a>
</div>
{/if}
<div id="plugin_zone_start_picwall" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
<div id="picwall_container text-center" class="clearfix">
{loop="$linksToDisplay"}
<div class="picwall-pictureframe ripple">
{ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore}
<img data-src="{$root_path}/{$value.thumbnail}#" class="b-lazy"
src=""
alt="thumbnail" width="{$thumbnails_width}" height="{$thumbnails_height}" />
<a class="picwall-link" href="{$value.real_url}"><span class="info">{$value.title}</span></a>
{loop="$value.picwall_plugin"}
{$value}
{/loop}
</div>
{/loop}
</div>
<div id="plugin_zone_end_picwall" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
<div class="clearfix"></div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,120 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="pluginsadmin"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="pluginadmindiv" class="page-pluginadmin container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<noscript>
<p>{'You need to enable Javascript to change plugin loading order.'|t}</p>
</noscript>
<h1>{'Plugin administration'|t}</h1>
<p>Drag and drop your plugin to change the order in which they'll be called. Uncheck enabled plugin to disable them and vice-versa.</p>
<form action="{$base_path}/admin/plugins" method="post">
<input type="hidden" name="token" value="{$token}">
<section class="card">
<div class="card-title">{'Enabled Plugins'|t}</div>
{if="count($enabledPlugins)==0"}
<div class="card-body">
<p>{'No plugin enabled.'|t}</p>
</div>
{else}
<ul id="list-plugin-enabled" class="list-sortable list-checkable">
{loop="$enabledPlugins"}
<li class="list-item list-item-sortable" data-line="{$key}" data-order="{$counter}">
<input type="checkbox" class="filled-in" name="{$key}" id="checkbox-{$key}" checked>
<label for="checkbox-{$key}"></label>
<input type="hidden" name="order_{$key}" value="{$counter}">
<div class="list-item-content">
<h3 class="list-item-label">{function="str_replace('_', ' ', $key)"}</h3>
<div class="list-item-sublabel">{$value.description}</div>
</div>
<div class="list-sortable-handle mdi mdi-menu"></div>
</li>
{/loop}
</ul>
{/if}
</section>
<section class="card">
<div class="card-title">Disabled plugins</div>
{if="count($disabledPlugins)==0"}
<div class="card-body">
<p>{'No plugin disabled.'|t}</p>
</div>
{else}
<ul class="list-sortable list-checkable">
{loop="$disabledPlugins"}
<li class="list-item list-item-sortable" data-line="{$key}" data-order="{$counter}">
<input type="checkbox" class="filled-in" id="checkbox-{$key}" name="{$key}">
<label for="checkbox-{$key}"></label>
<div class="list-item-content">
<h3 class="list-item-label">{function="str_replace('_', ' ', $key)"}</h3>
<div class="list-item-sublabel">{$value.description}</div>
</div>
</li>
{/loop}
</ul>
{/if}
</section>
<div class="clearfix">
<p>
{"More plugins available"|t}
<a href="{$root_path}/doc/html/Community-&-Related-software/#third-party-plugins">{"in the documentation"|t}</a>.
</p>
<button type="submit" class="button-raised button-primary pull-right">Save plugins</button>
</div>
</form>
<hr class="darker">
<form action="{$base_path}/admin/plugins" method="post">
<input type="hidden" name="token" value="{$token}">
<section class="card">
<div class="card-title">Plugin parameters</div>
<div class="card-body">
{if="count($enabledPlugins)==0"}
<p>{'No plugin enabled.'|t}</p>
{else}
{$count=0}
{loop="$enabledPlugins"}
{if="count($value.parameters) > 0"}
{if="$count>0"}
<hr>
{/if}
{$count=$count+1}
<h2>{function="str_replace('_', ' ', $key)"}</h2>
{loop="$value.parameters"}
<div class="row plugin-param">
<div class="col-sm-4 plugin-param-key">
<label for="{$key}" {if="isset($value.desc)"}title="{$value.desc}"{/if}>{function="str_replace('_', ' ', $key)"}</label>
{if="isset($value.desc)"}<div class="sublabel">{$value.desc}</div>{/if}
</div>
<div class="col-sm-8">
<input type="text" name="{$key}" value="{$value.value}" id="{$key}" {if="isset($value.desc)"}placeholder="{$value.desc}"{/if}>
</div>
</div>
{/loop}
{/if}
{/loop}
{if="$count==0"}
<p>{'No parameter available.'|t}</p>
{/if}
{/if}
</div>
<div class="card-footer clearfix">
<button type="submit" name="parameters_form" class="button-raised button-primary pull-right">Save plugin parameters</button>
</div>
</section>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,119 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="server"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="toolsdiv" class="page-server">
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h1>{'Server administration'|t}</h1>
<div class="card">
<div class="card-header">{'General'|t}</div>
<div class="card-body">
<div class="list">
<div class="key-value-item">
<div>{'Index URL'|t}</div>
<div><a href="{$index_url}" title="{$pagetitle}">{$index_url}</a></div>
</div>
<div class="key-value-item">
<div>{'Base path'|t}</div>
<div>{$base_path}</div>
</div>
<div class="key-value-item">
<div>{'Client IP'|t}</div>
<div>{$client_ip}</div>
</div>
<div class="key-value-item">
<div>{'Trusted reverse proxies'|t}</div>
<div>
{if="count($trusted_proxies) > 0"}
<ul>
{loop="$trusted_proxies"}
<li>{$value}</li>
{/loop}
</ul>
{else}
{'N/A'|t}
{/if}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{include="server.requirements"}
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="card">
<div class="card-header">{'Version'|t}</div>
<div class="card-body">
<div class="list">
<div class="key-value-item">
<div>{'Current version'|t}</div>
<div>{$current_version}</div>
</div>
<div class="key-value-item">
<div>{'Latest release'|t}</div>
<div><a href="{$release_url}" title="{'Visit releases page on Github'|t}">{$latest_version}</a></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-md-offset-3">
<div class="card">
<div class="card-header">{'Thumbnails'|t}</div>
<div class="card-body">
<div class="list">
<div class="key-value-item">
<div>{'Thumbnails status'|t}</div>
<div>
{if="$thumbnails_mode==='all'"}
{'All'|t}
{elseif="$thumbnails_mode==='common'"}
{'Only common media hosts'|t}
{else}
{'None'|t}
{/if}
</div>
</div>
</div>
</div>
{if="$thumbnails_mode!=='none'"}
<div class="card-footer">
<a href="{$base_path}/admin/thumbnails" title="{'Synchronize all link thumbnails'|t}" class="button-raised pull-right">{'Synchronize thumbnails'|t}</a>
<div class="clearfix"></div>
</div>
{/if}
</div>
</div>
<div class="col-md-6 col-md-offset-3">
<div class="card">
<div class="card-header">{'Cache'|t}</div>
<div class="card-footer">
<a href="{$base_path}/admin/clear-cache?type=main" class="button-raised pull-right">{'Clear main cache'|t}</a>
<a href="{$base_path}/admin/clear-cache?type=thumbnails" class="button-raised pull-right">{'Clear thumbnails cache'|t}</a>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,74 @@
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="card">
<div class="card-header">{'Permissions'|t}</div>
<div class="card-body">
{if="count($permissions) > 0"}
<p><i class="mdi mdi-close text-error"></i> {'There are permissions that need to be fixed.'|t}</p>
<ul>
{loop="$permissions"}
<li>{$value}</li>
{/loop}
</ul>
{else}
<p><i class="mdi mdi-check text-success"></i> {'All read/write permissions are properly set.'|t}</p>
{/if}
</div>
</div>
</div>
<div class="col-md-10 col-md-offset-1">
<div class="card">
<div class="card-header">PHP</div>
<div class="card-body">
<p>
<strong>{'Running PHP'|t} {$php_version}</strong>
{if="$php_has_reached_eol"}
<i class="mdi mdi-circle text-warning" aria-label="hidden"></i><br>
{'End of life: '|t} {$php_eol}
{else}
<i class="mdi mdi-circle text-success" aria-label="hidden"></i><br>
{/if}
</p>
<table>
<thead>
<tr>
<th>{'Extension'|t}</th>
<th>{'Usage'|t}</th>
<th>{'Status'|t}</th>
<th class="text-center">{'Loaded'|t}</th>
</tr>
</thead>
<tbody>
{loop="$php_extensions"}
<tr>
<td>{$value.name}</td>
<td>{$value.desc}</td>
<td>{$value.required ? t('Required') : t('Optional')}</td>
<td class="text-center">
{if="$value.loaded"}
{$classLoaded="text-success"}
{$strLoaded=t('Loaded')}
{else}
{$strLoaded=t('Not loaded')}
{if="$value.required"}
{$classLoaded="text-error"}
{else}
{$classLoaded="text-warning"}
{/if}
{/if}
<i class="mdi mdi-circle {$classLoaded}" aria-label="{$strLoaded}" title="{$strLoaded}"></i>
</td>
</tr>
{/loop}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="tag.cloud"}
{include="includes"}
</head>
<body>
{include="page.header"}
{include="tag.sort"}
<div class="center container">
<div id="plugin_zone_start_tagcloud" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
<div id="cloudtag">
{loop="$tags"}
<a href="{$base_path}/?searchtags={$tags_url.$key1}{$tags_separator|urlencode}{$search_tags_url}" style="font-size:{$value.size}em;">{$key}</a
><span class="link-tag"><a href="{$base_path}/add-tag/{$tags_url.$key1}" title="{'Filter by tag'|t}" class="count">{$value.count}</a></span>
{loop="$value.tag_plugin"}
{$value}
{/loop}
{/loop}
</div>
<div id="plugin_zone_end_tagcloud" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="tag.list"}
{include="includes"}
</head>
<body>
{include="page.header"}
{include="tag.sort"}
<div class="container">
{$countTags=count($tags)}
<div id="plugin_zone_start_tagcloud" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
<div id="taglist" class="card">
<div class="card-header">
<div class="pull-right"><a href="{$base_path}/?searchtags={$search_tags|urlencode}" title="{'List all links with those tags'|t}">{$countTags} {'tags'|t}</a></div>
{'Tag list'|t}
</div>
<form class="card-search" method="get">
<input type="hidden" name="do" value="taglist">
<input type="search" name="searchtags" placeholder="{'Filter by tag'|t}..."
{if="!empty($search_tags)"}
value="{$search_tags}"
{/if}
autocomplete="off" data-multiple data-autofirst data-minChars="1"
data-list="{loop="$tags"}{$key}, {/loop}"
>
</form>
{loop="tags"}
<div class="list-item-flex is-control-opaque-hover is-highlighted-hover">
<div class="list-item-start list-item-text">
{$value}
</div>
<div class="list-item-middle list-item-text">
<a href="{$base_path}/?searchtags={$key|urlencode} {$search_tags|urlencode}" class="tag-link">{$key}</a>
{loop="$value.tag_plugin"}
{$value}
{/loop}
</div>
<div class="list-item-end list-item-controls">
<a href="{$base_path}/add-tag/{$key|urlencode}" title="{'Add tag'|t} {$key} {'to the filters'|t}" class="count list-item-control">
<i class="mdi mdi-magnify-plus-outline"></i>
</a>
{if="$is_logged_in"}
<a href="{$base_path}/admin/tags?fromtag={$key|urlencode}" title="{'Rename tag'|t} {$key}" data-tag="{$key}" class="rename-tag list-item-control">
<i class="mdi mdi-pencil"></i>
</a>
<a href="#" class="delete-tag list-item-control" data-tag="{$key}" title="{'Delete tag'|t} {$key}"><i class="mdi mdi-delete"></i></a>
{/if}
</div>
</div>
{/loop}
</div>
<div id="plugin_zone_end_tagcloud" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
</div>
{if="$is_logged_in"}
<input type="hidden" name="taglist" value="{loop="$tags"}{$key} {/loop}">
{/if}
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,7 @@
<div class="subheader">
<div class="container text-center">
<a class="button-inverse button-default{if="$pageName === 'tag.cloud'"} active{/if}" href="{$base_path}/tags/cloud" title="{'Cloud'|t}">{'Cloud'|t}</a>
<a class="button-inverse button-default{if="$pageName === 'tag.list' && $_GET['sort'] === 'usage'"} active{/if}" href="{$base_path}/tags/list?sort=usage" title="{'Most used'|t}">{'Most used'|t}</a>
<a class="button-inverse button-default{if="$pageName === 'tag.list' && $_GET['sort'] === 'alpha'"} active{/if}" href="{$base_path}/tags/list?sort=alpha" title="{'Alphabetical'|t}">{'Alphabetical'|t}</a>
</div>
</div>

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="thumbnails"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div class="page-thumbnails container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="card">
<h1 class="card-header">{'Thumbnails update'|t}</h1>
<div class="card-body">
<div class="thumbnail-placeholder" style="width: {$thumbnails_width}px; height: {$thumbnails_height}px;">
</div>
<div class="thumbnail-link-title">
Loading...
</div>
<div class="progress-bar">
<div class="progress-actual"></div>
</div>
<div class="progress-counter">
<span class="progress-current">0</span> / <span class="progress-total">{$ids|count}</span>
</div>
<input type="hidden" name="ids" value="{function="implode(',', $ids)"}" />
</div>
</div>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>

View File

@ -0,0 +1,240 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="tools"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="toolsdiv" class="page-tools container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="card">
<h1 class="card-header">{'Settings'|t}</h1>
<div class="list-action list-big">
<a class="list-item ripple" href="{$base_path}/admin/configure">
<div class="row">
<div class="col-sm-2 col-xs-3">
<div class="round-image-container">
<i class="mdi mdi-settings" title="{'Configure your Shaarli'|t}"></i>
</div>
</div>
<div class="col-sm-10 col-xs-9">
<div class="list-item-label">{'Configure your Shaarli'|t}</div>
<div class="list-item-sublabel">{'Change Title, timezone...'|t}</div>
</div>
</div>
</a>
{if="!$openshaarli"}
<a class="list-item ripple" href="{$base_path}/admin/password">
<div class="row">
<div class="col-sm-2 col-xs-3">
<div class="round-image-container">
<i class="mdi mdi-lock" title="{'Change password'|t}"></i>
</div>
</div>
<div class="col-sm-10 col-xs-9">
<div class="list-item-label">{'Change password'|t}</div>
<div class="list-item-sublabel">{'Change your password'|t}</div>
</div>
</div>
</a>
{/if}
<a class="list-item ripple" href="{$base_path}/admin/plugins">
<div class="row">
<div class="col-sm-2 col-xs-3">
<div class="round-image-container">
<i class="mdi mdi-puzzle" title="{'Plugin administration'|t}"></i>
</div>
</div>
<div class="col-sm-10 col-xs-9">
<div class="list-item-label">{'Plugin administration'|t}</div>
<div class="list-item-sublabel">{'Enable, disable and configure plugins'|t}</div>
</div>
</div>
</a>
<a class="list-item ripple" href="{$base_path}/admin/server">
<div class="row">
<div class="col-sm-2 col-xs-3">
<div class="round-image-container">
<i class="mdi mdi-server" title="{'Check instance\'s server configuration'|t}"></i>
</div>
</div>
<div class="col-sm-10 col-xs-9">
<div class="list-item-label">{'Server administration'|t}</div>
<div class="list-item-sublabel">{'Check instance\'s server configuration'|t}</div>
</div>
</div>
</a>
<a class="list-item ripple" href="{$base_path}/admin/tags">
<div class="row">
<div class="col-sm-2 col-xs-3">
<div class="round-image-container">
<i class="mdi mdi-tag-multiple" title="{'Tags'|t}"></i>
</div>
</div>
<div class="col-sm-10 col-xs-9">
<div class="list-item-label">{'Tags'|t}</div>
<div class="list-item-sublabel">{'Rename or delete a tag in all links'|t}</div>
</div>
</div>
</a>
<a class="list-item ripple" href="{$base_path}/admin/import">
<div class="row">
<div class="col-sm-2 col-xs-3">
<div class="round-image-container">
<i class="mdi mdi-file-import" title="{'Import'|t}"></i>
</div>
</div>
<div class="col-sm-10 col-xs-9">
<div class="list-item-label">{'Import'|t}</div>
<div class="list-item-sublabel">{'Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)'|t}</div>
</div>
</div>
</a>
<a class="list-item ripple" href="{$base_path}/admin/export">
<div class="row">
<div class="col-sm-2 col-xs-3">
<div class="round-image-container">
<i class="mdi mdi-file-export" title="{'Export'|t}"></i>
</div>
</div>
<div class="col-sm-10 col-xs-9">
<div class="list-item-label">{'Export'|t}</div>
<div class="list-item-sublabel">{'Export Netscape html bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)'|t}</div>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
{if="!empty($linkcount)"}
<div class="row">
<div class="col-md-8 col-md-offset-2">
<hr class="darker"/>
<h2 class="mtm">{'Statistics'|t}</h2>
<ul>
<li><strong>{$linkcount}</strong> links in total</li>
<li><strong>{$privateLinkcount}</strong> private links</li>
</ul>
</div>
</div>
{/if}
{if="isset($tools_plugin)"}
<div class="row">
<div class="col-md-8 col-md-offset-2">
<hr class="darker"/>
<h2 class="mtm">Plugin settings</h2>
{loop="$tools_plugin"}
{$value}
{/loop}
</div>
</div>
{/if}
<div class="row">
<div class="col-md-8 col-md-offset-2">
<hr class="darker"/>
<h2 class="mtm">Bookmarklet</h2>
<p>You can easily bookmark links from anywhere on the web via bookmarklets right below.</p>
<p>They can be dragged and dropped among your browser's bookmarks. Then, you just have to click on them from your bookmarks menu.</p>
<div class="row">
<div class="col-xs-6 text-center">
<a class="bookmarklet"
href="javascript:(
function(){
var%20url%20=%20location.href;
var%20title%20=%20document.title%20||%20url;
var%20desc=document.getSelection().toString();
if(desc.length>4000){
desc=desc.substr(0,4000)+'...';
alert('{function="str_replace(' ', '%20', t('The selected text is too long, it will be truncated.'))"}');
}
window.open(
'{$pageabsaddr}admin/shaare?post='%20+%20encodeURIComponent(url)+
'&amp;title='%20+%20encodeURIComponent(title)+
'&amp;description='%20+%20encodeURIComponent(desc)+
'&amp;source=bookmarklet','_blank','menubar=no,height=800,width=600,toolbar=no,scrollbars=yes,status=no,dialog=1'
);
}
)();">
<img src="{$asset_path}/dist/img/tools/star-circle.png#" alt="" /> {'Shaare link'|t}
</a>
</div>
<div class="col-xs-6 text-center">
<a class="bookmarklet"
href="javascript:(
function(){
var%20desc=document.getSelection().toString();
if(desc.length>4000){
desc=desc.substr(0,4000)+'...';
alert('{function="str_replace(' ', '%20', t('The selected text is too long, it will be truncated.'))"}');
}
window.open(
'{$pageabsaddr}?private=1&amp;post='+
'&amp;description='%20+%20encodeURIComponent(desc)+
'&amp;source=bookmarklet','_blank','menubar=no,height=800,width=600,toolbar=no,scrollbars=yes,status=no,dialog=1'
);
}
)();">
<img src="{$asset_path}/dist/img/tools/star-circle.png#" alt="" /> {'Add Note'|t}
</a>
</div>
</div>
</div>
</div>
<div class="row mbm">
<div class="col-md-8 col-md-offset-2">
<hr class="darker"/>
<h2 class="mtm" id="firefox-api-title">{'3rd party'|t}</h2>
<ul>
<li>
<a href="https://addons.mozilla.org/fr/firefox/addon/shaarli/" title="Firefox {'Plugin'|t}" class="large-icon-button ripple">
<i class="mdi mdi-firefox"></i>
Firefox {'plugin'|t}
</a>
</li>
<li>
<a href="https://chromewebstore.google.com/detail/add-to-shaarli/jhfblapoehcfajokolimghdfmeeakbee" title="Chrome {'Plugin'|t}" class="large-icon-button ripple">
<i class="mdi mdi-google-chrome"></i>
Chrome {'plugin'|t}
</a>
</li>
<li>
<a href="https://f-droid.org/fr/packages/com.dimtion.shaarlier/" title="Android Shaarlier" class="large-icon-button ripple">
<i class="mdi mdi-android"></i>
Shaarlier
</a>
</li>
<li>
<a href="https://stakali.toneiv.eu/" title="Android Stakali" class="large-icon-button ripple">
<i class="mdi mdi-android"></i>
Stakali
</a>
</li>
<li>
<a href="https://github.com/lockcp/ShaarliOS" class="large-icon-button ripple">
<i class="mdi mdi-apple-ios"></i>
iOS
</a>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<p>Other <a href="https://shaarli.readthedocs.io/en/master/Community-and-related-software.html">{'Community and related software'|t}</a>.</p>
</div>
</div>
{loop="$tools_plugin"}
{$value}
{/loop}
</div>
{include="page.footer"}
</body>
</html>

6131
example_Shaarli-Material/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,54 @@
{
"name": "shaarlimaterial",
"version": "0.14.0",
"description": "Shaarli Material is a theme for [Shaarli](https://github.com/shaarli/Shaarli), the personal, minimalist, super fast, database-free, bookmarking service.",
"main": "src/js/main.js",
"type": "module",
"dependencies": {
"autosize": "^6.0.1",
"awesomplete": "^1.1.5",
"blazy": ">=1.3.0",
"bootstrap": "^3.4.1",
"jquery": "^3.5.1",
"qrcode": "^1.4.4",
"salvattore": "^1.0.9",
"sortablejs": "^1.13.0"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^28.0.2",
"@rollup/plugin-eslint": "^9.0.3",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-terser": "^0.4.0",
"cross-env": "^7.0.3",
"csso": "^5.0.5",
"eslint": "^9.17.0",
"neostandard": "^0.12.0",
"postcss": "^8.2.4",
"postcss-clean": "^1.1.0",
"rollup": "^4.29.1",
"rollup-plugin-copy": "^3.3.0",
"rollup-plugin-css-only": "^4.3.0",
"rollup-plugin-sass": "^1.12.19",
"sass": "^1.32.4"
},
"scripts": {
"build": "cross-env NODE_ENV=production rollup -c --strictDeprecations",
"dev": "cross-env NODE_ENV=development rollup -c --watch",
"lint": "eslint src/js",
"lint:fix": "eslint src/js --fix"
},
"repository": {
"type": "git",
"url": "https://github.com/kalvn/Shaarli-Material.git"
},
"keywords": [
"shaarli",
"material",
"design"
],
"author": "kalvn",
"license": "MIT",
"browserslist": [
"last 5 versions"
]
}

View File

@ -0,0 +1,50 @@
import path from 'path';
import fs from 'fs';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import postcss from 'postcss';
import clean from 'postcss-clean';
import copy from 'rollup-plugin-copy';
import terser from '@rollup/plugin-terser';
import sass from 'rollup-plugin-sass';
import css from 'rollup-plugin-css-only';
import { minify } from 'csso';
const config = {
input: 'src/js/main.js',
output: {
file: 'material/dist/bundle.js',
format: 'iife'
},
plugins: [
resolve({
browser: true
}),
commonjs(),
css({
output: function (styles) {
fs.writeFileSync(path.join('material', 'dist', 'lib.css'), minify(styles).css);
}
}),
sass({
output: true,
processor: css => postcss([clean])
.process(css, { from: undefined })
.then(result => result.css)
}),
process.env.NODE_ENV === 'production' ? terser() : null,
copy({
targets: [
{ src: 'src/assets/fonts', dest: 'material/dist' },
{ src: 'src/assets/img', dest: 'material/dist' },
{ src: 'node_modules/bootstrap/dist/fonts/*', dest: 'material/dist/fonts' }
]
})
],
watch: {
exclude: 'node_modules/**'
}
};
export default config;

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

@ -0,0 +1,203 @@
Font data copyright Google 2012
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 916 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 916 B

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="tpl/material/images/favicons/mstile-70x70.png"/>
<square150x150logo src="tpl/material/images/favicons/mstile-150x150.png"/>
<wide310x150logo src="tpl/material/images/favicons/mstile-310x150.png"/>
<TileColor>#603cba</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,29 @@
{
"name": "Links",
"icons": [
{
"src": "tpl\/material\/images\/favicons\/android-chrome-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "tpl\/material\/images\/favicons\/android-chrome-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "tpl\/material\/images\/favicons\/android-chrome-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "tpl\/material\/images\/favicons\/android-chrome-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

View File

@ -0,0 +1,46 @@
import $ from 'jquery';
import animations from './animations';
import { guid } from './utils';
const displayActionBar = function (options) {
if (typeof options !== 'object') {
console.error('displayActionBar expects an object as options.');
return;
}
const uid = guid();
let html = '<div id="' + uid + '" class="hidden actionbar ' + options.classes + '"><div class="container"><div class="row"><div class="actionbar-label">' + options.label + '</div><div class="actionbar-selectall">- <a href="#" class="actionbar-selectall-link">select all</a></div><div class="actionbar-controls">';
if (options.displayCancel) {
html += '<button type="button" class="button button-default" id="actionbar-cancel">Cancel</button>';
}
for (const i in options.controls) {
const control = options.controls[i];
html += '<button type="button" class="' + control.classes + '" id="' + control.id + '">' + control.label + '</button>';
}
html += '</div></div></div></div>';
$('body').append(html);
const $actionbar = $('#' + uid).eq(0);
if (typeof options.onCancel === 'function') {
$('#actionbar-cancel').on('click', function () {
options.onCancel();
});
}
for (const i in options.controls) {
const control = options.controls[i];
$('#' + control.id).on('click', control.callback);
}
animations.showSlideFromBottom($actionbar);
return $actionbar;
};
export default displayActionBar;

View File

@ -0,0 +1,97 @@
import $ from 'jquery';
const animationEventName = 'animationend';
const animations = {
animation: function (animationName, element, callbackBegin, callbackEnd) {
element.on(animationEventName + '.' + animationName, function () {
// Removes this listener and animation classes.
$(this).off(animationEventName + '.' + animationName)
.removeClass(function (index, classes) {
return (classes.match(/animate-\S+/g) || []).join(' ');
});
// Calls the specified callback if it exists.
if (typeof callbackEnd === 'function') {
callbackEnd();
}
});
element.addClass('animate-' + animationName);
if (typeof callbackBegin === 'function') {
callbackBegin();
}
},
fadeIn: function (element, callbackBegin, callbackEnd) {
const realCallbackBegin = function () {
element.removeClass('hidden');
if (typeof callbackBegin === 'function') {
callbackBegin();
}
};
this.animation('fade-in', element, realCallbackBegin, callbackEnd);
},
fadeOut: function (element, callbackBegin, callbackEnd) {
const realCallbackEnd = function () {
element.addClass('hidden');
if (typeof callbackEnd === 'function') {
callbackEnd();
}
};
this.animation('fade-out', element, callbackBegin, realCallbackEnd);
},
slideFromTop: function (element, callbackBegin, callbackEnd) {
const realCallbackBegin = function () {
element.removeClass('hidden');
if (typeof callbackBegin === 'function') {
callbackBegin();
}
};
this.animation('slide-from-top', element, realCallbackBegin, callbackEnd);
},
slideFromRight: function (element, callbackBegin, callbackEnd) {
const realCallbackBegin = function () {
element.removeClass('hidden');
if (typeof callbackBegin === 'function') {
callbackBegin();
}
};
this.animation('slide-from-right', element, realCallbackBegin, callbackEnd);
},
hideSlideToBottom: function (element, callbackBegin, callbackEnd) {
const realCallbackEnd = function () {
element.addClass('hidden');
if (typeof callbackEnd === 'function') {
callbackEnd();
}
};
this.animation('hide-slide-to-bottom', element, callbackBegin, realCallbackEnd);
},
showSlideFromBottom: function (element, callbackBegin, callbackEnd) {
const realCallbackBegin = function () {
element.removeClass('hidden');
if (typeof callbackBegin === 'function') {
callbackBegin();
}
};
this.animation('show-slide-from-bottom', element, realCallbackBegin, callbackEnd);
},
compressHeight: function (element, callbackBegin, callbackEnd) {
const realCallbackEnd = function () {
element.addClass('hidden');
if (typeof callbackEnd === 'function') {
callbackEnd();
}
};
this.animation('compress-height-50', element, callbackBegin, realCallbackEnd);
}
};
export default animations;

View File

@ -0,0 +1,159 @@
import $ from 'jquery';
import http from './http';
import modal from './modal';
const saveLink = async function ($form) {
const data = {};
const exists = $form.find('[name="lf_id"]').length > 0;
$form.find('input[type="text"], textarea, input[type="checkbox"], input[type="hidden"]')
.each(function (index, element) {
const $element = $(element);
if ($element.attr('type') === 'checkbox') {
if ($element.prop('checked')) {
data[$element.attr('name')] = 'on';
}
return;
}
data[$element.attr('name')] = $element.val();
});
try {
formBeforeLoad($form);
await http.createLink(data);
formAfterLoad($form, exists ? 'updated' : 'created', 'success');
} catch (err) {
console.error(err);
formError($form);
modal('Error', 'Something went wrong when saving the link.', 'alert');
}
};
const saveAllLinks = async function () {
const forms = document.querySelectorAll('form[name="linkform"]');
const total = forms.length;
const $progressOverlay = $('#progress-overlay');
const $progressCurrent = $progressOverlay.find('.progress-current');
const $progressTotal = $progressOverlay.find('.progress-total');
const $progressActual = $progressOverlay.find('.progress-actual');
$progressTotal.text(total);
$progressOverlay.removeClass('hidden');
for (let i = 0; i < total; i++) {
await saveLink($(forms[i]));
$progressCurrent.text(i + 1);
$progressActual.css('width', `${(i + 1) * 100 / total}%`);
}
$progressOverlay.addClass('hidden');
};
const deleteLink = async function ($buttonDelete) {
const url = $buttonDelete.attr('href');
const $form = $buttonDelete.closest('form');
modal('Delete link', 'Are you sure you want to delete this link?', 'confirm', async function (accepts) {
if (accepts) {
try {
formBeforeLoad($form);
await http.deleteLinkByUrl(url);
formAfterLoad($form, 'deleted', 'danger');
} catch (err) {
console.error(err);
formError($form);
modal('Error', 'Something went wrong when deleting the link.', 'alert');
}
}
});
};
const cancelLink = async function ($buttonCancel) {
const $form = $buttonCancel.closest('form');
formBeforeLoad($form);
// Necessary for the animation to play properly.
await new Promise(resolve => setTimeout(resolve, 100));
formAfterLoad($form, 'cancelled');
};
// Checks if there are remaining links.
const noMoreLinks = function () {
if ($('form[name="linkform"]').length === 0) {
$('[name="save_edit_batch"]').attr('disabled', 'disabled');
}
};
const formBeforeLoad = function ($form) {
$form.append('<div class="card-overlay"></div>');
const height = $form.height();
$form.css('max-height', `${height}px`);
};
const formAfterLoad = function ($form, message, type) {
const url = $form.find('[name="lf_url"]').val();
const customClass = type ? `is-${type}` : '';
$form.find('.card-overlay').html(`<div class="is-flex"><div class="nowrap">${url}</div><div class="tag is-light ${customClass}">${message}</div></div>`);
$form.closest('.editlinkform').addClass('is-batch-done');
// Prevents this form from being treated again.
$form.removeAttr('name');
};
const formError = function ($form) {
$form.find('.card-overlay').remove();
$form.css('max-height', 'none');
};
const batchAdd = {
init: function () {
if (shaarli.pageName === 'addlink') {
$('.button-batch-addform').on('click', function () {
$('.batch-addform').removeClass('hidden');
$(this).remove();
});
}
if (shaarli.pageName === 'editlinkbatch') {
// Single save buttons.
$('[name="save_edit"]').on('click', async function (event) {
event.preventDefault();
await saveLink($(this).closest('form'));
noMoreLinks();
return false;
});
$('[name="save_edit_batch"]').on('click', async function (event) {
event.preventDefault();
await saveAllLinks();
noMoreLinks();
return false;
});
// Single delete buttons.
$('[name="delete_link"]').off('click').on('click', async function (event) {
event.preventDefault();
await deleteLink($(this));
noMoreLinks();
return false;
});
// Single cancel buttons.
$('[name="cancel-batch-link"]').on('click', async function (event) {
event.preventDefault();
await cancelLink($(this));
noMoreLinks();
return false;
});
}
}
};
export default batchAdd;

Some files were not shown because too many files have changed in this diff Show More