]> git.mxchange.org Git - friendica.git/commitdiff
Merge remote-tracking branch 'upstream/develop' into contact-id-new
authorMichael <heluecht@pirati.ca>
Fri, 2 Aug 2019 16:52:34 +0000 (16:52 +0000)
committerMichael <heluecht@pirati.ca>
Fri, 2 Aug 2019 16:52:34 +0000 (16:52 +0000)
33 files changed:
composer.json
composer.lock
include/text.php
mod/lockview.php
mod/network.php
src/App/Router.php
src/Core/Protocol.php
src/Model/Group.php
src/Model/Item.php
src/Module/Item/Compose.php [new file with mode: 0644]
src/Protocol/ActivityPub/Processor.php
src/Worker/Notifier.php
tests/include/TextTest.php
view/js/main.js
view/templates/comment_item.tpl
view/templates/item/compose-footer.tpl [new file with mode: 0644]
view/templates/item/compose.tpl [new file with mode: 0644]
view/templates/moderated_comment.tpl
view/theme/duepuntozero/templates/comment_item.tpl
view/theme/duepuntozero/templates/moderated_comment.tpl
view/theme/frio/config.php
view/theme/frio/css/style.css
view/theme/frio/frameworks/friendica-tagsinput/LICENSE [new file with mode: 0644]
view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput-typeahead.css [new file with mode: 0644]
view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.css [new file with mode: 0644]
view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.js [new file with mode: 0644]
view/theme/frio/js/theme.js
view/theme/frio/templates/comment_item.tpl
view/theme/frio/templates/jot.tpl
view/theme/frio/templates/theme_settings.tpl
view/theme/frio/theme.php
view/theme/quattro/templates/comment_item.tpl
view/theme/vier/templates/comment_item.tpl

