diff --git a/composer.json b/composer.json
index 82ab4d93b..7f4e0a9b8 100644
--- a/composer.json
+++ b/composer.json
@@ -32,12 +32,14 @@
"ezyang/htmlpurifier": "~4.7.0",
"friendica/json-ld": "^1.0",
"league/html-to-markdown": "~4.8.0",
+ "level-2/dice": ">1.0",
"lightopenid/lightopenid": "dev-master",
"michelf/php-markdown": "^1.7",
"mobiledetect/mobiledetectlib": "2.8.*",
"monolog/monolog": "^1.24",
"nikic/fast-route": "^1.3",
"paragonie/hidden-string": "^1.0",
+ "pear/console_table": "^1.3",
"pear/text_languagedetect": "1.*",
"pragmarx/google2fa": "^5.0",
"pragmarx/recovery": "^0.1.0",
@@ -58,8 +60,7 @@
"npm-asset/fullcalendar": "^3.0.1",
"npm-asset/cropperjs": "1.2.2",
"npm-asset/imagesloaded": "4.1.4",
- "pear/console_table": "^1.3",
- "level-2/dice": ">1.0"
+ "npm-asset/typeahead.js": "^0.11.1"
},
"repositories": [
{
diff --git a/composer.lock b/composer.lock
index e1cefeed8..5b9013a0c 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "9f101b2b4a651f425e155d0029223774",
+ "content-hash": "8c88c86ebce0bbf672356d65fadc0008",
"packages": [
{
"name": "asika/simple-console",
@@ -1076,7 +1076,6 @@
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.2.2.tgz",
- "reference": null,
"shasum": "30dc7a7ce872155b23a33bd10ad4c76c0d613f55"
},
"require-dev": {
@@ -1170,7 +1169,6 @@
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/ev-emitter/-/ev-emitter-1.1.1.tgz",
- "reference": null,
"shasum": "8f18b0ce5c76a5d18017f71c0a795c65b9138f2a"
},
"type": "npm-asset-library",
@@ -1213,7 +1211,6 @@
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-3.10.0.tgz",
- "reference": null,
"shasum": "cc5e87d518fd6550e142816a31dd191664847919"
},
"type": "npm-asset-library",
@@ -1260,28 +1257,11 @@
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/imagesloaded/-/imagesloaded-4.1.4.tgz",
- "reference": null,
"shasum": "1376efcd162bb768c34c3727ac89cc04051f3cc7"
},
"require": {
"npm-asset/ev-emitter": ">=1.0.0,<2.0.0"
},
- "require-dev": {
- "npm-asset/chalk": ">=1.1.1,<2.0.0",
- "npm-asset/cheerio": ">=0.19.0,<0.20.0",
- "npm-asset/gulp": ">=3.9.0,<4.0.0",
- "npm-asset/gulp-jshint": ">=1.11.2,<2.0.0",
- "npm-asset/gulp-json-lint": ">=0.1.0,<0.2.0",
- "npm-asset/gulp-rename": ">=1.2.2,<2.0.0",
- "npm-asset/gulp-replace": ">=0.5.4,<0.6.0",
- "npm-asset/gulp-requirejs-optimize": "dev-github:metafizzy/gulp-requirejs-optimize",
- "npm-asset/gulp-uglify": ">=1.4.2,<2.0.0",
- "npm-asset/gulp-util": ">=3.0.7,<4.0.0",
- "npm-asset/highlight.js": ">=8.9.1,<9.0.0",
- "npm-asset/marked": ">=0.3.5,<0.4.0",
- "npm-asset/minimist": ">=1.2.0,<2.0.0",
- "npm-asset/transfob": ">=1.0.0,<2.0.0"
- },
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
@@ -1324,17 +1304,8 @@
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/jgrowl/-/jgrowl-1.4.6.tgz",
- "reference": null,
"shasum": "2736e332aaee73ccf0a14a5f0066391a0a13f4a3"
},
- "require-dev": {
- "npm-asset/grunt": "~0.4.2",
- "npm-asset/grunt-contrib-cssmin": "~0.9.0",
- "npm-asset/grunt-contrib-jshint": "~0.6.3",
- "npm-asset/grunt-contrib-less": "~0.11.0",
- "npm-asset/grunt-contrib-uglify": "~0.4.0",
- "npm-asset/grunt-contrib-watch": "~0.6.1"
- },
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
@@ -1365,35 +1336,8 @@
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz",
- "reference": null,
"shasum": "2c89d6889b5eac522a7eea32c14521559c6cbf02"
},
- "require-dev": {
- "npm-asset/commitplease": "2.0.0",
- "npm-asset/core-js": "0.9.17",
- "npm-asset/grunt": "0.4.5",
- "npm-asset/grunt-babel": "5.0.1",
- "npm-asset/grunt-cli": "0.1.13",
- "npm-asset/grunt-compare-size": "0.4.0",
- "npm-asset/grunt-contrib-jshint": "0.11.2",
- "npm-asset/grunt-contrib-uglify": "0.9.2",
- "npm-asset/grunt-contrib-watch": "0.6.1",
- "npm-asset/grunt-git-authors": "2.0.1",
- "npm-asset/grunt-jscs": "2.1.0",
- "npm-asset/grunt-jsonlint": "1.0.4",
- "npm-asset/grunt-npmcopy": "0.1.0",
- "npm-asset/gzip-js": "0.3.2",
- "npm-asset/jsdom": "5.6.1",
- "npm-asset/load-grunt-tasks": "1.0.0",
- "npm-asset/qunit-assert-step": "1.0.3",
- "npm-asset/qunitjs": "1.17.1",
- "npm-asset/requirejs": "2.1.17",
- "npm-asset/sinon": "1.10.3",
- "npm-asset/sizzle": "2.2.1",
- "npm-asset/strip-json-comments": "1.0.3",
- "npm-asset/testswarm": "1.1.0",
- "npm-asset/win-spawn": "2.0.0"
- },
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
@@ -1436,7 +1380,6 @@
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/jquery-colorbox/-/jquery-colorbox-1.6.4.tgz",
- "reference": null,
"shasum": "799452523a6c494839224ef702e807deb9c06cc5"
},
"require": {
@@ -1483,7 +1426,6 @@
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/jquery-datetimepicker/-/jquery-datetimepicker-2.5.20.tgz",
- "reference": null,
"shasum": "687d6204b90b03dc93f725f8df036e1d061f37ac"
},
"require": {
@@ -1541,15 +1483,8 @@
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz",
- "reference": null,
"shasum": "06f0335f16e353a695e7206bf50503cb523a6ee5"
},
- "require-dev": {
- "npm-asset/grunt": "~0.4.1",
- "npm-asset/grunt-contrib-connect": "~0.5.0",
- "npm-asset/grunt-contrib-jshint": "~0.7.1",
- "npm-asset/grunt-contrib-uglify": "~0.2.7"
- },
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
@@ -1596,7 +1531,6 @@
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
- "reference": null,
"shasum": "3c257f9839fc0e93ff53149632239eb90783ff66"
},
"type": "npm-asset-library",
@@ -1707,6 +1641,58 @@
"homepage": "https://github.com/kartik-v/php-date-formatter",
"time": "2018-07-13T06:56:46+00:00"
},
+ {
+ "name": "npm-asset/typeahead.js",
+ "version": "0.11.1",
+ "dist": {
+ "type": "tar",
+ "url": "https://registry.npmjs.org/typeahead.js/-/typeahead.js-0.11.1.tgz",
+ "shasum": "4e64e671b22310a8606f4aec805924ba84b015b8"
+ },
+ "require": {
+ "npm-asset/jquery": ">=1.7"
+ },
+ "type": "npm-asset-library",
+ "extra": {
+ "npm-asset-bugs": {
+ "url": "https://github.com/twitter/typeahead.js/issues"
+ },
+ "npm-asset-main": "dist/typeahead.bundle.js",
+ "npm-asset-directories": [],
+ "npm-asset-repository": {
+ "type": "git",
+ "url": "https://github.com/twitter/typeahead.js.git"
+ },
+ "npm-asset-scripts": {
+ "test": "./node_modules/karma/bin/karma start --single-run --browsers PhantomJS"
+ }
+ },
+ "authors": [
+ {
+ "name": "Twitter, Inc.",
+ "url": "https://twitter.com/twitteross"
+ },
+ {
+ "name": "Jake Harding",
+ "url": "https://twitter.com/JakeHarding"
+ },
+ {
+ "name": "Tim Trueman",
+ "url": "https://twitter.com/timtrueman"
+ },
+ {
+ "name": "Veljko Skarich",
+ "url": "https://twitter.com/vskarich"
+ }
+ ],
+ "description": "fast and fully-featured autocomplete library",
+ "homepage": "http://twitter.github.com/typeahead.js",
+ "keywords": [
+ "autocomplete",
+ "typeahead"
+ ],
+ "time": "2015-04-27T04:03:42+00:00"
+ },
{
"name": "paragonie/certainty",
"version": "v1.0.4",
@@ -2883,7 +2869,7 @@
"time": "2017-03-25T17:14:26+00:00"
},
{
- "name": "mikey179/vfsStream",
+ "name": "mikey179/vfsstream",
"version": "v1.6.5",
"source": {
"type": "git",
@@ -2920,8 +2906,8 @@
"authors": [
{
"name": "Frank Kleine",
- "homepage": "http://frankkleine.de/",
- "role": "Developer"
+ "role": "Developer",
+ "homepage": "http://frankkleine.de/"
}
],
"description": "Virtual file system to mock the real file system in unit tests.",
@@ -3353,8 +3339,8 @@
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
- "role": "lead"
+ "role": "lead",
+ "email": "sb@sebastian-bergmann.de"
}
],
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
@@ -3621,8 +3607,8 @@
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
+ "role": "lead",
+ "email": "sebastian@phpunit.de"
}
],
"description": "The PHP Unit Testing framework.",
@@ -3795,7 +3781,7 @@
}
],
"description": "Provides the functionality to compare PHP values for equality",
- "homepage": "https://github.com/sebastianbergmann/comparator",
+ "homepage": "http://www.github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
@@ -3897,7 +3883,7 @@
}
],
"description": "Provides functionality to handle HHVM/PHP environments",
- "homepage": "https://github.com/sebastianbergmann/environment",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
"keywords": [
"Xdebug",
"environment",
@@ -3965,7 +3951,7 @@
}
],
"description": "Provides the functionality to export PHP variables for visualization",
- "homepage": "https://github.com/sebastianbergmann/exporter",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
@@ -4017,7 +4003,7 @@
}
],
"description": "Snapshotting of global state",
- "homepage": "https://github.com/sebastianbergmann/global-state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
@@ -4119,7 +4105,7 @@
}
],
"description": "Provides functionality to recursively process PHP variables",
- "homepage": "https://github.com/sebastianbergmann/recursion-context",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"time": "2016-11-19T07:33:16+00:00"
},
{
@@ -4209,16 +4195,16 @@
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.11.0",
+ "version": "v1.9.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "82ebae02209c21113908c229e9883c419720738a"
+ "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
- "reference": "82ebae02209c21113908c229e9883c419720738a",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
+ "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
"shasum": ""
},
"require": {
@@ -4230,7 +4216,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.11-dev"
+ "dev-master": "1.9-dev"
}
},
"autoload": {
@@ -4263,20 +4249,20 @@
"polyfill",
"portable"
],
- "time": "2019-02-06T07:57:58+00:00"
+ "time": "2018-08-06T14:22:27+00:00"
},
{
"name": "symfony/yaml",
- "version": "v3.4.30",
+ "version": "v3.4.16",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "051d045c684148060ebfc9affb7e3f5e0899d40b"
+ "reference": "61973ecda60e9f3561e929e19c07d4878b960fc1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/051d045c684148060ebfc9affb7e3f5e0899d40b",
- "reference": "051d045c684148060ebfc9affb7e3f5e0899d40b",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/61973ecda60e9f3561e929e19c07d4878b960fc1",
+ "reference": "61973ecda60e9f3561e929e19c07d4878b960fc1",
"shasum": ""
},
"require": {
@@ -4322,7 +4308,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2019-07-24T13:01:31+00:00"
+ "time": "2018-09-24T08:15:45+00:00"
},
{
"name": "webmozart/assert",
diff --git a/include/text.php b/include/text.php
index 7bed8b499..3d6bf6a56 100644
--- a/include/text.php
+++ b/include/text.php
@@ -4,11 +4,11 @@
*/
use Friendica\App;
-use Friendica\Content\Smilies;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Protocol;
use Friendica\Model\Contact;
use Friendica\Model\FileTag;
+use Friendica\Model\Group;
use Friendica\Util\Strings;
/**
@@ -20,18 +20,9 @@ use Friendica\Util\Strings;
function expand_acl($s) {
// turn string array of angle-bracketed elements into numeric array
// e.g. "<1><2><3>" => array(1,2,3);
- $ret = [];
+ preg_match_all('/<(' . Group::FOLLOWERS . '|'. Group::MUTUALS . '|[0-9]+)>/', $s, $matches, PREG_PATTERN_ORDER);
- if (strlen($s)) {
- $t = str_replace('<', '', $s);
- $a = explode('>', $t);
- foreach ($a as $aa) {
- if (intval($aa)) {
- $ret[] = intval($aa);
- }
- }
- }
- return $ret;
+ return $matches[1];
}
@@ -42,6 +33,8 @@ function expand_acl($s) {
function sanitise_acl(&$item) {
if (intval($item)) {
$item = '<' . intval(Strings::escapeTags(trim($item))) . '>';
+ } elseif (in_array($item, [Group::FOLLOWERS, Group::MUTUALS])) {
+ $item = '<' . $item . '>';
} else {
unset($item);
}
diff --git a/mod/lockview.php b/mod/lockview.php
index 35c4b0433..eede1b6a0 100644
--- a/mod/lockview.php
+++ b/mod/lockview.php
@@ -6,6 +6,7 @@ use Friendica\App;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Database\DBA;
+use Friendica\Model\Group;
use Friendica\Model\Item;
function lockview_content(App $a)
@@ -67,6 +68,19 @@ function lockview_content(App $a)
$l = [];
if (count($allowed_groups)) {
+ $key = array_search(Group::FOLLOWERS, $allowed_groups);
+ if ($key !== false) {
+ $l[] = '' . L10n::t('Followers') . '';
+ unset($allowed_groups[$key]);
+ }
+
+ $key = array_search(Group::MUTUALS, $allowed_groups);
+ if ($key !== false) {
+ $l[] = '' . L10n::t('Mutuals') . '';
+ unset($allowed_groups[$key]);
+ }
+
+
$r = q("SELECT `name` FROM `group` WHERE `id` IN ( %s )",
DBA::escape(implode(', ', $allowed_groups))
);
@@ -89,6 +103,18 @@ function lockview_content(App $a)
}
if (count($deny_groups)) {
+ $key = array_search(Group::FOLLOWERS, $deny_groups);
+ if ($key !== false) {
+ $l[] = '' . L10n::t('Followers') . '';
+ unset($deny_groups[$key]);
+ }
+
+ $key = array_search(Group::MUTUALS, $deny_groups);
+ if ($key !== false) {
+ $l[] = '' . L10n::t('Mutuals') . '';
+ unset($deny_groups[$key]);
+ }
+
$r = q("SELECT `name` FROM `group` WHERE `id` IN ( %s )",
DBA::escape(implode(', ', $deny_groups))
);
diff --git a/mod/network.php b/mod/network.php
index 735466720..fddec60c8 100644
--- a/mod/network.php
+++ b/mod/network.php
@@ -643,7 +643,7 @@ function networkThreadedView(App $a, $update, $parent)
// NOTREACHED
}
- $contacts = Group::expand([$gid]);
+ $contacts = Group::expand(local_user(), [$gid]);
if ((is_array($contacts)) && count($contacts)) {
$contact_str_self = '';
diff --git a/src/App/Router.php b/src/App/Router.php
index a54f3a711..50b208792 100644
--- a/src/App/Router.php
+++ b/src/App/Router.php
@@ -94,6 +94,7 @@ class Router
$this->routeCollector->addRoute(['GET'], '/attach/{item:\d+}', Module\Attach::class);
$this->routeCollector->addRoute(['GET'], '/babel', Module\Debug\Babel::class);
$this->routeCollector->addRoute(['GET'], '/bookmarklet', Module\Bookmarklet::class);
+ $this->routeCollector->addRoute(['GET', 'POST'], '/compose[/{type}]', Module\Item\Compose::class);
$this->routeCollector->addGroup('/contact', function (RouteCollector $collector) {
$collector->addRoute(['GET'], '[/]', Module\Contact::class);
$collector->addRoute(['GET', 'POST'], '/{id:\d+}[/]', Module\Contact::class);
diff --git a/src/Core/Protocol.php b/src/Core/Protocol.php
index 759a6e70e..0ecc076a0 100644
--- a/src/Core/Protocol.php
+++ b/src/Core/Protocol.php
@@ -25,6 +25,8 @@ class Protocol
const FEDERATED = [self::DFRN, self::DIASPORA, self::OSTATUS, self::ACTIVITYPUB];
+ const SUPPORT_PRIVATE = [self::DFRN, self::DIASPORA, self::MAIL, self::ACTIVITYPUB, self::PUMPIO];
+
// Supported through a connector
const DIASPORA2 = 'dspc'; // Diaspora connector
const LINKEDIN = 'lnkd'; // LinkedIn
diff --git a/src/Model/Group.php b/src/Model/Group.php
index feff4661a..50160baaf 100644
--- a/src/Model/Group.php
+++ b/src/Model/Group.php
@@ -2,12 +2,14 @@
/**
* @file src/Model/Group.php
*/
+
namespace Friendica\Model;
use Friendica\BaseModule;
use Friendica\BaseObject;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
+use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
@@ -16,9 +18,21 @@ use Friendica\Database\DBA;
*/
class Group extends BaseObject
{
+ const FOLLOWERS = '~';
+ const MUTUALS = '&';
+
+ public static function getByUserId($uid, $includesDeleted = false)
+ {
+ $conditions = ['uid' => $uid];
+
+ if (!$includesDeleted) {
+ $conditions['deleted'] = false;
+ }
+
+ return DBA::selectToArray('group', [], $conditions);
+ }
+
/**
- *
- *
* @param int $group_id
* @return bool
* @throws \Exception
@@ -76,8 +90,8 @@ class Group extends BaseObject
/**
* Update group information.
*
- * @param int $id Group ID
- * @param string $name Group name
+ * @param int $id Group ID
+ * @param string $name Group name
*
* @return bool Was the update successful?
* @throws \Exception
@@ -96,14 +110,13 @@ class Group extends BaseObject
*/
public static function getIdsByContactId($cid)
{
- $condition = ['contact-id' => $cid];
- $stmt = DBA::select('group_member', ['gid'], $condition);
-
$return = [];
+ $stmt = DBA::select('group_member', ['gid'], ['contact-id' => $cid]);
while ($group = DBA::fetch($stmt)) {
$return[] = $group['gid'];
}
+ DBA::close($stmt);
return $return;
}
@@ -170,8 +183,9 @@ class Group extends BaseObject
* @return boolean
* @throws \Exception
*/
- public static function remove($gid) {
- if (! $gid) {
+ public static function remove($gid)
+ {
+ if (!$gid) {
return false;
}
@@ -215,14 +229,15 @@ class Group extends BaseObject
/**
* @brief Mark a group as deleted based on its name
*
- * @deprecated Use Group::remove instead
- *
* @param int $uid
* @param string $name
* @return bool
* @throws \Exception
+ * @deprecated Use Group::remove instead
+ *
*/
- public static function removeByName($uid, $name) {
+ public static function removeByName($uid, $name)
+ {
$return = false;
if (!empty($uid) && !empty($name)) {
$gid = self::getIdByName($uid, $name);
@@ -280,13 +295,13 @@ class Group extends BaseObject
/**
* @brief Removes a contact from a group based on its name
*
- * @deprecated Use Group::removeMember instead
- *
* @param int $uid
* @param string $name
* @param int $cid
* @return boolean
* @throws \Exception
+ * @deprecated Use Group::removeMember instead
+ *
*/
public static function removeMemberByName($uid, $name, $cid)
{
@@ -300,23 +315,55 @@ class Group extends BaseObject
/**
* @brief Returns the combined list of contact ids from a group id list
*
+ * @param int $uid
* @param array $group_ids
* @param boolean $check_dead
* @return array
* @throws \Exception
*/
- public static function expand($group_ids, $check_dead = false)
+ public static function expand($uid, array $group_ids, $check_dead = false)
{
if (!is_array($group_ids) || !count($group_ids)) {
return [];
}
- $stmt = DBA::select('group_member', ['contact-id'], ['gid' => $group_ids]);
-
$return = [];
- while($group_member = DBA::fetch($stmt)) {
+
+ $key = array_search(self::FOLLOWERS, $group_ids);
+ if ($key !== false) {
+ $followers = Contact::selectToArray(['id'], [
+ 'uid' => $uid,
+ 'rel' => [Contact::FOLLOWER, Contact::FRIEND],
+ 'protocol' => Protocol::SUPPORT_PRIVATE,
+ ]);
+
+ foreach ($followers as $follower) {
+ $return[] = $follower['id'];
+ }
+
+ unset($group_ids[$key]);
+ }
+
+ $key = array_search(self::MUTUALS, $group_ids);
+ if ($key !== false) {
+ $mutuals = Contact::selectToArray(['id'], [
+ 'uid' => $uid,
+ 'rel' => [Contact::FRIEND],
+ 'protocol' => Protocol::SUPPORT_PRIVATE,
+ ]);
+
+ foreach ($mutuals as $mutual) {
+ $return[] = $mutual['id'];
+ }
+
+ unset($group_ids[$key]);
+ }
+
+ $stmt = DBA::select('group_member', ['contact-id'], ['gid' => $group_ids]);
+ while ($group_member = DBA::fetch($stmt)) {
$return[] = $group_member['contact-id'];
}
+ DBA::close($stmt);
if ($check_dead) {
Contact::pruneUnavailable($return);
@@ -332,12 +379,10 @@ class Group extends BaseObject
* @param int $gid An optional pre-selected group
* @param string $label An optional label of the list
* @return string
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ * @throws \Exception
*/
public static function displayGroupSelection($uid, $gid = 0, $label = '')
{
- $stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => $uid], ['order' => ['name']]);
-
$display_groups = [
[
'name' => '',
@@ -345,6 +390,8 @@ class Group extends BaseObject
'selected' => ''
]
];
+
+ $stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => $uid], ['order' => ['name']]);
while ($group = DBA::fetch($stmt)) {
$display_groups[] = [
'name' => $group['name'],
@@ -352,7 +399,9 @@ class Group extends BaseObject
'selected' => $gid == $group['id'] ? 'true' : ''
];
}
- Logger::log('groups: ' . print_r($display_groups, true));
+ DBA::close($stmt);
+
+ Logger::info('Got groups', $display_groups);
if ($label == '') {
$label = L10n::t('Default privacy group for new contacts');
@@ -377,7 +426,7 @@ class Group extends BaseObject
* @param string $group_id
* @param int $cid
* @return string
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ * @throws \Exception
*/
public static function sidebarWidget($every = 'contact', $each = 'group', $editmode = 'standard', $group_id = '', $cid = 0)
{
@@ -394,13 +443,12 @@ class Group extends BaseObject
]
];
- $stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => local_user()], ['order' => ['name']]);
-
$member_of = [];
if ($cid) {
$member_of = self::getIdsByContactId($cid);
}
+ $stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => local_user()], ['order' => ['name']]);
while ($group = DBA::fetch($stmt)) {
$selected = (($group_id == $group['id']) ? ' group-selected' : '');
@@ -423,6 +471,7 @@ class Group extends BaseObject
'ismember' => in_array($group['id'], $member_of),
];
}
+ DBA::close($stmt);
// Don't show the groups on the network page when there is only one
if ((count($display_groups) <= 2) && ($each == 'network')) {
@@ -445,7 +494,6 @@ class Group extends BaseObject
'$form_security_token' => BaseModule::getFormSecurityToken('group_edit'),
]);
-
return $o;
}
}
diff --git a/src/Model/Item.php b/src/Model/Item.php
index ef461fe65..01c2c3459 100644
--- a/src/Model/Item.php
+++ b/src/Model/Item.php
@@ -1246,7 +1246,6 @@ class Item extends BaseObject
return $contact_id;
}
}
-
return $item['author-id'];
}
@@ -1472,6 +1471,9 @@ class Item extends BaseObject
$item['plink'] = defaults($item, 'plink', System::baseUrl() . '/display/' . urlencode($item['guid']));
+ // The contact-id should be set before "self::insert" was called - but there seems to be issues sometimes
+ $item["contact-id"] = self::contactId($item);
+
$default = ['url' => $item['author-link'], 'name' => $item['author-name'],
'photo' => $item['author-avatar'], 'network' => $item['network']];
@@ -1527,9 +1529,6 @@ class Item extends BaseObject
unset($item['causer-id']);
unset($item['causer-link']);
- // The contact-id should be set before "self::insert" was called - but there seems to be issues sometimes
- $item['contact-id'] = self::contactId($item);
-
if ($item['network'] == Protocol::PHANTOM) {
$item['network'] = Protocol::DFRN;
Logger::notice('Missing network, setting to {network}.', [
@@ -2804,7 +2803,7 @@ class Item extends BaseObject
$replace = true;
}
} elseif ($item) {
- if (self::samePermissions($item, $photo)) {
+ if (self::samePermissions($uid, $item, $photo)) {
$replace = true;
}
}
@@ -2854,7 +2853,7 @@ class Item extends BaseObject
!empty($obj['deny_cid']) || !empty($obj['deny_gid']);
}
- private static function samePermissions($obj1, $obj2)
+ private static function samePermissions($uid, $obj1, $obj2)
{
// first part is easy. Check that these are exactly the same.
if (($obj1['allow_cid'] == $obj2['allow_cid'])
@@ -2875,12 +2874,12 @@ class Item extends BaseObject
}
// returns an array of contact-ids that are allowed to see this object
- public static function enumeratePermissions($obj)
+ public static function enumeratePermissions(array $obj)
{
$allow_people = expand_acl($obj['allow_cid']);
- $allow_groups = Group::expand(expand_acl($obj['allow_gid']));
+ $allow_groups = Group::expand($obj['uid'], expand_acl($obj['allow_gid']));
$deny_people = expand_acl($obj['deny_cid']);
- $deny_groups = Group::expand(expand_acl($obj['deny_gid']));
+ $deny_groups = Group::expand($obj['uid'], expand_acl($obj['deny_gid']));
$recipients = array_unique(array_merge($allow_people, $allow_groups));
$deny = array_unique(array_merge($deny_people, $deny_groups));
$recipients = array_diff($recipients, $deny);
diff --git a/src/Module/Item/Compose.php b/src/Module/Item/Compose.php
new file mode 100644
index 000000000..09668af23
--- /dev/null
+++ b/src/Module/Item/Compose.php
@@ -0,0 +1,217 @@
+getCurrentTheme() !== 'frio') {
+ throw new NotImplementedException(L10n::t('This feature is only available with the frio theme.'));
+ }
+
+ /// @TODO Retrieve parameter from router
+ $posttype = $a->argv[1] ?? Item::PT_ARTICLE;
+ if (!in_array($posttype, [Item::PT_ARTICLE, Item::PT_PERSONAL_NOTE])) {
+ switch ($posttype) {
+ case 'note':
+ $posttype = Item::PT_PERSONAL_NOTE;
+ break;
+ default:
+ $posttype = Item::PT_ARTICLE;
+ break;
+ }
+ }
+
+ $user = User::getById(local_user(), ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'hidewall', 'default-location']);
+
+ switch ($posttype) {
+ case Item::PT_PERSONAL_NOTE:
+ $compose_title = L10n::t('Compose new personal note');
+ $type = 'note';
+ $doesFederate = false;
+ $contact_allow = $a->contact['id'];
+ $group_allow = '';
+ break;
+ default:
+ $compose_title = L10n::t('Compose new post');
+ $type = 'post';
+ $doesFederate = true;
+ $contact_allow = implode(',', expand_acl($user['allow_cid']));
+ $group_allow = implode(',', expand_acl($user['allow_gid'])) ?: Group::FOLLOWERS;
+ break;
+ }
+
+ $title = $_REQUEST['title'] ?? '';
+ $category = $_REQUEST['category'] ?? '';
+ $body = $_REQUEST['body'] ?? '';
+ $location = $_REQUEST['location'] ?? $user['default-location'];
+ $wall = $_REQUEST['wall'] ?? $type == 'post';
+ $contact_allow = $_REQUEST['contact_allow'] ?? $contact_allow;
+ $group_allow = $_REQUEST['group_allow'] ?? $group_allow;
+ $contact_deny = $_REQUEST['contact_deny'] ?? implode(',', expand_acl($user['deny_cid']));
+ $group_deny = $_REQUEST['group_deny'] ?? implode(',', expand_acl($user['deny_gid']));
+ $visibility = ($contact_allow . $user['allow_gid'] . $user['deny_cid'] . $user['deny_gid']) ? 'custom' : 'public';
+
+ $acl_contacts = Contact::selectToArray(['id', 'name', 'addr', 'micro'], ['uid' => local_user(), 'pending' => false, 'rel' => [Contact::FOLLOWER, Contact::FRIEND]]);
+ array_walk($acl_contacts, function (&$value) {
+ $value['type'] = 'contact';
+ });
+
+ $acl_groups = [
+ [
+ 'id' => Group::FOLLOWERS,
+ 'name' => L10n::t('Followers'),
+ 'addr' => '',
+ 'micro' => 'images/twopeople.png',
+ 'type' => 'group',
+ ],
+ [
+ 'id' => Group::MUTUALS,
+ 'name' => L10n::t('Mutuals'),
+ 'addr' => '',
+ 'micro' => 'images/twopeople.png',
+ 'type' => 'group',
+ ]
+ ];
+ foreach (Group::getByUserId(local_user()) as $group) {
+ $acl_groups[] = [
+ 'id' => $group['id'],
+ 'name' => $group['name'],
+ 'addr' => '',
+ 'micro' => 'images/twopeople.png',
+ 'type' => 'group',
+ ];
+ }
+
+ $acl = array_merge($acl_groups, $acl_contacts);
+
+ $jotnets_fields = [];
+ $mail_enabled = false;
+ $pubmail_enabled = false;
+ if (function_exists('imap_open') && !Config::get('system', 'imap_disabled')) {
+ $mailacct = DBA::selectFirst('mailacct', ['pubmail'], ['`uid` = ? AND `server` != ""', local_user()]);
+ if (DBA::isResult($mailacct)) {
+ $mail_enabled = true;
+ $pubmail_enabled = !empty($mailacct['pubmail']);
+ }
+ }
+
+ if (empty($user['hidewall'])) {
+ if ($mail_enabled) {
+ $jotnets_fields[] = [
+ 'type' => 'checkbox',
+ 'field' => [
+ 'pubmail_enable',
+ L10n::t('Post to Email'),
+ $pubmail_enabled
+ ]
+ ];
+ }
+
+ Hook::callAll('jot_networks', $jotnets_fields);
+ }
+
+ $jotplugins = '';
+ Hook::callAll('jot_tool', $jotplugins);
+
+ // Output
+
+ $a->registerFooterScript('view/js/ajaxupload.js');
+ $a->registerFooterScript('view/js/linkPreview.js');
+ $a->registerFooterScript('view/asset/typeahead.js/dist/typeahead.bundle.js');
+ $a->registerFooterScript('view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.js');
+ $a->registerStylesheet('view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.css');
+ $a->registerStylesheet('view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput-typeahead.css');
+
+ $tpl = Renderer::getMarkupTemplate('item/compose-footer.tpl');
+ $a->page['footer'] .= Renderer::replaceMacros($tpl, [
+ '$acl_contacts' => $acl_contacts,
+ '$acl_groups' => $acl_groups,
+ '$acl' => $acl,
+ ]);
+
+ $tpl = Renderer::getMarkupTemplate('item/compose.tpl');
+ return Renderer::replaceMacros($tpl, [
+ '$compose_title'=> $compose_title,
+ '$id' => 0,
+ '$posttype' => $posttype,
+ '$type' => $type,
+ '$wall' => $wall,
+ '$default' => L10n::t(''),
+ '$mylink' => $a->removeBaseURL($a->contact['url']),
+ '$mytitle' => L10n::t('This is you'),
+ '$myphoto' => $a->removeBaseURL($a->contact['thumb']),
+ '$submit' => L10n::t('Submit'),
+ '$edbold' => L10n::t('Bold'),
+ '$editalic' => L10n::t('Italic'),
+ '$eduline' => L10n::t('Underline'),
+ '$edquote' => L10n::t('Quote'),
+ '$edcode' => L10n::t('Code'),
+ '$edimg' => L10n::t('Image'),
+ '$edurl' => L10n::t('Link'),
+ '$edattach' => L10n::t('Link or Media'),
+ '$prompttext' => L10n::t('Please enter a image/video/audio/webpage URL:'),
+ '$preview' => L10n::t('Preview'),
+ '$location_set' => L10n::t('Set your location'),
+ '$location_clear' => L10n::t('Clear the location'),
+ '$location_unavailable' => L10n::t('Location services are unavailable on your device'),
+ '$location_disabled' => L10n::t('Location services are disabled. Please check the website\'s permissions on your device'),
+ '$wait' => L10n::t('Please wait'),
+ '$placeholdertitle' => L10n::t('Set title'),
+ '$placeholdercategory' => (Feature::isEnabled(local_user(),'categories') ? L10n::t('Categories (comma-separated list)') : ''),
+ '$public_title' => L10n::t('Public'),
+ '$public_desc' => L10n::t('This post will be sent to all your followers and can be seen in the community pages and by anyone with its link.'),
+ '$custom_title' => L10n::t('Limited/Private'),
+ '$custom_desc' => L10n::t('This post will be sent only to the people in the first box, to the exception of the people mentioned in the second box. It won\'t appear anywhere public.'),
+ '$emailcc' => L10n::t('CC: email addresses'),
+ '$title' => $title,
+ '$category' => $category,
+ '$body' => $body,
+ '$location' => $location,
+ '$visibility' => $visibility,
+ '$contact_allow'=> $contact_allow,
+ '$group_allow' => $group_allow,
+ '$contact_deny' => $contact_deny,
+ '$group_deny' => $group_deny,
+ '$jotplugins' => $jotplugins,
+ '$doesFederate' => $doesFederate,
+ '$jotnets_fields'=> $jotnets_fields,
+ '$sourceapp' => L10n::t($a->sourcename),
+ '$rand_num' => Crypto::randomDigits(12)
+ ]);
+ }
+}
diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php
index 65dd6d3e6..7639d0f2a 100644
--- a/src/Protocol/ActivityPub/Processor.php
+++ b/src/Protocol/ActivityPub/Processor.php
@@ -419,8 +419,8 @@ class Processor
$item['contact-id'] = Contact::getIdForURL($activity['author'], $receiver, true);
}
- if (empty($item['contact-id'])) {
- $item['contact-id'] = $item['author-id'];
+ if (($receiver != 0) && empty($item['contact-id'])) {
+ $item['contact-id'] = Contact::getIdForURL($activity['author'], 0, true);
}
if (!empty($activity['directmessage'])) {
diff --git a/src/Worker/Notifier.php b/src/Worker/Notifier.php
index 42ad0afb6..4efd3e912 100644
--- a/src/Worker/Notifier.php
+++ b/src/Worker/Notifier.php
@@ -271,9 +271,9 @@ class Notifier
}
$allow_people = expand_acl($parent['allow_cid']);
- $allow_groups = Group::expand(expand_acl($parent['allow_gid']),true);
+ $allow_groups = Group::expand($uid, expand_acl($parent['allow_gid']),true);
$deny_people = expand_acl($parent['deny_cid']);
- $deny_groups = Group::expand(expand_acl($parent['deny_gid']));
+ $deny_groups = Group::expand($uid, expand_acl($parent['deny_gid']));
// if our parent is a public forum (forum_mode == 1), uplink to the origional author causing
// a delivery fork. private groups (forum_mode == 2) do not uplink
diff --git a/tests/include/TextTest.php b/tests/include/TextTest.php
index e516fe824..5676da8f6 100644
--- a/tests/include/TextTest.php
+++ b/tests/include/TextTest.php
@@ -5,6 +5,7 @@
namespace Friendica\Test;
+use Friendica\Model\Group;
use PHPUnit\Framework\TestCase;
/**
@@ -55,8 +56,8 @@ class TextTest extends TestCase
*/
public function testExpandAclNormal()
{
- $text='<1><2><3>';
- $this->assertEquals(array(1, 2, 3), expand_acl($text));
+ $text='<1><2><3><' . Group::FOLLOWERS . '><' . Group::MUTUALS . '>';
+ $this->assertEquals(array('1', '2', '3', Group::FOLLOWERS, Group::MUTUALS), expand_acl($text));
}
/**
@@ -64,8 +65,8 @@ class TextTest extends TestCase
*/
public function testExpandAclBigNumber()
{
- $text='<1><'.PHP_INT_MAX.'><15>';
- $this->assertEquals(array(1, PHP_INT_MAX, 15), expand_acl($text));
+ $text='<1><' . PHP_INT_MAX . '><15>';
+ $this->assertEquals(array('1', (string)PHP_INT_MAX, '15'), expand_acl($text));
}
/**
@@ -76,7 +77,7 @@ class TextTest extends TestCase
public function testExpandAclString()
{
$text="<1><279012>";
- $this->assertEquals(array(1, 279012), expand_acl($text));
+ $this->assertEquals(array('1', '279012'), expand_acl($text));
}
/**
@@ -87,7 +88,7 @@ class TextTest extends TestCase
public function testExpandAclSpace()
{
$text="<1><279 012><32>";
- $this->assertEquals(array(1, "279", "32"), expand_acl($text));
+ $this->assertEquals(array('1', '32'), expand_acl($text));
}
/**
@@ -174,7 +175,7 @@ class TextTest extends TestCase
public function testExpandAclEmptyMatch()
{
$text="<1><><3>";
- $this->assertEquals(array(1,3), expand_acl($text));
+ $this->assertEquals(array('1', '3'), expand_acl($text));
}
/**
diff --git a/view/js/main.js b/view/js/main.js
index a2e2698a8..8b1303e7d 100644
--- a/view/js/main.js
+++ b/view/js/main.js
@@ -632,7 +632,6 @@ function post_comment(id) {
unpause();
commentBusy = true;
$('body').css('cursor', 'wait');
- $("#comment-preview-inp-" + id).val("0");
$.post(
"item",
$("#comment-edit-form-" + id).serialize(),
@@ -661,11 +660,10 @@ function post_comment(id) {
}
function preview_comment(id) {
- $("#comment-preview-inp-" + id).val("1");
$("#comment-edit-preview-" + id).show();
$.post(
"item",
- $("#comment-edit-form-" + id).serialize(),
+ $("#comment-edit-form-" + id).serialize() + '&preview=1',
function(data) {
if (data.preview) {
$("#comment-edit-preview-" + id).html(data.preview);
diff --git a/view/templates/comment_item.tpl b/view/templates/comment_item.tpl
index 352ff417f..977a5b75b 100644
--- a/view/templates/comment_item.tpl
+++ b/view/templates/comment_item.tpl
@@ -10,7 +10,6 @@
{{**}}
-
diff --git a/view/templates/item/compose-footer.tpl b/view/templates/item/compose-footer.tpl
new file mode 100644
index 000000000..697be6fd1
--- /dev/null
+++ b/view/templates/item/compose-footer.tpl
@@ -0,0 +1,251 @@
+
diff --git a/view/templates/item/compose.tpl b/view/templates/item/compose.tpl
new file mode 100644
index 000000000..d18b1a2d3
--- /dev/null
+++ b/view/templates/item/compose.tpl
@@ -0,0 +1,156 @@
+
+
{{$compose_title}}
+
+
+
+
\ No newline at end of file
diff --git a/view/templates/moderated_comment.tpl b/view/templates/moderated_comment.tpl
index d52bf172a..39dbbde47 100644
--- a/view/templates/moderated_comment.tpl
+++ b/view/templates/moderated_comment.tpl
@@ -6,7 +6,6 @@
-
diff --git a/view/theme/frio/config.php b/view/theme/frio/config.php
index 0f3eda4c9..df0f65a6b 100644
--- a/view/theme/frio/config.php
+++ b/view/theme/frio/config.php
@@ -16,15 +16,16 @@ function theme_post(App $a)
}
if (isset($_POST['frio-settings-submit'])) {
- PConfig::set(local_user(), 'frio', 'scheme', defaults($_POST, 'frio_scheme', ''));
- PConfig::set(local_user(), 'frio', 'nav_bg', defaults($_POST, 'frio_nav_bg', ''));
- PConfig::set(local_user(), 'frio', 'nav_icon_color', defaults($_POST, 'frio_nav_icon_color', ''));
- PConfig::set(local_user(), 'frio', 'link_color', defaults($_POST, 'frio_link_color', ''));
- PConfig::set(local_user(), 'frio', 'background_color', defaults($_POST, 'frio_background_color', ''));
- PConfig::set(local_user(), 'frio', 'contentbg_transp', defaults($_POST, 'frio_contentbg_transp', ''));
- PConfig::set(local_user(), 'frio', 'background_image', defaults($_POST, 'frio_background_image', ''));
- PConfig::set(local_user(), 'frio', 'bg_image_option', defaults($_POST, 'frio_bg_image_option', ''));
+ PConfig::set(local_user(), 'frio', 'scheme', $_POST['frio_scheme'] ?? '');
+ PConfig::set(local_user(), 'frio', 'nav_bg', $_POST['frio_nav_bg'] ?? '');
+ PConfig::set(local_user(), 'frio', 'nav_icon_color', $_POST['frio_nav_icon_color'] ?? '');
+ PConfig::set(local_user(), 'frio', 'link_color', $_POST['frio_link_color'] ?? '');
+ PConfig::set(local_user(), 'frio', 'background_color', $_POST['frio_background_color'] ?? '');
+ PConfig::set(local_user(), 'frio', 'contentbg_transp', $_POST['frio_contentbg_transp'] ?? '');
+ PConfig::set(local_user(), 'frio', 'background_image', $_POST['frio_background_image'] ?? '');
+ PConfig::set(local_user(), 'frio', 'bg_image_option', $_POST['frio_bg_image_option'] ?? '');
PConfig::set(local_user(), 'frio', 'css_modified', time());
+ PConfig::set(local_user(), 'frio', 'enable_compose', $_POST['frio_enable_compose'] ?? 0);
}
}
@@ -35,17 +36,18 @@ function theme_admin_post(App $a)
}
if (isset($_POST['frio-settings-submit'])) {
- Config::set('frio', 'scheme', defaults($_POST, 'frio_scheme', ''));
- Config::set('frio', 'nav_bg', defaults($_POST, 'frio_nav_bg', ''));
- Config::set('frio', 'nav_icon_color', defaults($_POST, 'frio_nav_icon_color', ''));
- Config::set('frio', 'link_color', defaults($_POST, 'frio_link_color', ''));
- Config::set('frio', 'background_color', defaults($_POST, 'frio_background_color', ''));
- Config::set('frio', 'contentbg_transp', defaults($_POST, 'frio_contentbg_transp', ''));
- Config::set('frio', 'background_image', defaults($_POST, 'frio_background_image', ''));
- Config::set('frio', 'bg_image_option', defaults($_POST, 'frio_bg_image_option', ''));
- Config::set('frio', 'login_bg_image', defaults($_POST, 'frio_login_bg_image', ''));
- Config::set('frio', 'login_bg_color', defaults($_POST, 'frio_login_bg_color', ''));
+ Config::set('frio', 'scheme', $_POST['frio_scheme'] ?? '');
+ Config::set('frio', 'nav_bg', $_POST['frio_nav_bg'] ?? '');
+ Config::set('frio', 'nav_icon_color', $_POST['frio_nav_icon_color'] ?? '');
+ Config::set('frio', 'link_color', $_POST['frio_link_color'] ?? '');
+ Config::set('frio', 'background_color', $_POST['frio_background_color'] ?? '');
+ Config::set('frio', 'contentbg_transp', $_POST['frio_contentbg_transp'] ?? '');
+ Config::set('frio', 'background_image', $_POST['frio_background_image'] ?? '');
+ Config::set('frio', 'bg_image_option', $_POST['frio_bg_image_option'] ?? '');
+ Config::set('frio', 'login_bg_image', $_POST['frio_login_bg_image'] ?? '');
+ Config::set('frio', 'login_bg_color', $_POST['frio_login_bg_color'] ?? '');
Config::set('frio', 'css_modified', time());
+ Config::set('frio', 'enable_compose', $_POST['frio_enable_compose'] ?? 0);
}
}
@@ -67,6 +69,7 @@ function theme_content(App $a)
$arr['contentbg_transp'] = PConfig::get(local_user(), 'frio', 'contentbg_transp', Config::get('frio', 'contentbg_transp'));
$arr['background_image'] = PConfig::get(local_user(), 'frio', 'background_image', Config::get('frio', 'background_image'));
$arr['bg_image_option'] = PConfig::get(local_user(), 'frio', 'bg_image_option' , Config::get('frio', 'bg_image_option'));
+ $arr['enable_compose'] = PConfig::get(local_user(), 'frio', 'enable_compose' , Config::get('frio', 'enable_compose'));
return frio_form($arr);
}
@@ -78,7 +81,7 @@ function theme_admin(App $a)
}
$arr = [];
- $arr['scheme'] = Config::get('frio', 'scheme', Config::get('frio', 'scheme'));
+ $arr['scheme'] = Config::get('frio', 'scheme', Config::get('frio', 'schema'));
$arr['share_string'] = '';
$arr['nav_bg'] = Config::get('frio', 'nav_bg');
$arr['nav_icon_color'] = Config::get('frio', 'nav_icon_color');
@@ -89,6 +92,7 @@ function theme_admin(App $a)
$arr['bg_image_option'] = Config::get('frio', 'bg_image_option');
$arr['login_bg_image'] = Config::get('frio', 'login_bg_image');
$arr['login_bg_color'] = Config::get('frio', 'login_bg_color');
+ $arr['enable_compose'] = Config::get('frio', 'enable_compose');
return frio_form($arr);
}
@@ -132,6 +136,7 @@ function frio_form($arr)
'$background_image' => array_key_exists('background_image', $disable) ? '' : ['frio_background_image', L10n::t('Set the background image'), $arr['background_image'], $background_image_help, false],
'$bg_image_options_title' => L10n::t('Background image style'),
'$bg_image_options' => Image::get_options($arr),
+ '$enable_compose' => ['frio_enable_compose', L10n::t('Enable Compose page'), $arr['enable_compose'], L10n::t('This replaces the jot modal window for writing new posts with a link to the new Compose page.')],
];
if (array_key_exists('login_bg_image', $arr) && !array_key_exists('login_bg_image', $disable)) {
diff --git a/view/theme/frio/css/style.css b/view/theme/frio/css/style.css
index a88276f15..c986b7232 100644
--- a/view/theme/frio/css/style.css
+++ b/view/theme/frio/css/style.css
@@ -192,7 +192,6 @@ blockquote {
background-image: none;
text-shadow: none;
border-radius: 3px;
- outline: 0!important;
margin-bottom: 0;
font-size: 14px;
font-weight: 600;
@@ -1314,7 +1313,8 @@ section ul.tabs {
section #jotOpen {
display: none;
}
-#jotOpen {
+#jotOpen,
+#composeOpen {
margin-top: 3px;
float: right;
}
@@ -1372,7 +1372,8 @@ section #jotOpen {
#jot-text-wrap .preview textarea {
width: 100%;
}
-#preview_profile-jot-text {
+#preview_profile-jot-text,
+.comment-edit-form .preview {
position: relative;
padding: 0px 10px;
margin-top: -2px;
@@ -1383,7 +1384,8 @@ section #jotOpen {
background: #fff;
color: #555;
}
-textarea#profile-jot-text:focus + #preview_profile-jot-text {
+textarea#profile-jot-text:focus + #preview_profile-jot-text,
+textarea.comment-edit-text:focus + .comment-edit-form .preview {
border: 2px solid #6fdbe8;
border-top: none;
}
@@ -3425,7 +3427,6 @@ section .profile-match-wrapper {
background-image: none;
text-shadow: none;
border-radius: 3px;
- outline: 0!important;
margin-bottom: 0;
font-size: 14px;
font-weight: 600;
@@ -3444,7 +3445,6 @@ section .profile-match-wrapper {
background-image: none;
text-shadow: none;
border-radius: 3px;
- outline: 0!important;
margin-bottom: 0;
font-size: 14px;
font-weight: 600;
diff --git a/view/theme/frio/frameworks/friendica-tagsinput/LICENSE b/view/theme/frio/frameworks/friendica-tagsinput/LICENSE
new file mode 100644
index 000000000..58bc985ba
--- /dev/null
+++ b/view/theme/frio/frameworks/friendica-tagsinput/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Tim Schlechter
+
+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.
diff --git a/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput-typeahead.css b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput-typeahead.css
new file mode 100644
index 000000000..8cec654c4
--- /dev/null
+++ b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput-typeahead.css
@@ -0,0 +1,54 @@
+/*
+ * friendica-tagsinput v0.8.0
+ *
+ */
+
+.twitter-typeahead .tt-query,
+.twitter-typeahead .tt-hint {
+ margin-bottom: 0;
+}
+
+.twitter-typeahead .tt-hint
+{
+ display: none;
+}
+
+.tt-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ display: none;
+ float: left;
+ min-width: 160px;
+ padding: 5px 0;
+ margin: 2px 0 0;
+ list-style: none;
+ font-size: 14px;
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ border-radius: 4px;
+ -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+ background-clip: padding-box;
+ cursor: pointer;
+}
+
+.tt-suggestion {
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: normal;
+ line-height: 1.428571429;
+ color: #333333;
+ white-space: nowrap;
+}
+
+.tt-suggestion:hover,
+.tt-suggestion:focus {
+ color: #ffffff;
+ text-decoration: none;
+ outline: 0;
+ background-color: #428bca;
+}
diff --git a/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.css b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.css
new file mode 100644
index 000000000..f2e1197a5
--- /dev/null
+++ b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.css
@@ -0,0 +1,72 @@
+/*
+ * friendica-tagsinput v0.8.0
+ *
+ */
+
+.friendica-tagsinput {
+ background-color: #fff;
+ border: 1px solid #ccc;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ display: inline-block;
+ padding: 4px 6px;
+ color: #555;
+ vertical-align: middle;
+ border-radius: 4px;
+ max-width: 100%;
+ line-height: 22px;
+ cursor: text;
+ height: auto;
+}
+.friendica-tagsinput.input-lg {
+ line-height: 27px;
+}
+.friendica-tagsinput input {
+ border: none;
+ box-shadow: none;
+ outline: none;
+ background-color: transparent;
+ padding: 0 6px;
+ margin: 0;
+ width: auto;
+ max-width: inherit;
+}
+.friendica-tagsinput.form-control input::-moz-placeholder {
+ color: #777;
+ opacity: 1;
+}
+.friendica-tagsinput.form-control input:-ms-input-placeholder {
+ color: #777;
+}
+.friendica-tagsinput.form-control input::-webkit-input-placeholder {
+ color: #777;
+}
+.friendica-tagsinput input:focus {
+ border: none;
+ box-shadow: none;
+}
+.friendica-tagsinput .tag {
+ margin: 0 2px 2px 0;
+ color: white;
+ font-weight: normal;
+}
+.friendica-tagsinput .tag img {
+ width: auto;
+ height: 1.5em;
+ vertical-align: text-top;
+ margin-right: 8px;
+}
+.friendica-tagsinput .tag [data-role="remove"] {
+ margin-left: 8px;
+ cursor: pointer;
+}
+.friendica-tagsinput .tag [data-role="remove"]:after {
+ content: "x";
+ padding: 0px 2px;
+ font-weight: bold;
+}
+.friendica-tagsinput .tag [data-role="remove"]:hover {
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+.friendica-tagsinput .tag [data-role="remove"]:hover:active {
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
diff --git a/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.js b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.js
new file mode 100644
index 000000000..2e83eedc6
--- /dev/null
+++ b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.js
@@ -0,0 +1,695 @@
+/*
+ * friendica-tagsinput v0.8.0
+ * Based on bootstrap-tagsinput v0.8.0
+ *
+ * Adds:
+ * - optional thumbnail
+ * - copying source input element class to the pseudo-input element
+ *
+ */
+
+(function ($) {
+ "use strict";
+
+ var defaultOptions = {
+ tagClass: function(item) {
+ return 'label label-info';
+ },
+ focusClass: 'focus',
+ itemValue: function(item) {
+ return item ? item.toString() : item;
+ },
+ itemText: function(item) {
+ return this.itemValue(item);
+ },
+ itemTitle: function(item) {
+ return null;
+ },
+ itemThumb: function(item) {
+ return null;
+ },
+ freeInput: true,
+ addOnBlur: true,
+ maxTags: undefined,
+ maxChars: undefined,
+ confirmKeys: [13, 44],
+ delimiter: ',',
+ delimiterRegex: null,
+ cancelConfirmKeysOnEmpty: false,
+ onTagExists: function(item, $tag) {
+ $tag.hide().fadeIn();
+ },
+ trimValue: false,
+ allowDuplicates: false,
+ triggerChange: true
+ };
+
+ /**
+ * Constructor function
+ */
+ function TagsInput(element, options) {
+ this.isInit = true;
+ this.itemsArray = [];
+
+ this.$element = $(element);
+ this.$element.hide();
+
+ this.isSelect = (element.tagName === 'SELECT');
+ this.multiple = (this.isSelect && element.hasAttribute('multiple'));
+ this.objectItems = options && options.itemValue;
+ this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : '';
+ this.inputSize = Math.max(1, this.placeholderText.length);
+
+ this.$container = $('');
+ this.$container.addClass(this.$element.attr('class'));
+ this.$input = $('').appendTo(this.$container);
+
+ this.$element.before(this.$container);
+
+ this.build(options);
+ this.isInit = false;
+ }
+
+ TagsInput.prototype = {
+ constructor: TagsInput,
+
+ /**
+ * Adds the given item as a new tag. Pass true to dontPushVal to prevent
+ * updating the elements val()
+ */
+ add: function(item, dontPushVal, options) {
+ let self = this;
+
+ if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags)
+ return;
+
+ // Ignore falsey values, except false
+ if (item !== false && !item)
+ return;
+
+ // Trim value
+ if (typeof item === "string" && self.options.trimValue) {
+ item = $.trim(item);
+ }
+
+ // Throw an error when trying to add an object while the itemValue option was not set
+ if (typeof item === "object" && !self.objectItems)
+ throw("Can't add objects when itemValue option is not set");
+
+ // Ignore strings only containg whitespace
+ if (item.toString().match(/^\s*$/))
+ return;
+
+ // If SELECT but not multiple, remove current tag
+ if (self.isSelect && !self.multiple && self.itemsArray.length > 0)
+ self.remove(self.itemsArray[0]);
+
+ if (typeof item === "string" && this.$element[0].tagName === 'INPUT') {
+ var delimiter = (self.options.delimiterRegex) ? self.options.delimiterRegex : self.options.delimiter;
+ var items = item.split(delimiter);
+ if (items.length > 1) {
+ for (var i = 0; i < items.length; i++) {
+ this.add(items[i], true);
+ }
+
+ if (!dontPushVal)
+ self.pushVal(self.options.triggerChange);
+ return;
+ }
+ }
+
+ var itemValue = self.options.itemValue(item),
+ itemText = self.options.itemText(item),
+ tagClass = self.options.tagClass(item),
+ itemTitle = self.options.itemTitle(item),
+ itemThumb = self.options.itemThumb(item);
+
+ // Ignore items allready added
+ var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0];
+ if (existing && !self.options.allowDuplicates) {
+ // Invoke onTagExists
+ if (self.options.onTagExists) {
+ var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; });
+ self.options.onTagExists(item, $existingTag);
+ }
+ return;
+ }
+
+ // if length greater than limit
+ if (self.items().toString().length + item.length + 1 > self.options.maxInputLength)
+ return;
+
+ // raise beforeItemAdd arg
+ var beforeItemAddEvent = $.Event('beforeItemAdd', { item: item, cancel: false, options: options});
+ self.$element.trigger(beforeItemAddEvent);
+ if (beforeItemAddEvent.cancel)
+ return;
+
+ // register item in internal array and map
+ self.itemsArray.push(item);
+
+ // add a tag element
+ var $tag = $('' +
+ (itemThumb !== null ? '' : '') +
+ htmlEncode(itemText) + '' +
+ '');
+ $tag.data('item', item);
+ self.findInputWrapper().before($tag);
+ $tag.after(' ');
+
+ // Check to see if the tag exists in its raw or uri-encoded form
+ var optionExists = (
+ $('option[value="' + encodeURIComponent(itemValue) + '"]', self.$element).length ||
+ $('option[value="' + htmlEncode(itemValue) + '"]', self.$element).length
+ );
+
+ // add if item represents a value not present in one of the 's options
+ if (self.isSelect && !optionExists) {
+ var $option = $('');
+ $option.data('item', item);
+ $option.attr('value', itemValue);
+ self.$element.append($option);
+ }
+
+ if (!dontPushVal)
+ self.pushVal(self.options.triggerChange);
+
+ // Add class when reached maxTags
+ if (self.options.maxTags === self.itemsArray.length || self.items().toString().length === self.options.maxInputLength)
+ self.$container.addClass('friendica-tagsinput-max');
+
+ // If using typeahead, once the tag has been added, clear the typeahead value so it does not stick around in the input.
+ if ($('.typeahead, .twitter-typeahead', self.$container).length) {
+ self.$input.typeahead('val', '');
+ }
+
+ if (this.isInit) {
+ self.$element.trigger($.Event('itemAddedOnInit', { item: item, options: options }));
+ } else {
+ self.$element.trigger($.Event('itemAdded', { item: item, options: options }));
+ }
+ },
+
+ /**
+ * Removes the given item. Pass true to dontPushVal to prevent updating the
+ * elements val()
+ */
+ remove: function(item, dontPushVal, options) {
+ var self = this;
+
+ if (self.objectItems) {
+ if (typeof item === "object")
+ item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == self.options.itemValue(item); } );
+ else
+ item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == item; } );
+
+ item = item[item.length-1];
+ }
+
+ if (item) {
+ var beforeItemRemoveEvent = $.Event('beforeItemRemove', { item: item, cancel: false, options: options });
+ self.$element.trigger(beforeItemRemoveEvent);
+ if (beforeItemRemoveEvent.cancel)
+ return;
+
+ $('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove();
+ $('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove();
+ if($.inArray(item, self.itemsArray) !== -1)
+ self.itemsArray.splice($.inArray(item, self.itemsArray), 1);
+ }
+
+ if (!dontPushVal)
+ self.pushVal(self.options.triggerChange);
+
+ // Remove class when reached maxTags
+ if (self.options.maxTags > self.itemsArray.length)
+ self.$container.removeClass('friendica-tagsinput-max');
+
+ self.$element.trigger($.Event('itemRemoved', { item: item, options: options }));
+ },
+
+ /**
+ * Removes all items
+ */
+ removeAll: function() {
+ var self = this;
+
+ $('.tag', self.$container).remove();
+ $('option', self.$element).remove();
+
+ while(self.itemsArray.length > 0)
+ self.itemsArray.pop();
+
+ self.pushVal(self.options.triggerChange);
+ },
+
+ /**
+ * Refreshes the tags so they match the text/value of their corresponding
+ * item.
+ */
+ refresh: function() {
+ var self = this;
+ $('.tag', self.$container).each(function() {
+ var $tag = $(this),
+ item = $tag.data('item'),
+ itemValue = self.options.itemValue(item),
+ itemText = self.options.itemText(item),
+ tagClass = self.options.tagClass(item);
+
+ // Update tag's class and inner text
+ $tag.attr('class', null);
+ $tag.addClass('tag ' + htmlEncode(tagClass));
+ $tag.contents().filter(function() {
+ return this.nodeType == 3;
+ })[0].nodeValue = htmlEncode(itemText);
+
+ if (self.isSelect) {
+ var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; });
+ option.attr('value', itemValue);
+ }
+ });
+ },
+
+ /**
+ * Returns the items added as tags
+ */
+ items: function() {
+ return this.itemsArray;
+ },
+
+ /**
+ * Assembly value by retrieving the value of each item, and set it on the
+ * element.
+ */
+ pushVal: function() {
+ var self = this,
+ val = $.map(self.items(), function(item) {
+ return self.options.itemValue(item).toString();
+ });
+
+ self.$element.val(val, true);
+
+ if (self.options.triggerChange)
+ self.$element.trigger('change');
+ },
+
+ /**
+ * Initializes the tags input behaviour on the element
+ */
+ build: function(options) {
+ var self = this;
+
+ self.options = $.extend({}, defaultOptions, options);
+ // When itemValue is set, freeInput should always be false
+ if (self.objectItems)
+ self.options.freeInput = false;
+
+ makeOptionItemFunction(self.options, 'itemValue');
+ makeOptionItemFunction(self.options, 'itemText');
+ makeOptionItemFunction(self.options, 'itemThumb');
+ makeOptionFunction(self.options, 'tagClass');
+
+ // Typeahead Bootstrap version 2.3.2
+ if (self.options.typeahead) {
+ var typeahead = self.options.typeahead || {};
+
+ makeOptionFunction(typeahead, 'source');
+
+ self.$input.typeahead($.extend({}, typeahead, {
+ source: function (query, process) {
+ function processItems(items) {
+ var texts = [];
+
+ for (var i = 0; i < items.length; i++) {
+ var text = self.options.itemText(items[i]);
+ map[text] = items[i];
+ texts.push(text);
+ }
+ process(texts);
+ }
+
+ this.map = {};
+ var map = this.map,
+ data = typeahead.source(query);
+
+ if ($.isFunction(data.success)) {
+ // support for Angular callbacks
+ data.success(processItems);
+ } else if ($.isFunction(data.then)) {
+ // support for Angular promises
+ data.then(processItems);
+ } else {
+ // support for functions and jquery promises
+ $.when(data)
+ .then(processItems);
+ }
+ },
+ updater: function (text) {
+ self.add(this.map[text]);
+ return this.map[text];
+ },
+ matcher: function (text) {
+ return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1);
+ },
+ sorter: function (texts) {
+ return texts.sort();
+ },
+ highlighter: function (text) {
+ var regex = new RegExp( '(' + this.query + ')', 'gi' );
+ return text.replace( regex, "$1" );
+ }
+ }));
+ }
+
+ // typeahead.js
+ if (self.options.typeaheadjs) {
+ var typeaheadConfig = null;
+ var typeaheadDatasets = {};
+
+ // Determine if main configurations were passed or simply a dataset
+ var typeaheadjs = self.options.typeaheadjs;
+ if ($.isArray(typeaheadjs)) {
+ typeaheadConfig = typeaheadjs[0];
+ typeaheadDatasets = typeaheadjs[1];
+ } else {
+ typeaheadDatasets = typeaheadjs;
+ }
+
+ self.$input.typeahead(typeaheadConfig, typeaheadDatasets).on('typeahead:selected', $.proxy(function (obj, datum) {
+ if (typeaheadDatasets.valueKey)
+ self.add(datum[typeaheadDatasets.valueKey]);
+ else
+ self.add(datum);
+ self.$input.typeahead('val', '');
+ }, self));
+ }
+
+ self.$container.on('click', $.proxy(function(event) {
+ if (! self.$element.attr('disabled')) {
+ self.$input.removeAttr('disabled');
+ }
+ self.$input.focus();
+ }, self));
+
+ if (self.options.addOnBlur && self.options.freeInput) {
+ self.$input.on('focusout', $.proxy(function(event) {
+ // HACK: only process on focusout when no typeahead opened, to
+ // avoid adding the typeahead text as tag
+ if ($('.typeahead, .twitter-typeahead', self.$container).length === 0) {
+ self.add(self.$input.val());
+ self.$input.val('');
+ }
+ }, self));
+ }
+
+ // Toggle the 'focus' css class on the container when it has focus
+ self.$container.on({
+ focusin: function() {
+ self.$container.addClass(self.options.focusClass);
+ },
+ focusout: function() {
+ self.$container.removeClass(self.options.focusClass);
+ },
+ });
+
+ self.$container.on('keydown', 'input', $.proxy(function(event) {
+ var $input = $(event.target),
+ $inputWrapper = self.findInputWrapper();
+
+ if (self.$element.attr('disabled')) {
+ self.$input.attr('disabled', 'disabled');
+ return;
+ }
+
+ switch (event.which) {
+ // BACKSPACE
+ case 8:
+ if (doGetCaretPosition($input[0]) === 0) {
+ var prev = $inputWrapper.prev();
+ if (prev.length) {
+ self.remove(prev.data('item'));
+ }
+ }
+ break;
+
+ // DELETE
+ case 46:
+ if (doGetCaretPosition($input[0]) === 0) {
+ var next = $inputWrapper.next();
+ if (next.length) {
+ self.remove(next.data('item'));
+ }
+ }
+ break;
+
+ // LEFT ARROW
+ case 37:
+ // Try to move the input before the previous tag
+ var $prevTag = $inputWrapper.prev();
+ if ($input.val().length === 0 && $prevTag[0]) {
+ $prevTag.before($inputWrapper);
+ $input.focus();
+ }
+ break;
+ // RIGHT ARROW
+ case 39:
+ // Try to move the input after the next tag
+ var $nextTag = $inputWrapper.next();
+ if ($input.val().length === 0 && $nextTag[0]) {
+ $nextTag.after($inputWrapper);
+ $input.focus();
+ }
+ break;
+ default:
+ // ignore
+ }
+
+ // Reset internal input's size
+ var textLength = $input.val().length,
+ wordSpace = Math.ceil(textLength / 5),
+ size = textLength + wordSpace + 1;
+ $input.attr('size', Math.max(this.inputSize, $input.val().length));
+ }, self));
+
+ self.$container.on('keypress', 'input', $.proxy(function(event) {
+ var $input = $(event.target);
+
+ if (self.$element.attr('disabled')) {
+ self.$input.attr('disabled', 'disabled');
+ return;
+ }
+
+ var text = $input.val(),
+ maxLengthReached = self.options.maxChars && text.length >= self.options.maxChars;
+ if (self.options.freeInput && (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached)) {
+ // Only attempt to add a tag if there is data in the field
+ if (text.length !== 0) {
+ self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text);
+ $input.val('');
+ }
+
+ // If the field is empty, let the event triggered fire as usual
+ if (self.options.cancelConfirmKeysOnEmpty === false) {
+ event.preventDefault();
+ }
+ }
+
+ // Reset internal input's size
+ var textLength = $input.val().length,
+ wordSpace = Math.ceil(textLength / 5),
+ size = textLength + wordSpace + 1;
+ $input.attr('size', Math.max(this.inputSize, $input.val().length));
+ }, self));
+
+ // Remove icon clicked
+ self.$container.on('click', '[data-role=remove]', $.proxy(function(event) {
+ if (self.$element.attr('disabled')) {
+ return;
+ }
+ self.remove($(event.target).closest('.tag').data('item'));
+ }, self));
+
+ // Only add existing value as tags when using strings as tags
+ if (self.options.itemValue === defaultOptions.itemValue) {
+ if (self.$element[0].tagName === 'INPUT') {
+ self.add(self.$element.val());
+ } else {
+ $('option', self.$element).each(function() {
+ self.add($(this).attr('value'), true);
+ });
+ }
+ }
+ },
+
+ /**
+ * Removes all tagsinput behaviour and unregsiter all event handlers
+ */
+ destroy: function() {
+ var self = this;
+
+ // Unbind events
+ self.$container.off('keypress', 'input');
+ self.$container.off('click', '[role=remove]');
+
+ self.$container.remove();
+ self.$element.removeData('tagsinput');
+ self.$element.show();
+ },
+
+ /**
+ * Sets focus on the tagsinput
+ */
+ focus: function() {
+ this.$input.focus();
+ },
+
+ /**
+ * Returns the internal input element
+ */
+ input: function() {
+ return this.$input;
+ },
+
+ /**
+ * Returns the element which is wrapped around the internal input. This
+ * is normally the $container, but typeahead.js moves the $input element.
+ */
+ findInputWrapper: function() {
+ var elt = this.$input[0],
+ container = this.$container[0];
+ while(elt && elt.parentNode !== container)
+ elt = elt.parentNode;
+
+ return $(elt);
+ }
+ };
+
+ /**
+ * Register JQuery plugin
+ */
+ $.fn.tagsinput = function(arg1, arg2, arg3) {
+ var results = [];
+
+ this.each(function() {
+ var tagsinput = $(this).data('tagsinput');
+ // Initialize a new tags input
+ if (!tagsinput) {
+ tagsinput = new TagsInput(this, arg1);
+ $(this).data('tagsinput', tagsinput);
+ results.push(tagsinput);
+
+ if (this.tagName === 'SELECT') {
+ $('option', $(this)).attr('selected', 'selected');
+ }
+
+ // Init tags from $(this).val()
+ $(this).val($(this).val());
+ } else if (!arg1 && !arg2) {
+ // tagsinput already exists
+ // no function, trying to init
+ results.push(tagsinput);
+ } else if(tagsinput[arg1] !== undefined) {
+ // Invoke function on existing tags input
+ if(tagsinput[arg1].length === 3 && arg3 !== undefined){
+ var retVal = tagsinput[arg1](arg2, null, arg3);
+ }else{
+ var retVal = tagsinput[arg1](arg2);
+ }
+ if (retVal !== undefined)
+ results.push(retVal);
+ }
+ });
+
+ if ( typeof arg1 == 'string') {
+ // Return the results from the invoked function calls
+ return results.length > 1 ? results : results[0];
+ } else {
+ return results;
+ }
+ };
+
+ $.fn.tagsinput.Constructor = TagsInput;
+
+ /**
+ * Most options support both a string or number as well as a function as
+ * option value. This function makes sure that the option with the given
+ * key in the given options is wrapped in a function
+ */
+ function makeOptionItemFunction(options, key) {
+ if (typeof options[key] !== 'function') {
+ var propertyName = options[key];
+ options[key] = function(item) { return item[propertyName]; };
+ }
+ }
+ function makeOptionFunction(options, key) {
+ if (typeof options[key] !== 'function') {
+ var value = options[key];
+ options[key] = function() { return value; };
+ }
+ }
+ /**
+ * HtmlEncodes the given value
+ */
+ var htmlEncodeContainer = $('');
+ function htmlEncode(value) {
+ if (value) {
+ return htmlEncodeContainer.text(value).html();
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Returns the position of the caret in the given input field
+ * http://flightschool.acylt.com/devnotes/caret-position-woes/
+ */
+ function doGetCaretPosition(oField) {
+ var iCaretPos = 0;
+ if (document.selection) {
+ oField.focus ();
+ var oSel = document.selection.createRange();
+ oSel.moveStart ('character', -oField.value.length);
+ iCaretPos = oSel.text.length;
+ } else if (oField.selectionStart || oField.selectionStart == '0') {
+ iCaretPos = oField.selectionStart;
+ }
+ return (iCaretPos);
+ }
+
+ /**
+ * Returns boolean indicates whether user has pressed an expected key combination.
+ * @param object keyPressEvent: JavaScript event object, refer
+ * http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+ * @param object lookupList: expected key combinations, as in:
+ * [13, {which: 188, shiftKey: true}]
+ */
+ function keyCombinationInList(keyPressEvent, lookupList) {
+ var found = false;
+ $.each(lookupList, function (index, keyCombination) {
+ if (typeof (keyCombination) === 'number' && keyPressEvent.which === keyCombination) {
+ found = true;
+ return false;
+ }
+
+ if (keyPressEvent.which === keyCombination.which) {
+ var alt = !keyCombination.hasOwnProperty('altKey') || keyPressEvent.altKey === keyCombination.altKey,
+ shift = !keyCombination.hasOwnProperty('shiftKey') || keyPressEvent.shiftKey === keyCombination.shiftKey,
+ ctrl = !keyCombination.hasOwnProperty('ctrlKey') || keyPressEvent.ctrlKey === keyCombination.ctrlKey;
+ if (alt && shift && ctrl) {
+ found = true;
+ return false;
+ }
+ }
+ });
+
+ return found;
+ }
+
+ /**
+ * Initialize tagsinput behaviour on inputs and selects which have
+ * data-role=tagsinput
+ */
+ $(function() {
+ $("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
+ });
+})(window.jQuery);
diff --git a/view/theme/frio/js/theme.js b/view/theme/frio/js/theme.js
index 19b5c4fa6..44a24b30a 100644
--- a/view/theme/frio/js/theme.js
+++ b/view/theme/frio/js/theme.js
@@ -64,9 +64,24 @@ $(document).ready(function(){
});
// add Jot botton to the scecond navbar
- if( $("section #jotOpen").length ) {
- $("section #jotOpen").appendTo("#topbar-second > .container > #navbar-button");
- if( $("#jot-popup").is(":hidden")) $("#topbar-second > .container > #navbar-button #jotOpen").hide();
+ let $jotButton = $("#jotOpen");
+ let $composeButton = $("#composeOpen");
+ if (compose) {
+ $jotButton.hide();
+ if ($composeButton.length) {
+ $composeButton.appendTo("#topbar-second > .container > #navbar-button");
+ if($("#jot-popup").is(":hidden")) {
+ $composeButton.hide();
+ }
+ }
+ } else {
+ $composeButton.hide();
+ if ($jotButton.length) {
+ $jotButton.appendTo("#topbar-second > .container > #navbar-button");
+ if($("#jot-popup").is(":hidden")) {
+ $jotButton.hide();
+ }
+ }
}
// show bulk deletion button at network page if checkbox is checked
diff --git a/view/theme/frio/templates/comment_item.tpl b/view/theme/frio/templates/comment_item.tpl
index 98b3c4563..b60f5c429 100644
--- a/view/theme/frio/templates/comment_item.tpl
+++ b/view/theme/frio/templates/comment_item.tpl
@@ -10,7 +10,6 @@
{{**}}
-
diff --git a/view/theme/frio/templates/jot.tpl b/view/theme/frio/templates/jot.tpl
index d5ba1bcc4..8b103eba5 100644
--- a/view/theme/frio/templates/jot.tpl
+++ b/view/theme/frio/templates/jot.tpl
@@ -1,6 +1,6 @@
{{* The button to open the jot - in This theme we move the button with js to the second nav bar *}}
-
+
{{$compose_title}}
+diff --git a/view/theme/frio/templates/jot.tpl b/view/theme/frio/templates/jot.tpl index d5ba1bcc4..8b103eba5 100644 --- a/view/theme/frio/templates/jot.tpl +++ b/view/theme/frio/templates/jot.tpl @@ -1,6 +1,6 @@ {{* The button to open the jot - in This theme we move the button with js to the second nav bar *}} - +