Kaynağa Gözat

Improved: Sharing/Integration configuration (#4269)

* stick plus button to select list

* HTML improved very much

* drag and drop improved

* add URL button

* fix remove button behavior

* prepare for PR#4238

* improve length of inputs

* First draft of documentation of the sharing services

* new config option: depricated

* i18n for depricated text

* Doc: Blogotext depricated to 2023

* dropdown menu with config link and depricated sign

* Update entry_bottom.phtml

* Update 08_sharing_services.md

* Update template.rtl.css

* Typo Deprecated/Depricated

* typo

* updated the documentation comment

* Update shares.php

* Update app/i18n/fr/conf.php

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update p/scripts/draggable.js

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update p/scripts/draggable.js

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Documentation: services from #4270

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
maTh 4 yıl önce
ebeveyn
işleme
be5848fd4f

+ 12 - 2
app/Models/Share.php

@@ -20,12 +20,13 @@ class FreshRSS_Share {
 			return;
 		}
 
+		$isDeprecated = isset($share_options['deprecated']) ? $share_options['deprecated'] : false;
 		$help_url = isset($share_options['help']) ? $share_options['help'] : '';
 		$field = isset($share_options['field']) ? $share_options['field'] : null;
 		self::$list_sharing[$type] = new FreshRSS_Share(
 			$type, $share_options['url'], $share_options['transform'],
 			$share_options['form'], $help_url, $share_options['method'],
-			$field
+			$field, $isDeprecated
 		);
 	}
 
