]> git.mxchange.org Git - friendica.git/commitdiff
Use visibility tags input for the default ACL selector
authorHypolite Petovan <hypolite@mrpetovan.com>
Thu, 28 Nov 2019 17:42:12 +0000 (12:42 -0500)
committerHypolite Petovan <hypolite@mrpetovan.com>
Thu, 28 Nov 2019 17:54:53 +0000 (12:54 -0500)
- Move friendica-tagsinput to default view folder
- Update all references to ACL::getFullSelectorHTML
- Fix theme-specific issues with the new ACL

24 files changed:
mod/community.php
mod/display.php
mod/events.php
mod/network.php
mod/photos.php
mod/settings.php
src/Core/ACL.php
src/Module/Bookmarklet.php
src/Module/Contact.php
src/Module/Profile.php
view/js/friendica-tagsinput/LICENSE [new file with mode: 0644]
view/js/friendica-tagsinput/friendica-tagsinput-typeahead.css [new file with mode: 0644]
view/js/friendica-tagsinput/friendica-tagsinput.css [new file with mode: 0644]
view/js/friendica-tagsinput/friendica-tagsinput.js [new file with mode: 0644]
view/templates/acl_selector.tpl
view/theme/frio/frameworks/friendica-tagsinput/LICENSE [deleted file]
view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput-typeahead.css [deleted file]
view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.css [deleted file]
view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.js [deleted file]
view/theme/frio/js/event_edit.js
view/theme/frio/js/modal.js
view/theme/frio/templates/acl_selector.tpl [deleted file]
view/theme/frio/templates/event_form.tpl
view/theme/smoothly/templates/jot.tpl

index 81857c6d3a0d64a3cacd78940a0864bf20edaf35..4d98f0c4fa24d6dabd74ad7097bb34e1b240f168 100644 (file)
@@ -125,7 +125,7 @@ function community_content(App $a, $update = 0)
                                'default_location' => $a->user['default-location'],
                                'nickname' => $a->user['nickname'],
                                'lockstate' => (is_array($a->user) && (strlen($a->user['allow_cid']) || strlen($a->user['allow_gid']) || strlen($a->user['deny_cid']) || strlen($a->user['deny_gid'])) ? 'lock' : 'unlock'),
-                               'acl' => ACL::getFullSelectorHTML($a->user, true),
+                               'acl' => ACL::getFullSelectorHTML($a->page, $a->user, true),
                                'bang' => '',
                                'visitor' => 'block',
                                'profile_uid' => local_user(),
index 12fa8d7ecefb9c320e71b641b2f0e9af216cc2d0..175616f98d9c4ef77a292773d67b95dbc0d25c80 100644 (file)
@@ -304,7 +304,7 @@ function display_content(App $a, $update = false, $update_uid = 0)
                        'default_location' => $a->user['default-location'],
                        'nickname' => $a->user['nickname'],
                        'lockstate' => (is_array($a->user) && (strlen($a->user['allow_cid']) || strlen($a->user['allow_gid']) || strlen($a->user['deny_cid']) || strlen($a->user['deny_gid'])) ? 'lock' : 'unlock'),
-                       'acl' => ACL::getFullSelectorHTML($a->user, true),
+                       'acl' => ACL::getFullSelectorHTML($a->page, $a->user, true),
                        'bang' => '',
                        'visitor' => 'block',
                        'profile_uid' => local_user(),
index 11bb25f51b9f8f8ec10e686e7f7a4ce9c2ee132e..a642f166510cff22a2da9ef9c97a6063107e720a 100644 (file)
@@ -13,6 +13,7 @@ use Friendica\Core\L10n;
 use Friendica\Core\Logger;
 use Friendica\Core\Renderer;
 use Friendica\Core\System;
+use Friendica\Core\Theme;
 use Friendica\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\Model\Event;
@@ -384,6 +385,12 @@ function events_content(App $a)
                        $events[$key]['item'] = $event_item;
                }
 