index 82ab4d93ba0bbf74d058c208f007f698834f5650..7f4e0a9b882f0dcd5a9c4ad71d302e1e0389e984 100644 (file)
                "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": [
                {
index e1cefeed8cdb4e0f0c7aa4423e1a3285445e24b7..5b9013a0cc528dbe2e3ebea884247b6c3593ce4b 100644 (file)
@@ -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",
             "dist": {
                 "type": "tar",
                 "url": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.2.2.tgz",
-                "reference": null,
                 "shasum": "30dc7a7ce872155b23a33bd10ad4c76c0d613f55"
             },
             "require-dev": {
             "dist": {
                 "type": "tar",
                 "url": "https://registry.npmjs.org/ev-emitter/-/ev-emitter-1.1.1.tgz",
-                "reference": null,
                 "shasum": "8f18b0ce5c76a5d18017f71c0a795c65b9138f2a"
             },
             "type": "npm-asset-library",
             "dist": {
                 "type": "tar",
                 "url": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-3.10.0.tgz",
-                "reference": null,
                 "shasum": "cc5e87d518fd6550e142816a31dd191664847919"
             },
             "type": "npm-asset-library",
             "dist": {
                 "type": "tar",
                 "url": "https://registry.npmjs.org/imagesloaded/-/imagesloaded-4.1.4.tgz",
-                "reference": null,
                 "shasum": "1376efcd162bb768c34c3727ac89cc04051f3cc7"
             },
             "require": {
                 "npm-asset/ev-emitter": ">=1.0.0,<2.0.0"
             },
-            "require-dev": {
-                "npm-asset/chalk": ">=1.1.1,<2.0.0",
-                "npm-asset/cheerio": ">=0.19.0,<0.20.0",
-                "npm-asset/gulp": ">=3.9.0,<4.0.0",
-                "npm-asset/gulp-jshint": ">=1.11.2,<2.0.0",
-                "npm-asset/gulp-json-lint": ">=0.1.0,<0.2.0",
-                "npm-asset/gulp-rename": ">=1.2.2,<2.0.0",
-                "npm-asset/gulp-replace": ">=0.5.4,<0.6.0",
-                "npm-asset/gulp-requirejs-optimize": "dev-github:metafizzy/gulp-requirejs-optimize",
-                "npm-asset/gulp-uglify": ">=1.4.2,<2.0.0",
-                "npm-asset/gulp-util": ">=3.0.7,<4.0.0",
-                "npm-asset/highlight.js": ">=8.9.1,<9.0.0",
-                "npm-asset/marked": ">=0.3.5,<0.4.0",
-                "npm-asset/minimist": ">=1.2.0,<2.0.0",
-                "npm-asset/transfob": ">=1.0.0,<2.0.0"
-            },
             "type": "npm-asset-library",
             "extra": {
                 "npm-asset-bugs": {
             "dist": {
                 "type": "tar",
                 "url": "https://registry.npmjs.org/jgrowl/-/jgrowl-1.4.6.tgz",
-                "reference": null,
                 "shasum": "2736e332aaee73ccf0a14a5f0066391a0a13f4a3"
             },
-            "require-dev": {
-                "npm-asset/grunt": "~0.4.2",
-                "npm-asset/grunt-contrib-cssmin": "~0.9.0",
-                "npm-asset/grunt-contrib-jshint": "~0.6.3",
-                "npm-asset/grunt-contrib-less": "~0.11.0",
-                "npm-asset/grunt-contrib-uglify": "~0.4.0",
-                "npm-asset/grunt-contrib-watch": "~0.6.1"
-            },
             "type": "npm-asset-library",
             "extra": {
                 "npm-asset-bugs": {
             "dist": {
                 "type": "tar",
                 "url": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz",
-                "reference": null,
                 "shasum": "2c89d6889b5eac522a7eea32c14521559c6cbf02"
             },
-            "require-dev": {
-                "npm-asset/commitplease": "2.0.0",
-                "npm-asset/core-js": "0.9.17",
-                "npm-asset/grunt": "0.4.5",
-                "npm-asset/grunt-babel": "5.0.1",
-                "npm-asset/grunt-cli": "0.1.13",
-                "npm-asset/grunt-compare-size": "0.4.0",
-                "npm-asset/grunt-contrib-jshint": "0.11.2",
-                "npm-asset/grunt-contrib-uglify": "0.9.2",
-                "npm-asset/grunt-contrib-watch": "0.6.1",
-                "npm-asset/grunt-git-authors": "2.0.1",
-                "npm-asset/grunt-jscs": "2.1.0",
-                "npm-asset/grunt-jsonlint": "1.0.4",
-                "npm-asset/grunt-npmcopy": "0.1.0",
-                "npm-asset/gzip-js": "0.3.2",
-                "npm-asset/jsdom": "5.6.1",
-                "npm-asset/load-grunt-tasks": "1.0.0",
-                "npm-asset/qunit-assert-step": "1.0.3",
-                "npm-asset/qunitjs": "1.17.1",
-                "npm-asset/requirejs": "2.1.17",
-                "npm-asset/sinon": "1.10.3",
-                "npm-asset/sizzle": "2.2.1",
-                "npm-asset/strip-json-comments": "1.0.3",
-                "npm-asset/testswarm": "1.1.0",
-                "npm-asset/win-spawn": "2.0.0"
-            },
             "type": "npm-asset-library",
             "extra": {
                 "npm-asset-bugs": {
             "dist": {
                 "type": "tar",
                 "url": "https://registry.npmjs.org/jquery-colorbox/-/jquery-colorbox-1.6.4.tgz",
-                "reference": null,
                 "shasum": "799452523a6c494839224ef702e807deb9c06cc5"
             },
             "require": {
             "dist": {
                 "type": "tar",
                 "url": "https://registry.npmjs.org/jquery-datetimepicker/-/jquery-datetimepicker-2.5.20.tgz",
-                "reference": null,
                 "shasum": "687d6204b90b03dc93f725f8df036e1d061f37ac"
             },
             "require": {
             "dist": {
                 "type": "tar",
                 "url": "https://registry.npmjs.org/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz",
-                "reference": null,
                 "shasum": "06f0335f16e353a695e7206bf50503cb523a6ee5"
             },
-            "require-dev": {
-                "npm-asset/grunt": "~0.4.1",
-                "npm-asset/grunt-contrib-connect": "~0.5.0",
-                "npm-asset/grunt-contrib-jshint": "~0.7.1",
-                "npm-asset/grunt-contrib-uglify": "~0.2.7"
-            },
             "type": "npm-asset-library",
             "extra": {
                 "npm-asset-bugs": {
             "dist": {
                 "type": "tar",
                 "url": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
-                "reference": null,
                 "shasum": "3c257f9839fc0e93ff53149632239eb90783ff66"
             },
             "type": "npm-asset-library",
             "homepage": "https://github.com/kartik-v/php-date-formatter",
             "time": "2018-07-13T06:56:46+00:00"
         },
+        {
+            "name": "npm-asset/typeahead.js",
+            "version": "0.11.1",
+            "dist": {
+                "type": "tar",
+                "url": "https://registry.npmjs.org/typeahead.js/-/typeahead.js-0.11.1.tgz",
+                "shasum": "4e64e671b22310a8606f4aec805924ba84b015b8"
+            },
+            "require": {
+                "npm-asset/jquery": ">=1.7"
+            },
+            "type": "npm-asset-library",
+            "extra": {
+                "npm-asset-bugs": {
+                    "url": "https://github.com/twitter/typeahead.js/issues"
+                },
+                "npm-asset-main": "dist/typeahead.bundle.js",
+                "npm-asset-directories": [],
+                "npm-asset-repository": {
+                    "type": "git",
+                    "url": "https://github.com/twitter/typeahead.js.git"
+                },
+                "npm-asset-scripts": {
+                    "test": "./node_modules/karma/bin/karma start --single-run --browsers PhantomJS"
+                }
+            },
+            "authors": [
+                {
+                    "name": "Twitter, Inc.",
+                    "url": "https://twitter.com/twitteross"
+                },
+                {
+                    "name": "Jake Harding",
+                    "url": "https://twitter.com/JakeHarding"
+                },
+                {
+                    "name": "Tim Trueman",
+                    "url": "https://twitter.com/timtrueman"
+                },
+                {
+                    "name": "Veljko Skarich",
+                    "url": "https://twitter.com/vskarich"
+                }
+            ],
+            "description": "fast and fully-featured autocomplete library",
+            "homepage": "http://twitter.github.com/typeahead.js",
+            "keywords": [
+                "autocomplete",
+                "typeahead"
+            ],
+            "time": "2015-04-27T04:03:42+00:00"
+        },
         {
             "name": "paragonie/certainty",
             "version": "v1.0.4",
             "time": "2017-03-25T17:14:26+00:00"
         },
         {
-            "name": "mikey179/vfsStream",
+            "name": "mikey179/vfsstream",
             "version": "v1.6.5",
             "source": {
                 "type": "git",
             "authors": [
                 {
                     "name": "Frank Kleine",
-                    "homepage": "http://frankkleine.de/",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "homepage": "http://frankkleine.de/"
                 }
             ],
             "description": "Virtual file system to mock the real file system in unit tests.",
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sb@sebastian-bergmann.de",
-                    "role": "lead"
+                    "role": "lead",
+                    "email": "sb@sebastian-bergmann.de"
                 }
             ],
             "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "role": "lead",
+                    "email": "sebastian@phpunit.de"
                 }
             ],
             "description": "The PHP Unit Testing framework.",
                 }
             ],
             "description": "Provides the functionality to compare PHP values for equality",
-            "homepage": "https://github.com/sebastianbergmann/comparator",
+            "homepage": "http://www.github.com/sebastianbergmann/comparator",
             "keywords": [
                 "comparator",
                 "compare",
                 }
             ],
             "description": "Provides functionality to handle HHVM/PHP environments",
-            "homepage": "https://github.com/sebastianbergmann/environment",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
             "keywords": [
                 "Xdebug",
                 "environment",
                 }
             ],
             "description": "Provides the functionality to export PHP variables for visualization",
-            "homepage": "https://github.com/sebastianbergmann/exporter",
+            "homepage": "http://www.github.com/sebastianbergmann/exporter",
             "keywords": [
                 "export",
                 "exporter"
                 }
             ],
             "description": "Snapshotting of global state",
-            "homepage": "https://github.com/sebastianbergmann/global-state",
+            "homepage": "http://www.github.com/sebastianbergmann/global-state",
             "keywords": [
                 "global state"
             ],
                 }
             ],
             "description": "Provides functionality to recursively process PHP variables",
-            "homepage": "https://github.com/sebastianbergmann/recursion-context",
+            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
             "time": "2016-11-19T07:33:16+00:00"
         },
         {
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.11.0",
+            "version": "v1.9.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "82ebae02209c21113908c229e9883c419720738a"
+                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
-                "reference": "82ebae02209c21113908c229e9883c419720738a",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
+                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.11-dev"
+                    "dev-master": "1.9-dev"
                 }
             },
             "autoload": {
                 "polyfill",
                 "portable"
             ],
-            "time": "2019-02-06T07:57:58+00:00"
+            "time": "2018-08-06T14:22:27+00:00"
         },
         {
             "name": "symfony/yaml",
-            "version": "v3.4.30",
+            "version": "v3.4.16",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
-                "reference": "051d045c684148060ebfc9affb7e3f5e0899d40b"
+                "reference": "61973ecda60e9f3561e929e19c07d4878b960fc1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/051d045c684148060ebfc9affb7e3f5e0899d40b",
-                "reference": "051d045c684148060ebfc9affb7e3f5e0899d40b",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/61973ecda60e9f3561e929e19c07d4878b960fc1",
+                "reference": "61973ecda60e9f3561e929e19c07d4878b960fc1",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Yaml Component",
             "homepage": "https://symfony.com",
-            "time": "2019-07-24T13:01:31+00:00"
+            "time": "2018-09-24T08:15:45+00:00"
         },
         {
             "name": "webmozart/assert",
index 7bed8b499433f1507c2fd410ed5b1837c10a217f..3d6bf6a5638943a23bae3245033bb21f766ad171 100644 (file)
@@ -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);
        }
