Merge pull request #7381 from MrPetovan/task/7309-frio-compose
[frio] New Compose page
This commit is contained in:
commit
e8459cce34
32 changed files with 1730 additions and 184 deletions
|
@ -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": [
|
||||
{
|
||||
|
|
166
composer.lock
generated
166
composer.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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[] = '<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))
|
||||
);
|
||||
|
@ -89,6 +103,18 @@ function lockview_content(App $a)
|
|||
}
|
||||
|
||||
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))
|
||||
);
|
||||
|
|
|
@ -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 = '';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2802,7 +2802,7 @@ class Item extends BaseObject
|
|||
$replace = true;
|
||||
}
|
||||
} elseif ($item) {
|
||||
if (self::samePermissions($item, $photo)) {
|
||||
if (self::samePermissions($uid, $item, $photo)) {
|
||||
$replace = true;
|
||||
}
|
||||
}
|
||||
|
@ -2852,7 +2852,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'])
|
||||
|
@ -2873,12 +2873,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);
|
||||
|
|
217
src/Module/Item/Compose.php
Normal file
217
src/Module/Item/Compose.php
Normal file
|
@ -0,0 +1,217 @@
|
|||
<?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)
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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><tt>";
|
||||
$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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
<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}}" >
|
||||
|
|
251
view/templates/item/compose-footer.tpl
Normal file
251
view/templates/item/compose-footer.tpl
Normal file
|
@ -0,0 +1,251 @@
|
|||
<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>
|
156
view/templates/item/compose.tpl
Normal file
156
view/templates/item/compose.tpl
Normal file
|
@ -0,0 +1,156 @@
|
|||
<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>
|
|
@ -6,7 +6,6 @@
|
|||
<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>
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
<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}}">
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
<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>
|
||||
|
|
|
@ -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 <a href="compose">the new Compose page</a>.')],
|
||||
];
|
||||
|
||||
if (array_key_exists('login_bg_image', $arr) && !array_key_exists('login_bg_image', $disable)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
20
view/theme/frio/frameworks/friendica-tagsinput/LICENSE
Normal file
20
view/theme/frio/frameworks/friendica-tagsinput/LICENSE
Normal file
|
@ -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.
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 = $('<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);
|
|
@ -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
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
<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">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{{* 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">
|
||||
|
|
|
@ -183,6 +183,10 @@
|
|||
});
|
||||
</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>
|
||||
|
|
|
@ -41,6 +41,14 @@ function frio_init(App $a)
|
|||
</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()
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
<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}}">
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
<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}}">
|
||||
|
|
Loading…
Reference in a new issue