+               // ACL blocks are loaded in modals in frio
+               $a->page->registerFooterScript(Theme::getPathForFile('asset/typeahead.js/dist/typeahead.bundle.js'));
+               $a->page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js'));
+               $a->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
+               $a->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css'));
+
                $o = Renderer::replaceMacros($tpl, [
                        '$tabs'      => $tabs,
                        '$title'     => L10n::t('Events'),
@@ -486,7 +493,7 @@ function events_content(App $a)
                $perms = ACL::getDefaultUserPermissions($orig_event);
 
                if (!$cid && in_array($mode, ['new', 'copy'])) {
-                       $acl = ACL::getFullSelectorHTML($a->user, false, $orig_event);
+                       $acl = ACL::getFullSelectorHTML($a->page, $a->user, false, $perms);
                } else {
                        $acl = '';
                }
@@ -506,11 +513,6 @@ function events_content(App $a)
                        '$cid'  => $cid,
                        '$uri'  => $uri,
 
-                       '$allow_cid' => json_encode($perms['allow_cid']),
-                       '$allow_gid' => json_encode($perms['allow_gid']),
-                       '$deny_cid'  => json_encode($perms['deny_cid']),
-                       '$deny_gid'  => json_encode($perms['deny_gid']),
-
                        '$title' => L10n::t('Event details'),
                        '$desc' => L10n::t('Starting date and Title are required.'),
                        '$s_text' => L10n::t('Event Starts:') . ' <span class="required" title="' . L10n::t('Required') . '">*</span>',
index 5fbfa9a5d8e736adaaa6c2ba10a4dc66d4072308..44c7c8b44f9aa4859baf0d606cbee35e7ac1465c 100644 (file)
@@ -377,7 +377,7 @@ function networkFlatView(App $a, $update = 0)
                        (strlen($a->user['allow_cid']) || strlen($a->user['allow_gid']) ||
                        strlen($a->user['deny_cid']) || strlen($a->user['deny_gid'])) ? 'lock' : 'unlock'),
                        'default_perms' => ACL::getDefaultUserPermissions($a->user),
-                       'acl' => ACL::getFullSelectorHTML($a->user, true),
+                       'acl' => ACL::getFullSelectorHTML($a->page, $a->user, true),
                        'bang' => '',
                        'visitor' => 'block',
                        'profile_uid' => local_user(),
@@ -554,7 +554,7 @@ function networkThreadedView(App $a, $update, $parent)
                        (strlen($a->user['allow_cid']) || strlen($a->user['allow_gid']) ||
                        strlen($a->user['deny_cid']) || strlen($a->user['deny_gid']))) ? 'lock' : 'unlock'),
                        'default_perms' => ACL::getDefaultUserPermissions($a->user),
-                       'acl' => ACL::getFullSelectorHTML($a->user, true, $default_permissions),
+                       'acl' => ACL::getFullSelectorHTML($a->page, $a->user, true, $default_permissions),
                        'bang' => (($gid || $cid || $nets) ? '!' : ''),
                        'visitor' => 'block',
                        'profile_uid' => local_user(),
index e0630e7dc9635266efb22d165125770f8a4ca28e..684e525d4376b3d04521eac10ba32612a3eaa4b2 100644 (file)
@@ -960,7 +960,7 @@ function photos_content(App $a)
 
                $tpl = Renderer::getMarkupTemplate('photos_upload.tpl');
 