index 35c4b043329ae4284250d95a398636407a3d5d8e..eede1b6a0dac4eca34d039a470b3d22f7e5dcdfa 100644 (file)
@@ -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))
                );
index 7354667200d0b1a6d9ab6c728737c3f18353ff08..fddec60c8dc86457e1273d551adb2d766e63babb 100644 (file)
@@ -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 = '';
index a54f3a711ee6cf87581b6fee082f4342cf7f2854..50b208792b76a08d55a4734f8c31edabd861f4ea 100644 (file)
@@ -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);
index 759a6e70e66160c78f45258e09c90d8bec3af857..0ecc076a0fd5501e9b355b175c5b6ca20d30b1b0 100644 (file)
@@ -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
index feff4661ab6f8856fb916d7b12ffe0eacd7a2099..50160baaf4a88d56a5bcf53d47514ded6f128641 100644 (file)
@@ -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;
        }
 }
index ef461fe65285a1a25af112fd330f7107dd03a388..01c2c3459d848cb8288c6eaa5851653ef1de04cc 100644 (file)
@@ -1246,7 +1246,6 @@ class Item extends BaseObject
                                return $contact_id;
                        }
                }
-
                return $item['author-id'];
        }
 
@@ -1472,6 +1471,9 @@ class Item extends BaseObject
 
                $item['plink'] = defaults($item, 'plink', System::baseUrl() . '/display/' . urlencode($item['guid']));
 
