"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",
"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": [
{
"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",
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.2.2.tgz",
- "reference": null,
"shasum": "30dc7a7ce872155b23a33bd10ad4c76c0d613f55"
},
"require-dev": {
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/ev-emitter/-/ev-emitter-1.1.1.tgz",
- "reference": null,
"shasum": "8f18b0ce5c76a5d18017f71c0a795c65b9138f2a"
},
"type": "npm-asset-library",
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-3.10.0.tgz",
- "reference": null,
"shasum": "cc5e87d518fd6550e142816a31dd191664847919"
},
"type": "npm-asset-library",
"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": {
"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": {
"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": {
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/jquery-colorbox/-/jquery-colorbox-1.6.4.tgz",
- "reference": null,
"shasum": "799452523a6c494839224ef702e807deb9c06cc5"
},
"require": {
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/jquery-datetimepicker/-/jquery-datetimepicker-2.5.20.tgz",
- "reference": null,
"shasum": "687d6204b90b03dc93f725f8df036e1d061f37ac"
},
"require": {
"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": {
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
- "reference": null,
"shasum": "3c257f9839fc0e93ff53149632239eb90783ff66"
},
"type": "npm-asset-library",
"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",
"time": "2017-03-25T17:14:26+00:00"
},
{
- "name": "mikey179/vfsStream",
+ "name": "mikey179/vfsstream",
"version": "v1.6.5",
"source": {
"type": "git",
"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.",
"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.",
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
+ "role": "lead",
+ "email": "sebastian@phpunit.de"
}
],
"description": "The PHP Unit Testing framework.",
}
],
"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",
}
],
"description": "Provides functionality to handle HHVM/PHP environments",
- "homepage": "https://github.com/sebastianbergmann/environment",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
"keywords": [
"Xdebug",
"environment",
}
],
"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"
}
],
"description": "Snapshotting of global state",
- "homepage": "https://github.com/sebastianbergmann/global-state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
}
],
"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"
},
{
},
{
"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": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.11-dev"
+ "dev-master": "1.9-dev"
}
},
"autoload": {
"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": {
],
"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",
*/
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;
/**
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];
}
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);
}
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)
$l = [];
if (count($allowed_groups)) {
+ $key = array_search(Group::FOLLOWERS, $allowed_groups);
+ if ($key !== false) {
+ $l[] = '<b>' . L10n::t('Followers') . '</b>';
+ unset($allowed_groups[$key]);
+ }
+
+ $key = array_search(Group::MUTUALS, $allowed_groups);
+ if ($key !== false) {
+ $l[] = '<b>' . L10n::t('Mutuals') . '</b>';
+ unset($allowed_groups[$key]);
+ }
+
+
$r = q("SELECT `name` FROM `group` WHERE `id` IN ( %s )",
DBA::escape(implode(', ', $allowed_groups))
);
}
if (count($deny_groups)) {
+ $key = array_search(Group::FOLLOWERS, $deny_groups);
+ if ($key !== false) {
+ $l[] = '<b><strike>' . L10n::t('Followers') . '</strike></b>';
+ unset($deny_groups[$key]);
+ }
+
+ $key = array_search(Group::MUTUALS, $deny_groups);
+ if ($key !== false) {
+ $l[] = '<b><strike>' . L10n::t('Mutuals') . '</strike></b>';
+ unset($deny_groups[$key]);
+ }
+
$r = q("SELECT `name` FROM `group` WHERE `id` IN ( %s )",
DBA::escape(implode(', ', $deny_groups))
);
// NOTREACHED
}
- $contacts = Group::expand([$gid]);
+ $contacts = Group::expand(local_user(), [$gid]);
if ((is_array($contacts)) && count($contacts)) {
$contact_str_self = '';
$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);
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
/**
* @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;
*/
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
/**
* 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
*/
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;
}
* @return boolean
* @throws \Exception
*/
- public static function remove($gid) {
- if (! $gid) {
+ public static function remove($gid)
+ {
+ if (!$gid) {
return false;
}
/**
* @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);
/**
* @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)
{
/**
* @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);
* @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' => '',
'selected' => ''
]
];
+
+ $stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => $uid], ['order' => ['name']]);
while ($group = DBA::fetch($stmt)) {
$display_groups[] = [
'name' => $group['name'],
'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');
* @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)
{
]
];
- $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' : '');
'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')) {
'$form_security_token' => BaseModule::getFormSecurityToken('group_edit'),
]);
-
return $o;
}
}
return $contact_id;
}
}
-
return $item['author-id'];
}
$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']];
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}.', [
$replace = true;
}
} elseif ($item) {
- if (self::samePermissions($item, $photo)) {
+ if (self::samePermissions($uid, $item, $photo)) {
$replace = true;
}
}
!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'])
}
// 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);
--- /dev/null
+<?php
+
+namespace Friendica\Module\Item;
+
+use Friendica\BaseModule;
+use Friendica\Content\Feature;
+use Friendica\Core\Config;
+use Friendica\Core\Hook;
+use Friendica\Core\L10n;
+use Friendica\Core\Renderer;
+use Friendica\Database\DBA;
+use Friendica\Model\Contact;
+use Friendica\Model\FileTag;
+use Friendica\Model\Group;
+use Friendica\Model\Item;
+use Friendica\Model\User;
+use Friendica\Module\Login;
+use Friendica\Network\HTTPException\NotImplementedException;
+use Friendica\Util\Crypto;
+
+class Compose extends BaseModule
+{
+ public static function post()
+ {
+ if (!empty($_REQUEST['body'])) {
+ $_REQUEST['return'] = 'network';
+ require_once 'mod/item.php';
+ item_post(self::getApp());
+ } else {
+ notice(L10n::t('Please enter a post body.'));
+ }
+ }
+
+ public static function content()
+ {
+ if (!local_user()) {
+ return Login::form('compose', false);
+ }
+
+ $a = self::getApp();
+
+ if ($a->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)
+ ]);
+ }
+}
$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'])) {
}
$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
namespace Friendica\Test;
+use Friendica\Model\Group;
use PHPUnit\Framework\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));
}
/**
*/
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));
}
/**
public function testExpandAclString()
{
$text="<1><279012><tt>";
- $this->assertEquals(array(1, 279012), expand_acl($text));
+ $this->assertEquals(array('1', '279012'), expand_acl($text));
}
/**
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));
}
/**
public function testExpandAclEmptyMatch()
{
$text="<1><><3>";
- $this->assertEquals(array(1,3), expand_acl($text));
+ $this->assertEquals(array('1', '3'), expand_acl($text));
}
/**
unpause();
commentBusy = true;
$('body').css('cursor', 'wait');
- $("#comment-preview-inp-" + id).val("0");
$.post(
"item",
$("#comment-edit-form-" + id).serialize(),
}
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);
<input type="hidden" name="parent" value="{{$parent}}" />
{{*<!--<input type="hidden" name="return" value="{{$return_path}}" />-->*}}
<input type="hidden" name="jsreload" value="{{$jsreload}}" />
- <input type="hidden" name="preview" id="comment-preview-inp-{{$id}}" value="0" />
<input type="hidden" name="post_id_random" value="{{$rand_num}}" />
<div class="comment-edit-photo" id="comment-edit-photo-{{$id}}" >
--- /dev/null
+<script type="text/javascript">
+ function updateLocationButtonDisplay(location_button, location_input)
+ {
+ location_button.classList.remove('btn-primary');
+ if (location_input.value) {
+ location_button.disabled = false;
+ location_button.classList.add('btn-primary');
+ location_button.title = location_button.dataset.titleClear;
+ } else if (!"geolocation" in navigator) {
+ location_button.disabled = true;
+ location_button.title = location_button.dataset.titleUnavailable;
+ } else if (location_button.disabled) {
+ location_button.title = location_button.dataset.titleDisabled;
+ } else {
+ location_button.title = location_button.dataset.titleSet;
+ }
+ }
+
+ $(function() {
+ // Jot attachment live preview.
+ let $textarea = $('#comment-edit-text-0');
+ $textarea.linkPreview();
+ $textarea.keyup(function(){
+ var textlen = $(this).val().length;
+ $('#character-counter').text(textlen);
+ });
+ $textarea.editor_autocomplete(baseurl+"/acl");
+ $textarea.bbco_autocomplete('bbcode');
+
+ let $acl_allow_input = $('#acl_allow');
+ let $group_allow_input = $('[name=group_allow]');
+ let $contact_allow_input = $('[name=contact_allow]');
+ let $acl_deny_input = $('#acl_deny');
+ let $group_deny_input = $('[name=group_deny]');
+ let $contact_deny_input = $('[name=contact_deny]');
+
+ // Visibility accordion
+
+ // Prevents open panel to collapse
+ // @see https://stackoverflow.com/a/43593116
+ $('[data-toggle="collapse"]').click(function(e) {
+ target = $(this).attr('href');
+ if ($(target).hasClass('in')) {
+ e.preventDefault(); // to stop the page jump to the anchor target.
+ e.stopPropagation()
+ }
+ });
+ // Accessibility: enable space and enter to open a panel when focused
+ $('body').on('keyup', '[data-toggle="collapse"]:focus', function (e) {
+ if (e.key === ' ' || e.key === 'Enter') {
+ $(this).click();
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ });
+
+ $('#visibility-public-panel').on('show.bs.collapse', function() {
+ $('#visibility-public').prop('checked', true);
+ $group_allow_input.prop('disabled', true);
+ $contact_allow_input.prop('disabled', true);
+ $group_deny_input.prop('disabled', true);
+ $contact_deny_input.prop('disabled', true);
+
+ $('.profile-jot-net input[type=checkbox]').each(function() {
+ // Restores checkbox state if it had been saved
+ if ($(this).attr('data-checked') !== undefined) {
+ $(this).prop('checked', $(this).attr('data-checked') === 'true');
+ }
+ });
+ $('.profile-jot-net input').attr('disabled', false);
+ });
+
+ $('#visibility-custom-panel').on('show.bs.collapse', function() {
+ $('#visibility-custom').prop('checked', true);
+ $group_allow_input.prop('disabled', false);
+ $contact_allow_input.prop('disabled', false);
+ $group_deny_input.prop('disabled', false);
+ $contact_deny_input.prop('disabled', false);
+
+ $('.profile-jot-net input[type=checkbox]').each(function() {
+ // Saves current checkbox state
+ $(this)
+ .attr('data-checked', $(this).prop('checked'))
+ .prop('checked', false);
+ });
+ $('.profile-jot-net input').attr('disabled', 'disabled');
+ });
+
+ if (document.querySelector('input[name="visibility"]:checked').value === 'custom') {
+ $('#visibility-custom-panel').collapse({parent: '#visibility-accordion'});
+ }
+
+ // Custom visibility tags inputs
+
+ let acl_groups = new Bloodhound({
+ local: {{$acl_groups|@json_encode nofilter}},
+ identify: function(obj) { return obj.id; },
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace(['name']),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ });
+ let acl_contacts = new Bloodhound({
+ local: {{$acl_contacts|@json_encode nofilter}},
+ identify: function(obj) { return obj.id; },
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace(['name', 'addr']),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ });
+ let acl = new Bloodhound({
+ local: {{$acl|@json_encode nofilter}},
+ identify: function(obj) { return obj.id; },
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace(['name', 'addr']),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ });
+ acl.initialize();
+
+ let suggestionTemplate = function (item) {
+ return '<div><img src="' + item.micro + '" alt="" style="float: left; width: auto; height: 2.8em; margin-right: 0.5em;"> <strong>' + item.name + '</strong><br /><em>' + item.addr + '</em></div>';
+ };
+
+ $acl_allow_input.tagsinput({
+ confirmKeys: [13, 44],
+ cancelConfirmKeysOnEmpty: true,
+ freeInput: false,
+ tagClass: function(item) {
+ switch (item.type) {
+ case 'group' : return 'label label-primary';
+ case 'contact' :
+ default:
+ return 'label label-info';
+ }
+ },
+ itemValue: 'id',
+ itemText: 'name',
+ itemThumb: 'micro',
+ itemTitle: function(item) {
+ return item.addr;
+ },
+ typeaheadjs: {
+ name: 'contacts',
+ displayKey: 'name',
+ templates: {
+ suggestion: suggestionTemplate
+ },
+ source: acl.ttAdapter()
+ }
+ });
+
+ $acl_deny_input
+ .tagsinput({
+ confirmKeys: [13, 44],
+ freeInput: false,
+ tagClass: function(item) {
+ switch (item.type) {
+ case 'group' : return 'label label-primary';
+ case 'contact' :
+ default:
+ return 'label label-info';
+ }
+ },
+ itemValue: 'id',
+ itemText: 'name',
+ itemThumb: 'micro',
+ itemTitle: function(item) {
+ return item.addr;
+ },
+ typeaheadjs: {
+ name: 'contacts',
+ displayKey: 'name',
+ templates: {
+ suggestion: suggestionTemplate
+ },
+ source: acl.ttAdapter()
+ }
+ });
+
+ // Import existing ACL into the tags input fields.
+
+ $group_allow_input.val().split(',').forEach(function (val) {
+ $acl_allow_input.tagsinput('add', acl_groups.get(val)[0]);
+ });
+ $contact_allow_input.val().split(',').forEach(function (val) {
+ $acl_allow_input.tagsinput('add', acl_contacts.get(val)[0]);
+ });
+ $group_deny_input.val().split(',').forEach(function (val) {
+ $acl_deny_input.tagsinput('add', acl_groups.get(val)[0]);
+ });
+ $contact_deny_input.val().split(',').forEach(function (val) {
+ $acl_deny_input.tagsinput('add', acl_contacts.get(val)[0]);
+ });
+
+ // Anti-duplicate callback + acl fields value generation
+
+ $acl_allow_input.on('itemAdded', function (event) {
+ // Removes duplicate in the opposite acl box
+ $acl_deny_input.tagsinput('remove', event.item);
+
+ // Update the real acl field
+ $group_allow_input.val('');
+ $contact_allow_input.val('');
+ [].forEach.call($acl_allow_input.tagsinput('items'), function (item) {
+ if (item.type === 'group') {
+ $group_allow_input.val($group_allow_input.val() + '<' + item.id + '>');
+ } else {
+ $contact_allow_input.val($contact_allow_input.val() + '<' + item.id + '>');
+ }
+ });
+ });
+
+ $acl_deny_input.on('itemAdded', function (event) {
+ // Removes duplicate in the opposite acl box
+ $acl_allow_input.tagsinput('remove', event.item);
+
+ // Update the real acl field
+ $group_deny_input.val('');
+ $contact_deny_input.val('');
+ [].forEach.call($acl_deny_input.tagsinput('items'), function (item) {
+ if (item.type === 'group') {
+ $group_deny_input.val($group_allow_input.val() + '<' + item.id + '>');
+ } else {
+ $contact_deny_input.val($contact_allow_input.val() + '<' + item.id + '>');
+ }
+ });
+ });
+
+ let location_button = document.getElementById('profile-location');
+ let location_input = document.getElementById('jot-location');
+
+ updateLocationButtonDisplay(location_button, location_input);
+
+ location_input.addEventListener('change', function () {
+ updateLocationButtonDisplay(location_button, location_input);
+ });
+ location_input.addEventListener('keyup', function () {
+ updateLocationButtonDisplay(location_button, location_input);
+ });
+
+ location_button.addEventListener('click', function() {
+ if (location_input.value) {
+ location_input.value = '';
+ updateLocationButtonDisplay(location_button, location_input);
+ } else if ("geolocation" in navigator) {
+ navigator.geolocation.getCurrentPosition(function(position) {
+ location_input.value = position.coords.latitude + ', ' + position.coords.longitude;
+ updateLocationButtonDisplay(location_button, location_input);
+ }, function (error) {
+ location_button.disabled = true;
+ updateLocationButtonDisplay(location_button, location_input);
+ });
+ }
+ });
+ })
+</script>
--- /dev/null
+<div class="generic-page-wrapper">
+ <h2>{{$compose_title}}</h2>
+ <div id="profile-jot-wrapper">
+ <form class="comment-edit-form" data-item-id="{{$id}}" id="comment-edit-form-{{$id}}" action="compose/{{$type}}" method="post">
+ {{*<!--<input type="hidden" name="return" value="{{$return_path}}" />-->*}}
+ <input type="hidden" name="post_id_random" value="{{$rand_num}}" />
+ <input type="hidden" name="post_type" value="{{$posttype}}" />
+ <input type="hidden" name="wall" value="{{$wall}}" />
+
+ <div id="jot-title-wrap">
+ <input type="text" name="title" id="jot-title" class="jothidden jotforms form-control" placeholder="{{$placeholdertitle}}" title="{{$placeholdertitle}}" value="{{$title}}" tabindex="1"/>
+ </div>
+ {{if $placeholdercategory}}
+ <div id="jot-category-wrap">
+ <input name="category" id="jot-category" class="jothidden jotforms form-control" type="text" placeholder="{{$placeholdercategory}}" title="{{$placeholdercategory}}" value="{{$category}}" tabindex="2"/>
+ </div>
+ {{/if}}
+
+ <p class="comment-edit-bb-{{$id}} comment-icon-list">
+ <span>
+ <button type="button" class="btn btn-sm icon bb-img" aria-label="{{$edimg}}" title="{{$edimg}}" data-role="insert-formatting" data-bbcode="img" data-id="{{$id}}" tabindex="7">
+ <i class="fa fa-picture-o"></i>
+ </button>
+ <button type="button" class="btn btn-sm icon bb-attach" aria-label="{{$edattach}}" title="{{$edattach}}" ondragenter="return commentLinkDrop(event, {{$id}});" ondragover="return commentLinkDrop(event, {{$id}});" ondrop="commentLinkDropper(event);" onclick="commentGetLink({{$id}}, '{{$prompttext}}');" tabindex="8">
+ <i class="fa fa-paperclip"></i>
+ </button>
+ </span>
+ <span>
+ <button type="button" class="btn btn-sm icon bb-url" aria-label="{{$edurl}}" title="{{$edurl}}" onclick="insertFormatting('url',{{$id}});" tabindex="9">
+ <i class="fa fa-link"></i>
+ </button>
+ <button type="button" class="btn btn-sm icon underline" aria-label="{{$eduline}}" title="{{$eduline}}" onclick="insertFormatting('u',{{$id}});" tabindex="10">
+ <i class="fa fa-underline"></i>
+ </button>
+ <button type="button" class="btn btn-sm icon italic" aria-label="{{$editalic}}" title="{{$editalic}}" onclick="insertFormatting('i',{{$id}});" tabindex="11">
+ <i class="fa fa-italic"></i>
+ </button>
+ <button type="button" class="btn btn-sm icon bold" aria-label="{{$edbold}}" title="{{$edbold}}" onclick="insertFormatting('b',{{$id}});" tabindex="12">
+ <i class="fa fa-bold"></i>
+ </button>
+ <button type="button" class="btn btn-sm icon quote" aria-label="{{$edquote}}" title="{{$edquote}}" onclick="insertFormatting('quote',{{$id}});" tabindex="13">
+ <i class="fa fa-quote-left"></i>
+ </button>
+ </span>
+ </p>
+ <p>
+ <textarea id="comment-edit-text-{{$id}}" class="comment-edit-text form-control text-autosize" name="body" placeholder="{{$default}}" rows="7" tabindex="3">{{$body}}</textarea>
+ </p>
+
+ <p class="comment-edit-submit-wrapper">
+{{if $type == 'post'}}
+ <span role="presentation" class="form-inline">
+ <input type="text" name="location" class="form-control" id="jot-location" value="{{$location}}" placeholder="{{$location_set}}"/>
+ <button type="button" class="btn btn-sm icon" id="profile-location"
+ data-title-set="{{$location_set}}"
+ data-title-disabled="{{$location_disabled}}"
+ data-title-unavailable="{{$location_unavailable}}"
+ data-title-clear="{{$location_clear}}"
+ title="{{$location_set}}"
+ tabindex="6">
+ <i class="fa fa-map-marker" aria-hidden="true"></i>
+ </button>
+ </span>
+{{/if}}
+ <span role="presentation" id="profile-rotator-wrapper">
+ <img role="presentation" id="profile-rotator" src="images/rotator.gif" alt="{{$wait}}" title="{{$wait}}" style="display: none;" />
+ </span>
+ <span role="presentation" id="character-counter" class="grey text-info"></span>
+ {{if $preview}}
+ <button type="button" class="btn btn-defaul btn-sm" onclick="preview_comment({{$id}});" id="comment-edit-preview-link-{{$id}}" tabindex="5"><i class="fa fa-eye"></i> {{$preview}}</button>
+ {{/if}}
+ <button type="submit" class="btn btn-primary btn-sm" id="comment-edit-submit-{{$id}}" name="submit" tabindex="4"><i class="fa fa-envelope"></i> {{$submit}}</button>
+ </p>
+
+ <div id="comment-edit-preview-{{$id}}" class="comment-edit-preview" style="display:none;"></div>
+
+ <input type="hidden" name="group_allow" value="{{$group_allow}}" {{if $visibility == 'public'}}disabled{{/if}}/>
+ <input type="hidden" name="contact_allow" value="{{$contact_allow}}" {{if $visibility == 'public'}}disabled{{/if}}/>
+ <input type="hidden" name="group_deny" value="{{$group_deny}}" {{if $visibility == 'public'}}disabled{{/if}}/>
+ <input type="hidden" name="contact_deny" value="{{$contact_deny}}" {{if $visibility == 'public'}}disabled{{/if}}/>
+{{if $type == 'post'}}
+ <h3>Visibility</h3>
+ <div class="panel-group" id="visibility-accordion" role="tablist" aria-multiselectable="true">
+ <div class="panel panel-success">
+ <div class="panel-heading" id="visibility-public-heading" role="button" data-toggle="collapse" data-parent="#visibility-accordion" href="#visibility-public-panel" aria-expanded="true" aria-controls="visibility-public-panel" tabindex="14">
+ <label>
+ <input type="radio" name="visibility" id="visibility-public" value="public" {{if $visibility == 'public'}}checked{{/if}} style="display:none">
+ <i class="fa fa-globe"></i> {{$public_title}}
+ </label>
+ </div>
+ <div id="visibility-public-panel" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="visibility-public-heading">
+ <div class="panel-body">
+ <p>{{$public_desc}}</p>
+ {{if $doesFederate && $jotnets_fields}}
+ {{if $jotnets_fields|count < 3}}
+ <div class="profile-jot-net">
+ {{else}}
+ <details class="profile-jot-net">
+ <summary>{{$jotnets_summary}}</summary>
+ {{/if}}
+
+ {{foreach $jotnets_fields as $jotnets_field}}
+ {{if $jotnets_field.type == 'checkbox'}}
+ {{include file="field_checkbox.tpl" field=$jotnets_field.field}}
+ {{elseif $jotnets_field.type == 'select'}}
+ {{include file="field_select.tpl" field=$jotnets_field.field}}
+ {{/if}}
+ {{/foreach}}
+
+ {{if $jotnets_fields|count >= 3}}
+ </details>
+ {{else}}
+ </div>
+ {{/if}}
+ {{/if}}
+ </div>
+ </div>
+ </div>
+ <div class="panel panel-info">
+ <div class="panel-heading collapsed" id="visibility-custom-heading" role="button" data-toggle="collapse" data-parent="#visibility-accordion" href="#visibility-custom-panel" aria-expanded="true" aria-controls="visibility-custom-panel" tabindex="15">
+ <label>
+ <input type="radio" name="visibility" id="visibility-custom" value="custom" {{if $visibility == 'custom'}}checked{{/if}} style="display:none">
+ <i class="fa fa-lock"></i> {{$custom_title}}
+ </label>
+ </div>
+ <div id="visibility-custom-panel" class="panel-collapse collapse" role="tabpanel" aria-labelledby="visibility-custom-heading">
+ <div class="panel-body">
+ <p>{{$custom_desc}}</p>
+
+ <div class="form-group">
+ <label for="acl_allow">Deliver to:</label>
+ <input type="text" class="form-control input-lg" id="acl_allow">
+ </div>
+
+ <div class="form-group">
+ <label for="acl_deny">Except to:</label>
+ <input type="text" class="form-control input-lg" id="acl_deny">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ {{if $doesFederate}}
+ <div class="form-group">
+ <label for="profile-jot-email" id="profile-jot-email-label">{{$emailcc}}</label>
+ <input type="text" name="emailcc" id="profile-jot-email" class="form-control" title="{{$emtitle}}" />
+ </div>
+ <div id="profile-jot-email-end"></div>
+ {{/if}}
+ <div class="jotplugins">
+ {{$jotplugins nofilter}}
+ </div>
+{{/if}}
+ </form>
+ </div>
+</div>
\ No newline at end of file
<input type="hidden" name="parent" value="{{$parent}}" />
<input type="hidden" name="return" value="{{$return_path}}" />
<input type="hidden" name="jsreload" value="{{$jsreload}}" />
- <input type="hidden" name="preview" id="comment-preview-inp-{{$id}}" value="0" />
<div class="comment-edit-photo" id="comment-edit-photo-{{$id}}">
<a class="comment-edit-photo-link" href="{{$mylink}}" title="{{$mytitle}}"><img class="my-comment-photo" src="{{$myphoto}}" alt="{{$mytitle}}" title="{{$mytitle}}" /></a>
<input type="hidden" name="parent" value="{{$parent}}" />
{{*<!--<input type="hidden" name="return" value="{{$return_path}}" />-->*}}
<input type="hidden" name="jsreload" value="{{$jsreload}}" />
- <input type="hidden" name="preview" id="comment-preview-inp-{{$id}}" value="0" />
<input type="hidden" name="post_id_random" value="{{$rand_num}}" />
<div class="comment-edit-photo" id="comment-edit-photo-{{$id}}">
<input type="hidden" name="parent" value="{{$parent}}" />
<input type="hidden" name="return" value="{{$return_path}}" />
<input type="hidden" name="jsreload" value="{{$jsreload}}" />
- <input type="hidden" name="preview" id="comment-preview-inp-{{$id}}" value="0" />
<div class="comment-edit-photo" id="comment-edit-photo-{{$id}}">
<a class="comment-edit-photo-link" href="{{$mylink}}" title="{{$mytitle}}"><img class="my-comment-photo" src="{{$myphoto}}" alt="{{$mytitle}}" title="{{$mytitle}}" /></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);
}
}
}
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);
}
}
$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);
}
}
$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');
$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);
}
'$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 <a href="compose">the new Compose page</a>.')],
];
if (array_key_exists('login_bg_image', $arr) && !array_key_exists('login_bg_image', $disable)) {
background-image: none;
text-shadow: none;
border-radius: 3px;
- outline: 0!important;
margin-bottom: 0;
font-size: 14px;
font-weight: 600;
section #jotOpen {
display: none;
}
-#jotOpen {
+#jotOpen,
+#composeOpen {
margin-top: 3px;
float: right;
}
#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;
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;
}
background-image: none;
text-shadow: none;
border-radius: 3px;
- outline: 0!important;
margin-bottom: 0;
font-size: 14px;
font-weight: 600;
background-image: none;
text-shadow: none;
border-radius: 3px;
- outline: 0!important;
margin-bottom: 0;
font-size: 14px;
font-weight: 600;
--- /dev/null
+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.
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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 = $('<div class="friendica-tagsinput"></div>');
+ this.$container.addClass(this.$element.attr('class'));
+ this.$input = $('<input type="text" placeholder="' + this.placeholderText + '"/>').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 = $('<span class="tag ' + htmlEncode(tagClass) + (itemTitle !== null ? ('" title="' + itemTitle) : '') + '">' +
+ (itemThumb !== null ? '<img src="' + itemThumb + '" alt="">' : '') +
+ htmlEncode(itemText) + '<span data-role="remove"></span>' +
+ '</span>');
+ $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 <option /> if item represents a value not present in one of the <select />'s options
+ if (self.isSelect && !optionExists) {
+ var $option = $('<option selected>' + htmlEncode(itemText) + '</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, "<strong>$1</strong>" );
+ }
+ }));
+ }
+
+ // 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 = $('<div />');
+ 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);
});
// 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
<input type="hidden" name="parent" value="{{$parent}}" />
{{*<!--<input type="hidden" name="return" value="{{$return_path}}" />-->*}}
<input type="hidden" name="jsreload" value="{{$jsreload}}" />
- <input type="hidden" name="preview" id="comment-preview-inp-{{$id}}" value="0" />
<input type="hidden" name="post_id_random" value="{{$rand_num}}" />
<p class="comment-edit-bb-{{$id}} comment-icon-list">
{{* The button to open the jot - in This theme we move the button with js to the second nav bar *}}
<button class="btn btn-sm btn-main pull-right" id="jotOpen" aria-label="{{$new_post}}" title="{{$new_post}}" onclick="jotShow();"><i class="fa fa-pencil-square-o fa-2x"></i></button>
-
+<a class="btn btn-sm btn-main pull-right" id="composeOpen" href="compose/{{$posttype}}{{if $content}}?body={{$content}}{{/if}}" aria-label="{{$new_post}}" title="{{$new_post}}"><i class="fa fa-pencil-square-o fa-2x"></i></a>
<div id="jot-content">
<div id="jot-sections">
});
</script>
+<div class="form-group">
+ {{include file="field_checkbox.tpl" field=$enable_compose}}
+</div>
+
<div class="settings-submit-wrapper form-group pull-right">
<button type="submit" value="{{$submit}}" class="settings-submit btn btn-primary" name="frio-settings-submit">{{$submit}}</button>
</div>
</script>
EOT;
}
+
+ $enable_compose = \Friendica\Core\PConfig::get(local_user(), 'frio', 'enable_compose');
+ $compose = $enable_compose === '1' || $enable_compose === null && Config::get('frio', 'enable_compose') ? 1 : 0;
+ $a->page['htmlhead'] .= <<< HTML
+ <script type="text/javascript">
+ var compose = $compose;
+ </script>
+HTML;
}
function frio_install()
<input type="hidden" name="parent" value="{{$parent}}" />
{{*<!--<input type="hidden" name="return" value="{{$return_path}}" />-->*}}
<input type="hidden" name="jsreload" value="{{$jsreload}}" />
- <input type="hidden" name="preview" id="comment-preview-inp-{{$id}}" value="0" />
<input type="hidden" name="post_id_random" value="{{$rand_num}}" />
<div class="comment-edit-photo" id="comment-edit-photo-{{$id}}">
<input type="hidden" name="parent" value="{{$parent}}" />
{{*<!--<input type="hidden" name="return" value="{{$return_path}}" />-->*}}
<input type="hidden" name="jsreload" value="{{$jsreload}}" />
- <input type="hidden" name="preview" id="comment-preview-inp-{{$id}}" value="0" />
<input type="hidden" name="post_id_random" value="{{$rand_num}}" />
<div class="comment-edit-photo" id="comment-edit-photo-{{$id}}">