-               $aclselect_e = ($visitor ? '' : ACL::getFullSelectorHTML($a->user));
+               $aclselect_e = ($visitor ? '' : ACL::getFullSelectorHTML($a->page, $a->user));
 
                $o .= Renderer::replaceMacros($tpl,[
                        '$pagename' => L10n::t('Upload Photos'),
@@ -1332,7 +1332,7 @@ function photos_content(App $a)
 
                        $album_e = $ph[0]['album'];
                        $caption_e = $ph[0]['desc'];
-                       $aclselect_e = ACL::getFullSelectorHTML($a->user, false, $ph[0]);
+                       $aclselect_e = ACL::getFullSelectorHTML($a->page, $a->user, false, $ph[0]);
 
                        $edit = Renderer::replaceMacros($edit_tpl, [
                                '$id' => $ph[0]['id'],
index 5ae4086b61741e48ce7145b555a06f073f2569de..2ab4cc8f3cc628cd5166e4741d4d982261e4e6e9 100644 (file)
@@ -1197,7 +1197,7 @@ function settings_content(App $a)
                '$permissions' => L10n::t('Default Post Permissions'),
                '$permdesc' => L10n::t("\x28click to open/close\x29"),
                '$visibility' => $profile['net-publish'],
-               '$aclselect' => ACL::getFullSelectorHTML($a->user),
+               '$aclselect' => ACL::getFullSelectorHTML($a->page, $a->user),
                '$suggestme' => $suggestme,
                '$blockwall'=> $blockwall, // array('blockwall', L10n::t('Allow friends to post to your profile page:'), !$blockwall, ''),
                '$blocktags'=> $blocktags, // array('blocktags', L10n::t('Allow friends to tag your posts:'), !$blocktags, ''),
index ccc2b34d5d2d8a86d907db4b8d3bb13d8f5acb46..880a1e47ff4f6df6de22747b5513a4b21140f41b 100644 (file)
@@ -6,13 +6,10 @@
 
 namespace Friendica\Core;
 
+use Friendica\App\Page;
 use Friendica\BaseObject;
-use Friendica\Content\Feature;
 use Friendica\Database\DBA;
 use Friendica\Model\Contact;
-use Friendica\Model\GContact;
-use Friendica\Core\Session;
-use Friendica\Util\Network;
 use Friendica\Model\Group;
 
 /**
@@ -312,26 +309,50 @@ class ACL extends BaseObject
        /**
         * Return the full jot ACL selector HTML
         *
+        * @param Page  $page
         * @param array $user                User array
-        * @param bool  $show_jotnets
-        * @param array $default_permissions Static defaults permission array: ['allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '']
+        * @param bool  $for_federation
+        * @param array $default_permissions Static defaults permission array:
+        *                                   [
+        *                                      'allow_cid' => [],
+        *                                      'allow_gid' => [],
+        *                                      'deny_cid' => [],
+        *                                      'deny_gid' => [],
+        *                                      'hidewall' => true/false
+        *                                   ]
         * @return string
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       public static function getFullSelectorHTML(array $user = null, $show_jotnets = false, array $default_permissions = [])
+       public static function getFullSelectorHTML(Page $page, array $user = null, bool $for_federation = false, array $default_permissions = [])
        {
+               $page->registerFooterScript(Theme::getPathForFile('asset/typeahead.js/dist/typeahead.bundle.js'));
+               $page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js'));
+               $page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
+               $page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css'));
+
                // Defaults user permissions
                if (empty($default_permissions)) {
                        $default_permissions = self::getDefaultUserPermissions($user);
                }
 
+               if (count($default_permissions['allow_cid'])
+                       + count($default_permissions['allow_gid'])
+                       + count($default_permissions['deny_cid'])
+                       + count($default_permissions['deny_gid'])) {
+                       $visibility = 'custom';
+               } else {
+                       $visibility = 'public';
+                       // Default permission display for custom panel
+                       $default_permissions['allow_gid'] = [Group::FOLLOWERS];
+               }
+
                $jotnets_fields = [];
-               if ($show_jotnets) {
+               if ($for_federation) {
                        $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()]);
+                               $mailacct = DBA::selectFirst('mailacct', ['pubmail'], ['`uid` = ? AND `server` != ""', $user['úid']]);
                                if (DBA::isResult($mailacct)) {
                                        $mail_enabled = true;
                                        $pubmail_enabled = !empty($mailacct['pubmail']);
@@ -354,27 +375,35 @@ class ACL extends BaseObject
                        }
                }
 
+               $acl_contacts = self::getContactListByUserId($user['uid']);
+
+               $acl_groups = self::getGroupListByUserId($user['uid']);
+
+               $acl_list = array_merge($acl_groups, $acl_contacts);
+
                $tpl = Renderer::getMarkupTemplate('acl_selector.tpl');
                $o = Renderer::replaceMacros($tpl, [
-                       '$showall' => L10n::t('Visible to everybody'),
-                       '$show' => L10n::t('show'),
-                       '$hide' => L10n::t('don\'t show'),
-                       '$allowcid' => json_encode(($default_permissions['allow_cid'] ?? '') ?: []), // We need arrays for
-                       '$allowgid' => json_encode(($default_permissions['allow_gid'] ?? '') ?: []), // Javascript since we
-                       '$denycid'  => json_encode(($default_permissions['deny_cid']  ?? '') ?: []), // call .remove() and
-                       '$denygid'  => json_encode(($default_permissions['deny_gid']  ?? '') ?: []), // .push() on these values
-                       '$networks' => $show_jotnets,
-                       '$emailcc' => L10n::t('CC: email addresses'),
-                       '$emtitle' => L10n::t('Example: bob@example.com, mary@example.com'),
-                       '$jotnets_enabled' => empty($default_permissions['hidewall']),
+                       '$public_title'   => L10n::t('Public'),
+                       '$public_desc'    => L10n::t('This content will be shown 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 content will be shown 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.'),
+                       '$allow_label'    => L10n::t('Show to:'),
+                       '$deny_label'     => L10n::t('Except to:'),
+                       '$emailcc'        => L10n::t('CC: email addresses'),
+                       '$emtitle'        => L10n::t('Example: bob@example.com, mary@example.com'),
                        '$jotnets_summary' => L10n::t('Connectors'),
-                       '$jotnets_fields' => $jotnets_fields,
                        '$jotnets_disabled_label' => L10n::t('Connectors disabled, since "%s" is enabled.', L10n::t('Hide your profile details from unknown viewers?')),
-                       '$aclModalTitle' => L10n::t('Permissions'),
-                       '$aclModalDismiss' => L10n::t('Close'),
-                       '$features' => [
-                               'aclautomention' => !empty($user['uid']) && Feature::isEnabled($user['uid'], 'aclautomention') ? 'true' : 'false'
-                       ],
+                       '$visibility'     => $visibility,
+                       '$acl_contacts'   => $acl_contacts,
+                       '$acl_groups'     => $acl_groups,
+                       '$acl_list'       => $acl_list,
+                       '$contact_allow'  => implode(',', $default_permissions['allow_cid']),
+                       '$group_allow'    => implode(',', $default_permissions['allow_gid']),
+                       '$contact_deny'   => implode(',', $default_permissions['deny_cid']),
+                       '$group_deny'     => implode(',', $default_permissions['deny_gid']),
+                       '$for_federation' => $for_federation,
+                       '$jotnets_fields' => $jotnets_fields,
+                       '$user_hidewall'  => $default_permissions['hidewall'],
                ]);
 
                return $o;
index a50f23c256963e160fb4d501b778cad28fee3e47..08bac2c1d06bba6add18bc35c669e7b58a6dd4e1 100644 (file)
@@ -44,7 +44,7 @@ class Bookmarklet extends BaseModule
                                'nickname'         => $app->user['nickname'],
                                'lockstate'        => ((is_array($app->user) && ((strlen($app->user['allow_cid'])) || (strlen($app->user['allow_gid'])) || (strlen($app->user['deny_cid'])) || (strlen($app->user['deny_gid'])))) ? 'lock' : 'unlock'),
                                'default_perms'    => ACL::getDefaultUserPermissions($app->user),
-                               'acl'              => ACL::getFullSelectorHTML($app->user, true),
+                               'acl'              => ACL::getFullSelectorHTML($app->page, $app->user, true),
                                'bang'             => '',
                                'visitor'          => 'block',
                                'profile_uid'      => local_user(),
index ded5ffbe2343ec2e15161e1425b9263aca27241a..5ef06b72a183b22488820bf6dc8c74b3823ff983 100644 (file)
@@ -926,7 +926,7 @@ class Contact extends BaseModule
                                        'default_location' => $a->user['default-location'],
                                        'nickname' => $a->user['nickname'],
                                        'lockstate' => (is_array($a->user) && (strlen($a->user['allow_cid']) || strlen($a->user['allow_gid']) || strlen($a->user['deny_cid']) || strlen($a->user['deny_gid'])) ? 'lock' : 'unlock'),
-                                       'acl' => ACL::getFullSelectorHTML($a->user, true),
+                                       'acl' => ACL::getFullSelectorHTML($a->page, $a->user, true),
                                        'bang' => '',
                                        'visitor' => 'block',
                                        'profile_uid' => local_user(),
index aab5918567fbab92582531c7967e1ced93bc32d1..db1a6f86b3db44e4674aeb8394772f12d6373df0 100644 (file)
@@ -208,7 +208,7 @@ class Profile extends BaseModule
                                                || strlen($a->user['deny_cid'])
                                                || strlen($a->user['deny_gid'])
                                        ) ? 'lock' : 'unlock',
-                                       'acl' => $is_owner ? ACL::getFullSelectorHTML($a->user, true) : '',
+                                       'acl' => $is_owner ? ACL::getFullSelectorHTML($a->page, $a->user, true) : '',
                                        'bang' => '',
                                        'visitor' => $is_owner || $commvisitor ? 'block' : 'none',
                                        'profile_uid' => $a->profile['profile_uid'],
diff --git a/view/js/friendica-tagsinput/LICENSE b/view/js/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/js/friendica-tagsinput/friendica-tagsinput-typeahead.css b/view/js/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/js/friendica-tagsinput/friendica-tagsinput.css b/view/js/friendica-tagsinput/friendica-tagsinput.css
new file mode 100644 (file)
index 0000000..6ad1d00
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * friendica-tagsinput v0.8.0
+ *
+ * Non-Bootstrap edition
+ */
+
+.label {
+       display: inline;
+       padding: .2em .6em .3em;
+       font-size: 75%;
+       font-weight: 700;
+       line-height: 1;
+       color: #fff;
+       text-align: center;
+       white-space: nowrap;
+       vertical-align: baseline;
+       border-radius: .25em;
+}
+
+.label-default {
+       background-color: #777777;
+}
+.label-default[href]:hover,
+.label-default[href]:focus {
+       background-color: #5e5e5e;
+}
+.label-primary {
+       background-color: #337ab7;
+}
+.label-primary[href]:hover,
+.label-primary[href]:focus {
+       background-color: #286090;
+}
+.label-success {
+       background-color: #5cb85c;
+}
+.label-success[href]:hover,
+.label-success[href]:focus {
+       background-color: #449d44;
+}
+.label-info {
+       background-color: #5bc0de;
+}
+.label-info[href]:hover,
+.label-info[href]:focus {
+       background-color: #31b0d5;
+}
+.label-warning {
+       background-color: #f0ad4e;
+}
+.label-warning[href]:hover,
+.label-warning[href]:focus {
+       background-color: #ec971f;
+}
+.label-danger {
+       background-color: #d9534f;
+}
+.label-danger[href]:hover,
+.label-danger[href]:focus {
+       background-color: #c9302c;
+}
+
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+       background-color: #eeeeee;
+       opacity: 1;
+}
+.form-control[disabled],
+fieldset[disabled] .form-control {
+       cursor: not-allowed;
+}
+
+
+
+
+.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/js/friendica-tagsinput/friendica-tagsinput.js b/view/js/friendica-tagsinput/friendica-tagsinput.js
new file mode 100644 (file)
index 0000000..af722d7
--- /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 (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached) {
+          // Only attempt to add a tag if there is data in the field
+          if (self.options.freeInput && 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 58a0f483b8eb8a3bc2c0a32021ac566d686ace71..21b28d5d7aeb67d5bd96b2b9b214bc216cf86015 100644 (file)
-
 <div id="acl-wrapper">
-       <input id="acl-search" autocomplete="off">
-       <a id="acl-showall">{{$showall}}</a>
-       <div id="acl-list">
-               <div id="acl-list-content">
+       <div class="panel-group" id="visibility-accordion" role="tablist" aria-multiselectable="true">
+               <div class="panel panel-success">
+                       <div class="panel-heading{{if $visibility != 'public'}} collapsed{{/if}}" id="visibility-public-heading" aria-expanded="{{if $visibility == 'public'}}true{{else}}false{{/if}}">
+                               <label>
+                                       <input type="radio" name="visibility" id="visibility-public" value="public" tabindex="14" {{if $visibility == 'public'}}checked{{/if}}>
+                                       <i class="fa fa-globe"></i> {{$public_title}}
+                               </label>
+                       </div>
+                       <fieldset id="visibility-public-panel" class="panel-collapse collapse{{if $visibility == 'public'}} in{{/if}}" role="tabpanel" aria-labelledby="visibility-public-heading" {{if $visibility != 'public'}}disabled{{/if}}>
+                               <div class="panel-body">
+                                       <p>{{$public_desc}}</p>
+                       {{if $for_federation}}
+                               {{if $user_hidewall}}
+                                       <h4>{{$jotnets_summary}}</h4>
+                               {{$jotnets_disabled_label}}
+                               {{elseif $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}}
+                       {{/if}}
+                               </div>
+                       </fieldset>
+               </div>
+               <div class="panel panel-info">
+                       <div class="panel-heading{{if $visibility != 'custom'}} collapsed{{/if}}" id="visibility-custom-heading" aria-expanded="{{if $visibility == 'custom'}}true{{else}}false{{/if}}">
+                               <label>
+                                       <input type="radio" name="visibility" id="visibility-custom" value="custom" tabindex="15" {{if $visibility == 'custom'}}checked{{/if}}>
+                                       <i class="fa fa-lock"></i> {{$custom_title}}
+                               </label>
+                       </div>
+                       <fieldset id="visibility-custom-panel" class="panel-collapse collapse{{if $visibility == 'custom'}} in{{/if}}" role="tabpanel" aria-labelledby="visibility-custom-heading" {{if $visibility != 'custom'}}disabled{{/if}}>
+                               <input type="hidden" name="group_allow" value="{{$group_allow}}"/>
+                               <input type="hidden" name="contact_allow" value="{{$contact_allow}}"/>
+                               <input type="hidden" name="group_deny" value="{{$group_deny}}"/>
+                               <input type="hidden" name="contact_deny" value="{{$contact_deny}}"/>
+                               <div class="panel-body">
+                                       <p>{{$custom_desc}}</p>
+
+                                       <div class="form-group">
+                                               <label for="acl_allow">{{$allow_label}}</label>
+                                               <input type="text" class="form-control input-lg" id="acl_allow">
+                                       </div>
+
+                                       <div class="form-group">
+                                               <label for="acl_deny">{{$deny_label}}</label>
+                                               <input type="text" class="form-control input-lg" id="acl_deny">
+                                       </div>
+                               </div>
+                       </fieldset>
                </div>
        </div>
-       <span id="acl-fields"></span>
-</div>
 
-<div class="acl-list-item" rel="acl-template" style="display:none">
-       <img data-src="{0}"><p>{1}</p>
-       <a class='acl-button-show'>{{$show}}</a>
-       <a class='acl-button-hide'>{{$hide}}</a>
-</div>
 
-{{if $networks}}
-<hr style="clear:both"/>
-<div id="profile-jot-email-label">{{$emailcc}}</div><input type="text" name="emailcc" id="profile-jot-email" title="{{$emtitle}}" />
-<div id="profile-jot-email-end"></div>
-
-       {{if $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}}
+{{if $for_federation}}
+       <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>
+<script type="text/javascript">
+       $(function() {
+               let $acl_allow_input = $('#acl_allow');
+               let $contact_allow_input = $('[name=contact_allow]');
+               let $group_allow_input = $('[name=group_allow]');
+               let $acl_deny_input = $('#acl_deny');
+               let $contact_deny_input = $('[name=contact_deny]');
+               let $group_deny_input = $('[name=group_deny]');
+               let $visibility_public_panel = $('#visibility-public-panel');
+               let $visibility_custom_panel = $('#visibility-custom-panel');
+               let $visibility_public_radio = $('#visibility-public');
+               let $visibility_custom_radio = $('#visibility-custom');
+
+               // Frio specific
+               if ($.fn.collapse) {
+                       $visibility_public_panel.collapse({parent: '#visibility-accordion', toggle: false});
+                       $visibility_custom_panel.collapse({parent: '#visibility-accordion', toggle: false});
+               }
+
+               $visibility_public_radio.on('change', function (e) {
+                       if ($.fn.collapse) {
+                               $visibility_public_panel.collapse('show');
+                       }
+
+                       $visibility_public_panel.prop('disabled', false);
+                       $visibility_custom_panel.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_radio.on('change', function(e) {
+                       if ($.fn.collapse) {
+                               $visibility_custom_panel.collapse('show');
+                       }
+
+                       $visibility_public_panel.prop('disabled', true);
+                       $visibility_custom_panel.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');
+               });
+
+               // 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_list|@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],
+                       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);
 
-<script>
-$(document).ready(function() {
-       if(typeof acl=="undefined"){
-               acl = new ACL(
-                       baseurl + '/search/acl',
-                       [ {{$allowcid nofilter}},{{$allowgid nofilter}},{{$denycid nofilter}},{{$denygid nofilter}} ],
-                       {{$features.aclautomention}},
-                       {{if $APP->is_mobile}}true{{else}}false{{/if}}
-               );
-       }
-});
+                       // 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);
+                               }
+                       });
+               });
+       });
 </script>
diff --git a/view/theme/frio/frameworks/friendica-tagsinput/LICENSE b/view/theme/frio/frameworks/friendica-tagsinput/LICENSE
deleted file mode 100644 (file)
index 58bc985..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-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
deleted file mode 100644 (file)
index 8cec654..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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
deleted file mode 100644 (file)
index f2e1197..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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
deleted file mode 100644 (file)
index af722d7..0000000
+++ /dev/null
@@ -1,695 +0,0 @@
-/*
- * 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 (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached) {
-          // Only attempt to add a tag if there is data in the field
-          if (self.options.freeInput && 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 597563fe1f81b1a828ddfebdc9d45556d4d3dd01..a068b4fe0e53385f1898873e1d0618bb11f0d63c 100644 (file)
@@ -49,24 +49,6 @@ $(document).ready(function() {
                $("#event-preview").empty();
                e.preventDefault();
        });
-
-       // Construct a new ACL. We need this everytime the 'event-edit-form' is loaded
-       // without page reloading (e.g. closing an old modal and open a new modal).
-       // Otherwise we wouldn't get the ACL data.
-       /// @todo: Try to implement some kind of ACL reloading in acl.js.
-
-       var eventPerms = document.getElementById('event-edit-form');
-
-       acl = new ACL(
-               baseurl + '/search/acl',
-               [
-                       JSON.parse(eventPerms.dataset.allow_cid),
-                       JSON.parse(eventPerms.dataset.allow_gid),
-                       JSON.parse(eventPerms.dataset.deny_cid),
-                       JSON.parse(eventPerms.dataset.deny_gid)
-               ]
-       );
-       acl.get(0, 100);
 });
 
 // Load the html of the actual event and incect the output to the
index f33988e13911120049d7064e7ec0aa151fe7fca8..04184a651fe56d12875e64dcb1f36b2f615e18bc 100644 (file)
@@ -344,11 +344,11 @@ function toggleJotNav (elm) {
 
        // Minimize all tab content wrapper and activate only the selected
        // tab panel.
-       $('#jot-modal [role=tabpanel]').addClass("minimize").attr("aria-hidden" ,"true");
-       $('#jot-modal #' + tabpanel).removeClass("minimize").attr("aria-hidden" ,"false");
+       $('#profile-jot-form > [role=tabpanel]').addClass("minimize").attr("aria-hidden" ,"true");
+       $('#' + tabpanel).removeClass("minimize").attr("aria-hidden" ,"false");
 
        // Set the aria-selected states
-       $("#jot-modal .nav-tabs .jot-nav-lnk").attr("aria-selected", "false");
+       $("#jot-modal .modal-header .nav-tabs .jot-nav-lnk").attr("aria-selected", "false");
        elm.setAttribute("aria-selected", "true");
 
        // For some some tab panels we need to execute other js functions.
diff --git a/view/theme/frio/templates/acl_selector.tpl b/view/theme/frio/templates/acl_selector.tpl
deleted file mode 100644 (file)
index e335a4f..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-
-<div id="acl-wrapper">
-       <div class="form-group form-group-search">
-               <button id="acl-showall" class="btn btn-block btn-default"><i class="fa fa-globe"></i> {{$showall}}</button>
-       </div>
-       <div class="form-group form-group-search">
-               <input type="text" id="acl-search" class="form-control form-search" autocomplete="off">
-       </div>
-       <div id="acl-list">
-               <div id="acl-list-content"></div>
-       </div>
-       <span id="acl-fields"></span>
-</div>
-
-<div class="acl-list-item" rel="acl-template" style="display:none">
-       <img data-src="{0}" alt="{1}"><p>{1}</p>
-       <button class='acl-button-hide btn btn-sm btn-default'>{{$hide}}</button>
-       <button class='acl-button-show btn btn-sm btn-default'>{{$show}}</button>
-</div>
-
-{{if $networks}}
-<hr style="clear:both"/>
-<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 $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}}
-{{/if}}
-
-<script type="text/javascript">
-$(document).ready(function() {
-       if(typeof acl=="undefined"){
-               acl = new ACL(
-                       baseurl + '/search/acl',
-                       [ {{$allowcid nofilter}},{{$allowgid nofilter}},{{$denycid nofilter}},{{$denygid nofilter}} ],
-                       {{$features.aclautomention}},
-                       {{if $APP->is_mobile}}true{{else}}false{{/if}}
-               );
-       }
-});
-</script>
index 661bdecb2664b92adeb121e7879f2dded971566a..c48d5c7fc8bba2d00a087e4bebd9a952bc25b9b6 100644 (file)
@@ -30,7 +30,7 @@
        </ul>
 
        <div id="event-edit-form-wrapper">
-       <form id="event-edit-form" action="{{$post}}" method="post" data-allow_cid="{{$allow_cid}}" data-allow_gid="{{$allow_gid}}" data-deny_cid="{{$deny_cid}}" data-deny_gid="{{$deny_gid}}">
+       <form id="event-edit-form" action="{{$post}}" method="post">
 
                <input type="hidden" name="event_id" value="{{$eid}}" />
                <input type="hidden" name="cid" value="{{$cid}}" />
index c690cdf18014b7a26589a05981413e25e4934dec..07c452c7e04e051f8f1c14ed2a3574aadf7172a4 100644 (file)
@@ -70,9 +70,6 @@
         <div style="display: none;">
             <div id="profile-jot-acl-wrapper" style="width:auto;height:auto;overflow:auto;">
                 {{$acl nofilter}}
-                <hr style="clear:both"/>
-                <div id="profile-jot-email-label">{{$emailcc}}</div><input type="text" name="emailcc" id="profile-jot-email" title="{{$emtitle}}" />
-                <div id="profile-jot-email-end"></div>
                 {{$jotnets nofilter}}
             </div>
         </div>