+               // The contact-id should be set before "self::insert" was called - but there seems to be issues sometimes
+               $item["contact-id"] = self::contactId($item);
+
                $default = ['url' => $item['author-link'], 'name' => $item['author-name'],
                        'photo' => $item['author-avatar'], 'network' => $item['network']];
 
@@ -1527,9 +1529,6 @@ class Item extends BaseObject
                unset($item['causer-id']);
                unset($item['causer-link']);
 
-               // The contact-id should be set before "self::insert" was called - but there seems to be issues sometimes
-               $item['contact-id'] = self::contactId($item);
-
                if ($item['network'] == Protocol::PHANTOM) {
                        $item['network'] = Protocol::DFRN;
                        Logger::notice('Missing network, setting to {network}.', [
@@ -2804,7 +2803,7 @@ class Item extends BaseObject
                                                                        $replace = true;
                                                                }
                                                        } elseif ($item) {
-                                                               if (self::samePermissions($item, $photo)) {
+                                                               if (self::samePermissions($uid, $item, $photo)) {
                                                                        $replace = true;
                                                                }
                                                        }
@@ -2854,7 +2853,7 @@ class Item extends BaseObject
                        !empty($obj['deny_cid']) || !empty($obj['deny_gid']);
        }
 
-       private static function samePermissions($obj1, $obj2)
+       private static function samePermissions($uid, $obj1, $obj2)
        {
                // first part is easy. Check that these are exactly the same.
                if (($obj1['allow_cid'] == $obj2['allow_cid'])
@@ -2875,12 +2874,12 @@ class Item extends BaseObject
        }
 
        // returns an array of contact-ids that are allowed to see this object
-       public static function enumeratePermissions($obj)
+       public static function enumeratePermissions(array $obj)
        {
                $allow_people = expand_acl($obj['allow_cid']);
-               $allow_groups = Group::expand(expand_acl($obj['allow_gid']));
+               $allow_groups = Group::expand($obj['uid'], expand_acl($obj['allow_gid']));
                $deny_people  = expand_acl($obj['deny_cid']);
-               $deny_groups  = Group::expand(expand_acl($obj['deny_gid']));
+               $deny_groups  = Group::expand($obj['uid'], expand_acl($obj['deny_gid']));
                $recipients   = array_unique(array_merge($allow_people, $allow_groups));
                $deny         = array_unique(array_merge($deny_people, $deny_groups));
                $recipients   = array_diff($recipients, $deny);
diff --git a/src/Module/Item/Compose.php b/src/Module/Item/Compose.php
new file mode 100644 (file)
index 0000000..09668af
--- /dev/null
@@ -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)
+               ]);
+       }
+}
index 65dd6d3e627e98d32978651a650c3f4c98d02681..7639d0f2a325d97ee6dc3ea6a9f2fb48a4f0d6b4 100644 (file)
@@ -419,8 +419,8 @@ class Processor
                                $item['contact-id'] = Contact::getIdForURL($activity['author'], $receiver, true);
                        }
 