@@ -83,6 +84,7 @@ class FreshRSS_Share {
 	private $id = null;
 	private $title = null;
 	private $link = null;
+	private $isDeprecated = false;
 	private $method = 'GET';
 	private $field;
 
@@ -97,11 +99,12 @@ class FreshRSS_Share {
 	 * @param string $help_url is an optional url to give help on this option.
 	 * @param string $method defines the sharing method (GET or POST)
 	 */
-	private function __construct($type, $url_transform, $transform, $form_type, $help_url, $method, $field) {
+	private function __construct($type, $url_transform, $transform, $form_type, $help_url, $method, $field, $isDeprecated = false) {
 		$this->type = $type;
 		$this->name = _t('gen.share.' . $type);
 		$this->url_transform = $url_transform;
 		$this->help_url = $help_url;
+		$this->isDeprecated = $isDeprecated;
 
 		if (!is_array($transform)) {
 			$transform = array();
@@ -196,6 +199,13 @@ class FreshRSS_Share {
 		return $this->base_url;
 	}
 
+	/**
+	 * Return the deprecated status of the share option.
+	 */
+	public function isDeprecated() {
+		return $this->isDeprecated;
+	}
+
 	/**
 	 * Return the current url by merging url_transform and base_url.
 	 */

+ 1 - 0
app/i18n/cz/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Sdílení',
 		'add' => 'Přidat metodu sdílení',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'E-mail',
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/de/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Teilen',
 		'add' => 'Füge eine Teilen-Dienst hinzu',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'Dieser Dienst ist veraltet und wir in einer <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">zukünftigen FreshRSS-Version</a> entfernt.',
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'E-Mail',
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/en-us/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Sharing',	// IGNORE
 		'add' => 'Add a sharing method',	// IGNORE
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'Email',	// IGNORE
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/en/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Sharing',
 		'add' => 'Add a sharing method',
 		'blogotext' => 'Blogotext',
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',
 		'email' => 'Email',
 		'facebook' => 'Facebook',

+ 1 - 0
app/i18n/es/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Compartir',
 		'add' => 'Agregar un método de uso compartido',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'Email',	// TODO
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/fr/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Partage',
 		'add' => 'Ajouter une méthode de partage',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'Ce service est obsolète et sera supprimé dans une <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Voir la documentation" target="_blank">prochaine version de FreshRSS</a>.',
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'Courriel',
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/he/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'שיתוף',
 		'add' => 'Add a sharing method',	// TODO
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'דואר אלקטרוני',
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/it/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Condivisione',
 		'add' => 'Add a sharing method',	// TODO
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'Email',	// TODO
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/ja/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => '共有',
 		'add' => '共有方法を追加する',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'Eメール',
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/ko/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => '공유',
 		'add' => '공유 방법 추가',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => '메일',
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/nl/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Delen',
 		'add' => 'Deelmethode toevoegen',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'Email',	// IGNORE
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/oc/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Partatge',
 		'add' => 'Ajustar un metòde de partatge',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'Corrièl',
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/pl/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Podawanie dalej',
 		'add' => 'Dodaj sposób na podanie dalej wiadomości',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'E-mail',
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/pt-br/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Compartilhando',
 		'add' => 'Adicionar um método de compartilhamento',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'E-mail',
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/ru/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Поделиться',
 		'add' => 'Добавить способ поделиться',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'Электронная почта',
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/sk/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Zdieľanie',
 		'add' => 'Pridať spôsob zdieľania',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'E-mail',	// IGNORE
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/tr/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => 'Paylaşım',
 		'add' => 'Bir paylaşım türü ekle',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'Email',	// IGNORE
 		'facebook' => 'Facebook',	// IGNORE

+ 1 - 0
app/i18n/zh-cn/conf.php

@@ -194,6 +194,7 @@ return array(
 		'_' => '分享',
 		'add' => '添加分享方式',
 		'blogotext' => 'Blogotext',	// IGNORE
+		'deprecated' => 'This service is deprecated and will be removed from FreshRSS in a <a href="https://freshrss.github.io/FreshRSS/en/users/08_sharing_services.md" title="Open documentation for more information" target="_blank">future release</a>.',	// TODO
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => '邮箱',	// IGNORE
 		'facebook' => '脸书',	// IGNORE

+ 9 - 5
app/shares.php

@@ -7,23 +7,27 @@
  *
  * For each share there is different configuration options. Here is the description
  * of those options:
- *   - url is a mandatory option. It is a string representing the share URL. It
+ *   - 'deprecated' (optional) is a boolean. Default: 'false'.
+ *     'true', if the sharing service is planned to remove in the future.
+ *     Add more information into the documentation center.
+ *   - 'url' is a mandatory option. It is a string representing the share URL. It
  *     supports 4 different placeholders for custom data. The ~URL~ placeholder
  *     represents the URL of the system used to share, it is configured by the
  *     user. The ~LINK~ placeholder represents the link of the shared article.
  *     The ~TITLE~ placeholder represents the title of the shared article. The
  *     ~ID~ placeholder represents the id of the shared article (only useful
  *     for internal use)
- *   - transform is an array of transformation to apply on links and titles
- *   - help is a URL to a help page (mandatory for form = 'advanced')
- *   - form is the type of form to display during configuration. It’s either
+ *   - 'transform' is an array of transformation to apply on links and titles
+ *   - 'help' is a URL to a help page (mandatory for form = 'advanced')
+ *   - 'form' is the type of form to display during configuration. It’s either
  *     'simple' or 'advanced'. 'simple' is used when only the name is configurable,
  *     'advanced' is used when the name and the location are configurable.
- *   - method is the HTTP method (POST or GET) used to share a link.
+ *   - 'method' is the HTTP method (POST or GET) used to share a link.
  */
 
 return array(
 	'blogotext' => array(
+		'deprecated' => true,
 		'url' => '~URL~/admin/links.php?url=~LINK~',
 		'transform' => array(),
 		'help' => 'http://lehollandaisvolant.net/blogotext/fr/',

+ 80 - 44
app/views/configure/integration.phtml

@@ -11,21 +11,28 @@
 	<h1><?= _t('conf.sharing') ?></h1>
 
 	<form method="post" action="<?= _url('configure', 'integration') ?>"
-		data-simple='<div class="form-group" id="group-share-##key##"><label class="group-name">##label##</label><div class="group-controls"><div class="stick">
-			<input type="text" id="share_##key##_name" name="share[##key##][name]" class="extend" value="##label##" placeholder="<?= _t('conf.sharing.share_name') ?>" size="64" />
-			<input type="url" id="share_##key##_url" name="share[##key##][url]" class="extend" value="" placeholder="<?= _t('gen.short.not_applicable') ?>" size="64" disabled="disabled" />
-			<a href="#" class="remove btn btn-attention"><?= _i('close') ?></a></div>
-			<input type="hidden" id="share_##key##_type" name="share[##key##][type]" value="##type##" /></div></div>'
-		data-advanced='<div class="form-group" id="group-share-##key##"><label class="group-name">##label##</label><div class="group-controls">
+		data-simple='<formgroup><legend>##label##</legend>
+			<input type="hidden" id="share_##key##_type" name="share[##key##][type]" value="##type##" />
+			<div class="form-group" id="group-share-##key##">
+			<label class="group-name" for="share_##key##_name"><?= _t('conf.sharing.share_name') ?></label><div class="group-controls">
+			<input type="text" id="share_##key##_name" name="share[##key##][name]" value="##label##" />
+			</div>
+			</div></formgroup>'
+		data-advanced='<formgroup class="group-share"><legend>##label##</legend>
 			<input type="hidden" id="share_##key##_type" name="share[##key##][type]" value="##type##" />
 			<input type="hidden" id="share_##key##_method" name="share[##key##][method]" value="##method##" />
 			<input type="hidden" id="share_##key##_field" name="share[##key##][field]" value="##field##" />
-			<div class="stick">
-			<input type="text" id="share_##key##_name" name="share[##key##][name]" class="extend" value="" placeholder="<?= _t('conf.sharing.share_name') ?>" size="64" />
-			<input type="url" id="share_##key##_url" name="share[##key##][url]" class="extend" value="" placeholder="<?= _t('conf.sharing.share_url') ?>" size="64" />
-			<a href="#" class="remove btn btn-attention" title="<?= _t('conf.sharing.remove') ?>"><?= _i('close') ?></a></div>
-			<a target="_blank" rel="noreferrer" class="btn" title="<?= _t('conf.sharing.more_information') ?>" href="##help##"><?= _i('help') ?></a>
-			</div></div>' class="draggableList">
+			<div class="form-group" id="group-share-##key##"><label class="group-name" for="share_##key##_name"><?= _t('conf.sharing.share_name') ?></label><div class="group-controls">
+			<input type="text" id="share_##key##_name" name="share[##key##][name]" value="" />
+			</div>
+			<div class="form-group" id="group-share-##key##"><label class="group-name" for="share_##key##_url"><?= _t('conf.sharing.share_url') ?></label><div class="group-controls">
+			<input type="url" id="share_##key##_url" name="share[##key##][url]" class="long" value="" required />
+			<p class="help"><?= _i('help') ?> <a href="##help##" target="_blank" rel="noreferrer"><?= _t('conf.sharing.more_information') ?></a></p>
+			</div><div class="form-group">
+				<div class="group-controls">
+					<button type="button" class="remove btn btn-attention" title="<?= _t('conf.sharing.remove') ?>"><?= _t('gen.action.remove') ?></button>
+				</div>
+			</div></formgroup>' class="draggableList">
 		<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
 
 		<?php
@@ -33,46 +40,75 @@
 				$share = FreshRSS_Share::get($share_options['type']);
 				$share->update($share_options);
 		?>
-			<div class="form-group group-share" id="group-share-<?= $key ?>" draggable="true">
-				<label class="group-name">
-					<?= $share->name(true) ?>
+		<formgroup class="group-share dragbox" id="group-share-<?= $key ?>">
+			<legend draggable="true"><?= $share->name(true) ?></legend>
+			<input type="hidden" id="share_<?= $key ?>_type" name="share[<?= $key ?>][type]" value="<?= $share->type() ?>" />
+			<input type="hidden" id="share_<?= $key ?>_method" name="share[<?= $key ?>][method]" value="<?= $share->method() ?>" />
+			<input type="hidden" id="share_<?= $key ?>_field" name="share[<?= $key ?>][field]" value="<?= $share->field() ?>" />
+			
+			<?php if ($share->isDeprecated()) { ?>
+			<div class="prompt alert alert-warn">
+				<p><?= _t('conf.sharing.deprecated') ?></p>
+			</div>
+			<?php } ?>
+
+			<div class="form-group">
+				<label class="group-name" for="share_<?= $key ?>_name">
+					<?= _t('conf.sharing.share_name') ?>
 				</label>
 				<div class="group-controls">
-					<input type='hidden' id='share_<?= $key ?>_type' name="share[<?= $key ?>][type]" value='<?= $share->type() ?>' />
-					<input type='hidden' id='share_<?= $key ?>_method' name="share[<?= $key ?>][method]" value='<?= $share->method() ?>' />
-					<input type='hidden' id='share_<?= $key ?>_field' name="share[<?= $key ?>][field]" value='<?= $share->field() ?>' />
-					<div class="stick">
-						<input type="text" id="share_<?= $key ?>_name" name="share[<?= $key ?>][name]" class="extend" value="<?= $share->name() ?>"
-							placeholder="<?= _t('conf.sharing.share_name') ?>" size="64" data-leave-validation="<?= $share->name() ?>"/>
-					<?php if ($share->formType() === 'advanced') { ?>
-						<input type="url" id="share_<?= $key ?>_url" name="share[<?= $key ?>][url]" class="extend" value="<?= $share->baseUrl() ?>"
-							placeholder="<?= _t('conf.sharing.share_url') ?>" size="64" data-leave-validation="<?= $share->baseUrl() ?>"/>
-					<?php } else { ?>
-						<input type="url" id="share_<?= $key ?>_url" name="share[<?= $key ?>][url]" class="extend" value="<?= $share->baseUrl() ?>"
-							placeholder="<?= _t('gen.short.not_applicable') ?>" size="64" disabled="disabled" />
-					<?php } ?>
-						<a href='#' class='remove btn btn-attention' title="<?= _t('conf.sharing.remove') ?>"><?= _i('close') ?></a>
+					<input type="text" id="share_<?= $key ?>_name" name="share[<?= $key ?>][name]" value="<?= $share->name() ?>"
+							data-leave-validation="<?= $share->name() ?>" />
+				</div>
+			</div>
+			
+			<div class="form-group">
+				<?php if ($share->formType() === 'advanced') { ?>
+					<label class="group-name" for="share_<?= $key ?>_url">
+						<?= _t('conf.sharing.share_url') ?>
+					</label>
+					
+					<div class="group-controls">
+						<div class="stick">
+							<input type="url" id="share_<?= $key ?>_url" name="share[<?= $key ?>][url]" class="long" value="<?= $share->baseUrl() ?>"
+								data-leave-validation="<?= $share->baseUrl() ?>" required />
+							<a class="btn open-url" target="_blank" rel="noreferrer" href="<?= $share->baseUrl() ?>" title="<?= _t('gen.action.see_url') ?>" data-input="share_<?= $key ?>_url"><?= _i('link') ?></a>
+						</div>
+						<p class="help"><?= _i('help') ?> <a href="<?= $share->help() ?>" target="_blank" rel="noreferrer"><?= _t('conf.sharing.more_information') ?></a></p>
 					</div>
-					<?php if ($share->formType() === 'advanced') { ?>
-						<a target="_blank" rel="noreferrer" class="btn" title="<?= _t('conf.sharing.more_information') ?>" href="<?= $share->help() ?>"><?= _i('help') ?></a>
-					<?php } ?>
+				<?php } ?>
+			</div>
+			
+			<div class="form-group">
+				<div class="group-controls">
+					<button type="button" class="remove btn btn-attention" title="<?= _t('conf.sharing.remove') ?>"><?= _t('gen.action.remove') ?></button>
 				</div>
 			</div>
+
+
+		</formgroup>
 		<?php } ?>
 
-		<div class="form-group">
-			<div class="group-controls">
-				<select>
-					<?php foreach (FreshRSS_Share::enum() as $share) { ?>
-					<option value='<?= $share->type() ?>' data-form='<?= $share->formType() ?>' data-help='<?= $share->help() ?>'
-						data-method='<?= $share->method() ?>' data-field='<?= $share->field() ?>'>
-						<?= $share->name(true) ?>
-					</option>
-					<?php } ?>
-				</select>
-				<a href='#' class='share add btn' title="<?= _t('conf.sharing.add') ?>"><?= _i('add') ?></a>
+		<formgroup>
+			<legend>
+				<?= _t('conf.sharing.add') ?>
+			</legend>
+			<div class="form-group">
+				<div class="group-controls">
+					<div class="stick">
+						<select>
+							<?php foreach (FreshRSS_Share::enum() as $share) { ?>
+							<option value='<?= $share->type() ?>' data-form='<?= $share->formType() ?>' data-help='<?= $share->help() ?>'
+								data-method='<?= $share->method() ?>' data-field='<?= $share->field() ?>'>
+								<?= $share->name(true) ?>
+							</option>
+							<?php } ?>
+						</select>
+						<a href='#' class='share add btn' title="<?= _t('conf.sharing.add') ?>"><?= _i('add') ?></a>
+					</div>
+				</div>
 			</div>
-		</div>
+		</formgroup>
 
 		<div class="form-group form-actions">
 			<div class="group-controls">

+ 4 - 2
app/views/helpers/index/normal/entry_bottom.phtml

@@ -80,7 +80,8 @@
 			</a>
 
 			<ul class="dropdown-menu">
-				<li class="dropdown-close"><a href="#close">❌</a></li><?php
+				<li class="dropdown-close"><a href="#close">❌</a></li>
+				<li class="dropdown-header"><?= _t('index.share') ?> <a href="<?= _url('configure', 'integration') ?>"><?= _i('configure') ?></a></li><?php
 					$id = $this->entry->id();
 					$link = $this->entry->link();
 					$title = $this->entry->title() . ' · ' . $this->feed->name();
@@ -89,11 +90,12 @@
 						if ($share === null) {
 							continue;
 						}
+						$cssClass = $share->isDeprecated() ? ' error' : '';
 						$share_options['id'] = $id;
 						$share_options['link'] = $link;
 						$share_options['title'] = $title;
 						$share->update($share_options);
-				?><li class="item share">
+				?><li class="item share<?= $cssClass ?>">
 					<?php if ('GET' === $share->method()) {?>
 						<a target="_blank" rel="noreferrer" href="<?= $share->url() ?>" data-type="<?= $share->type() ?>"><?= $share->name() ?></a>
 					<?php } else {?>

+ 1 - 0
docs/en/index.md

@@ -18,6 +18,7 @@ FreshRSS has a lot of features including:
 * The application is "responsive," which means it adapts to small screens so you can bring articles in your pocket
 * Self-hosted: the code is free (under AGPL3 licence), so you can host your own instance of FreshRSS
 * Multi-user, so you can also host for your friends and family
+* share article links with a bunch of services
 * And a lot more!
 
 ## Manual Chapters

+ 50 - 0
docs/en/users/08_sharing_services.md

@@ -0,0 +1,50 @@
+# Sharing Services
+
+FreshRSS has the option to share links with a bunch of services.
+
+## Available Services: Simple Sharing
+
+| Service       | Short description                                      | Notes                                                         |
+|:--------------|:-------------------------------------------------------|:--------------------------------------------------------------|
+| Clipboard     | Copy article link into the operation system clipboard | |
+| Email         | Open the email app to send the article link            | |
+| Print         | Open browser's print dialog to print out the article   | |
+
+## Available Services: Hosted Services
+
+| Service           | Short description                                    | Links                                            | Notes                                                         |
+|:------------------|:-----------------------------------------------------|:-------------------------------------------------|:--------------------------------------------------------------|
+| Blogotext         | A little more than a lightweight SQLite Blog-Engine. | [GitHub](https://github.com/BlogoText/blogotext) | Deprecated since FreshRSS V1.20.0 (2022). Will be deleted in 2023 (scheduled to FreshRSS V1.22.0) |
+| Diaspora*         | The online social world where you are in control     | [Website](https://diasporafoundation.org/), [Wikipedia](https://en.wikipedia.org/wiki/Diaspora_(social_network)) |  |
+| Facebook          | Worldwide social network (by Meta Platforms)         | [Website](https://facebook.com), [Wikipedia](https://en.wikipedia.org/wiki/Facebook)
+| GNU social        | Social communication software for both public and private communications | [Website](https://gnu.io/social/) | |
+| Journal du hacker | Le Journal du hacker s'inspire directement du site anglophone Hacker News | [Website](https://www.journalduhacker.net/) |
+| Known based sites | Its robust open source framework can be used to build fully-fledged community sites, or a blog for a single user. | [Website](https://withknown.com/) | |
+| Lemmy             | Selfhosted social link aggregation and discussion platform | [Website](https://join-lemmy.org/) | |
+| LinkedIn          | Business and employment-oriented online service      | [Website](https://www.linkedin.com/), [Wikipedia](https://en.wikipedia.org/wiki/LinkedIn)| |
+| Mastodon          | Self-hosted social networking & microblogging services | [Website](https://joinmastodon.org/), [Wikipedia](https://en.wikipedia.org/wiki/Mastodon_(software)) | |
+| Movim             | A powerful web frontend for XMPP                     | [Website](https://movim.eu/) | |
+| Pinboard          | Social Bookmarking for Introverts                    | [Website](https://pinboard.in/) | |
+| Pinterest         | Is an image sharing and social media service designed to enable saving and discovery of information| [Website](https://pinterest.com/), [Wikipedia](https://en.wikipedia.org/wiki/Pinterest) | |
+| Pocket            | Social bookmarking (previous "Read it Later", owned by Mozilla) | [Website](https://getpocket.com), [Wikipedia](https://en.wikipedia.org/wiki/Pocket_(service)) | |
+| Raindrop.io       | All-in-one bookmark manager                          | [Website](https://raindrop.io/)| |
+| Reddit            | A network of communities where people can dive into their interests, hobbies and passions| [Website](https://www.reddit.com/), [Wikipedia](https://en.wikipedia.org/wiki/Reddit)| |
+| Shaarli           | Self-hosted minimalist bookmark manager and link sharing service | [Website](https://shaarli.readthedocs.io/) | |
+| Twitter           | Microblogging social network                         | [Website](https://twitter.com), [Wikipedia](https://de.wikipedia.org/wiki/Twitter) | |
+| wallabag          | Save and classify articles. Read them later. Freely  | [Website](https://www.wallabag.org) | Compatible to version 1 and 2
+| Whatsapp          | Instant messaging and voice-over-IP service owned by Meta Platforms| [Website](https://www.whatsapp.com), [Wikipedia](https://en.wikipedia.org/wiki/WhatsApp) | |
+| XING              | Career-oriented social networking site, operated by New Work SE | [Website](https://www.xing.com/), [Wikipedia](https://en.wikipedia.org/wiki/XING) | |
+
+## Configuration
+
+Select the needed sharing services in the configuration menu (Configuration / Sharing).
+
+## Usage
+
+Activate the sharing menu in configuration menu (Configuration / Display). It is only available for the bottom line.
+
+The menu with the selected services is available in the footer of article.
+
+## Add More Sharing Services
+
+Please open a new issue on [GitHub](https://github.com/FreshRSS/FreshRSS/issues) and support us with information.

+ 14 - 2
p/scripts/draggable.js

@@ -27,6 +27,10 @@ const init_draggable_list = function () {
 
 	draggableList.addEventListener('dragstart', event => {
 		source = event.target.closest('[draggable="true"]');
+		const dragbox = source.closest('.dragbox');
+		if (dragbox) {
+			source = dragbox;
+		}
 		event.dataTransfer.setData('text/html', source.outerHTML);
 		event.dataTransfer.effectAllowed = 'move';
 	});
@@ -36,7 +40,11 @@ const init_draggable_list = function () {
 			return;
 		}
 
-		const draggableItem = event.target.closest('[draggable="true"]');
+		let draggableItem = event.target.closest('[draggable="true"]');
+		const dragbox = event.target.closest('.dragbox');
+		if (dragbox) {
+			draggableItem = dragbox;
+		}
 		if (null === draggableItem || source === draggableItem) {
 			return;
 		}
@@ -59,7 +67,11 @@ const init_draggable_list = function () {
 			return;
 		}
 
-		const draggableItem = event.target.closest('[draggable="true"]');
+		let draggableItem = event.target.closest('[draggable="true"]');
+		const dragbox = event.target.closest('.dragbox');
+		if (dragbox) {
+			draggableItem = dragbox;
+		}
 		if (null === draggableItem || source === draggableItem) {
 			return;
 		}

+ 2 - 2
p/scripts/integration.js

@@ -21,7 +21,7 @@ const init_integration = function () {
 		newShare = newShare.replace(/##key##/g, shares);
 		newShare = newShare.replace(/##method##/g, shareType.getAttribute('data-method'));
 		newShare = newShare.replace(/##field##/g, shareType.getAttribute('data-field'));
-		event.target.closest('.form-group').insertAdjacentHTML('beforebegin', newShare);
+		event.target.closest('formgroup').insertAdjacentHTML('beforebegin', newShare);
 		shares++;
 	});
 
@@ -30,7 +30,7 @@ const init_integration = function () {
 			return;
 		}
 
-		const deleteButton = event.target.closest('a.remove');
+		const deleteButton = event.target.closest('.remove');
 		if (null === deleteButton || !deleteButton.closest) {
 			return;
 		}

+ 6 - 1
p/themes/base-theme/template.css

@@ -1530,8 +1530,9 @@ input:checked + .slide-container .properties {
 	text-align: center;
 }
 
+.item.share.error a::after,
 .category .title.error::before {
-	content: "⚠ ";
+	content: " ⚠ ";
 	color: #bd362f;
 }
 
@@ -1672,6 +1673,10 @@ input:checked + .slide-container .properties {
 		position: inherit;
 	}
 
+	.dropdown .dropdown-header {
+		line-height: 2;
+	}
+
 	.dropdown .dropdown-menu {
 		width: 94%;
 		border-radius: 0;

+ 6 - 1
p/themes/base-theme/template.rtl.css

@@ -1530,8 +1530,9 @@ input:checked + .slide-container .properties {
 	text-align: center;
 }
 
+.item.share.error a::after,
 .category .title.error::before {
-	content: "⚠ ";
+	content: " ⚠ ";
 	color: #bd362f;
 }
 
@@ -1672,6 +1673,10 @@ input:checked + .slide-container .properties {
 		position: inherit;
 	}
 
+	.dropdown .dropdown-header {
+		line-height: 2;
+	}
+
 	.dropdown .dropdown-menu {
 		width: 94%;
 		border-radius: 0;