-                       if (empty($item['contact-id'])) {
-                               $item['contact-id'] = $item['author-id'];
+                       if (($receiver != 0) && empty($item['contact-id'])) {
+                               $item['contact-id'] = Contact::getIdForURL($activity['author'], 0, true);
                        }
 
                        if (!empty($activity['directmessage'])) {
index 42ad0afb6dccaaae72d85e14d0e242eb04890c5c..4efd3e912622fd4e590afc0ebca29ee8af54af4d 100644 (file)
@@ -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
index e516fe824ae3e8e23cfe24f2551fbfbee44ba2cd..5676da8f62e4a3ee6ff9a58e8ceb7fe42d3ab132 100644 (file)
@@ -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));
        }
 
        /**
index a2e2698a821730dd81653f97781294bd21143dd2..8b1303e7d3f534e3937f6cc459d441b0385018d7 100644 (file)
@@ -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);
index 352ff417f0e01ab9812498e5a6165b7dbc26d807..977a5b75b2663eb49f86f80c9c3513b5ced23103 100644 (file)
@@ -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}}" >
diff --git a/view/templates/item/compose-footer.tpl b/view/templates/item/compose-footer.tpl
new file mode 100644 (file)
index 0000000..697be6f
--- /dev/null
@@ -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>
diff --git a/view/templates/item/compose.tpl b/view/templates/item/compose.tpl
new file mode 100644 (file)
index 0000000..d18b1a2
--- /dev/null
@@ -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>
\ No newline at end of file
index d52bf172a0e1957c6802dd51d30e664b100216cc..39dbbde4777a42f5e037dc01b14baac247a33c5e 100644 (file)
@@ -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>
index bfa5f093884d5cce04866eee21c2abc4bfb9c242..cd14b253c5efe3641ee3e1eedb758ce02a7a9fc3 100644 (file)
@@ -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}}">
index e5c2e5b9ad25f23e149ab31b8f175dec40750173..197d1281d35ff572972910bafb542d7049fdc548 100644 (file)
@@ -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>
index 0f3eda4c940a494372cdaf3e8e36878b3e1b2aad..df0f65a6b873e8cd4b301beea015c1e7d5d48333 100644 (file)
@@ -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)) {
index a88276f15df69f03d14c51f963321d23ebdb4109..c986b7232c9d3847ebe677ab16a83e9324d41629 100644 (file)
@@ -192,7 +192,6 @@ blockquote {
     background-image: none;
     text-shadow: none;
     border-radius: 3px;
-    outline: 0!important;
     margin-bottom: 0;
     font-size: 14px;
     font-weight: 600;
@@ -1314,7 +1313,8 @@ section ul.tabs {
 section #jotOpen {
     display: none;
 }
-#jotOpen {
+#jotOpen,
+#composeOpen {
     margin-top: 3px;
     float: right;
 }
@@ -1372,7 +1372,8 @@ section #jotOpen {
 #jot-text-wrap .preview textarea {
     width: 100%;
 }
-#preview_profile-jot-text {
+#preview_profile-jot-text,
+.comment-edit-form .preview {
     position: relative;
     padding: 0px 10px;
     margin-top: -2px;
@@ -1383,7 +1384,8 @@ section #jotOpen {
     background: #fff;
     color: #555;
 }
-textarea#profile-jot-text:focus + #preview_profile-jot-text {
+textarea#profile-jot-text:focus + #preview_profile-jot-text,
+textarea.comment-edit-text:focus + .comment-edit-form .preview {
     border: 2px solid #6fdbe8;
     border-top: none;
 }
@@ -3425,7 +3427,6 @@ section .profile-match-wrapper {
     background-image: none;
     text-shadow: none;
     border-radius: 3px;
-    outline: 0!important;
     margin-bottom: 0;
     font-size: 14px;
     font-weight: 600;
@@ -3444,7 +3445,6 @@ section .profile-match-wrapper {
     background-image: none;
     text-shadow: none;
     border-radius: 3px;
-    outline: 0!important;
     margin-bottom: 0;
     font-size: 14px;
     font-weight: 600;
diff --git a/view/theme/frio/frameworks/friendica-tagsinput/LICENSE b/view/theme/frio/frameworks/friendica-tagsinput/LICENSE
new file mode 100644 (file)
index 0000000..58bc985
--- /dev/null
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Tim Schlechter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput-typeahead.css b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput-typeahead.css
new file mode 100644 (file)
index 0000000..8cec654
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * friendica-tagsinput v0.8.0
+ * 
+ */
+
+.twitter-typeahead .tt-query,
+.twitter-typeahead .tt-hint {
+    margin-bottom: 0;
+}
+
+.twitter-typeahead .tt-hint
+{
+    display: none;
+}
+
+.tt-menu {
+    position: absolute;
+    top: 100%;
+    left: 0;
+    z-index: 1000;
+    display: none;
+    float: left;
+    min-width: 160px;
+    padding: 5px 0;
+    margin: 2px 0 0;
+    list-style: none;
+    font-size: 14px;
+    background-color: #ffffff;
+    border: 1px solid #cccccc;
+    border: 1px solid rgba(0, 0, 0, 0.15);
+    border-radius: 4px;
+    -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+    box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+    background-clip: padding-box;
+    cursor: pointer;
+}
+
+.tt-suggestion {
+    display: block;
+    padding: 3px 20px;
+    clear: both;
+    font-weight: normal;
+    line-height: 1.428571429;
+    color: #333333;
+    white-space: nowrap;
+}
+
+.tt-suggestion:hover,
+.tt-suggestion:focus {
+    color: #ffffff;
+    text-decoration: none;
+    outline: 0;
+    background-color: #428bca;
+}
diff --git a/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.css b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.css
new file mode 100644 (file)
index 0000000..f2e1197
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * friendica-tagsinput v0.8.0
+ * 
+ */
+
+.friendica-tagsinput {
+  background-color: #fff;
+  border: 1px solid #ccc;
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  display: inline-block;
+  padding: 4px 6px;
+  color: #555;
+  vertical-align: middle;
+  border-radius: 4px;
+  max-width: 100%;
+  line-height: 22px;
+  cursor: text;
+  height: auto;
+}
+.friendica-tagsinput.input-lg {
+  line-height: 27px;
+}
+.friendica-tagsinput input {
+  border: none;
+  box-shadow: none;
+  outline: none;
+  background-color: transparent;
+  padding: 0 6px;
+  margin: 0;
+  width: auto;
+  max-width: inherit;
+}
+.friendica-tagsinput.form-control input::-moz-placeholder {
+  color: #777;
+  opacity: 1;
+}
+.friendica-tagsinput.form-control input:-ms-input-placeholder {
+  color: #777;
+}
+.friendica-tagsinput.form-control input::-webkit-input-placeholder {
+  color: #777;
+}
+.friendica-tagsinput input:focus {
+  border: none;
+  box-shadow: none;
+}
+.friendica-tagsinput .tag {
+  margin: 0 2px 2px 0;
+  color: white;
+  font-weight: normal;
+}
+.friendica-tagsinput .tag img {
+  width: auto;
+  height: 1.5em;
+  vertical-align: text-top;
+  margin-right: 8px;
+}
+.friendica-tagsinput .tag [data-role="remove"] {
+  margin-left: 8px;
+  cursor: pointer;
+}
+.friendica-tagsinput .tag [data-role="remove"]:after {
+  content: "x";
+  padding: 0px 2px;
+  font-weight: bold;
+}
+.friendica-tagsinput .tag [data-role="remove"]:hover {
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+.friendica-tagsinput .tag [data-role="remove"]:hover:active {
+  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
diff --git a/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.js b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.js
new file mode 100644 (file)
index 0000000..2e83eed
--- /dev/null
@@ -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);
index 19b5c4fa6e72a70e9c9216c884202580ecbc56bc..44a24b30a11884e1cfae92537a5247922d5a6012 100644 (file)
@@ -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
index 98b3c4563632f1e203e5fc61c803bbde1b663607..b60f5c429e8fb76074ff7e94092e336967d14105 100644 (file)
@@ -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">
index d5ba1bcc458e4f503947fc2548f194e57e750e13..8b103eba562aa7875c747de44a696801ea3015a3 100644 (file)
@@ -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">
index 3ac6282cabc3f02df46f76fe305a6ea66c3b7122..9c32558b8ebde183722d47c0c2c9312c3859da86 100644 (file)
        });
 </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>
index a209664b9388e89c21fd4543ca61de084e848e60..5995f54374171f4b2e701e31a8b96f182f22f621 100644 (file)
@@ -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()
index 774ffac2757e887a511a6ba6ad181b40f42cb71f..1fa4e333ed7bd46c6b904f906f0526610b3111b6 100644 (file)
@@ -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}}">
index 8ccdccb80d2878f17e9e9d8ab395ec11e3864e11..0e2cbb5bbfe01d7b099ce5e3ae31fe990e5b6d57 100644 (file)
@@ -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}}">