Merge branch 'fix-fatal' into item-view

This commit is contained in:
Michael 2020-05-06 05:14:18 +00:00
commit acb44f82c7
63 changed files with 5441 additions and 346 deletions

View file

@ -50,7 +50,6 @@
"bower-asset/base64": "^1.0",
"bower-asset/chart-js": "^2.8",
"bower-asset/dompurify": "^1.0",
"bower-asset/perfect-scrollbar": "^0.6",
"bower-asset/vue": "^2.6",
"npm-asset/es-jquery-sortable": "^0.9.13",
"npm-asset/jquery": "^2.0",

46
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e1a839b13f7ba5892c8730d0da3ddf1c",
"content-hash": "0d0fe0cafbe7bd050b41a5c9e96dba5f",
"packages": [
{
"name": "asika/simple-console",
@ -237,37 +237,6 @@
],
"time": "2019-02-28T15:21:34+00:00"
},
{
"name": "bower-asset/perfect-scrollbar",
"version": "0.6.16",
"source": {
"type": "git",
"url": "https://github.com/utatti/perfect-scrollbar-bower.git",
"reference": "3049129e5dbb403295ce8507a461cdd0f200938c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utatti/perfect-scrollbar-bower/zipball/3049129e5dbb403295ce8507a461cdd0f200938c",
"reference": "3049129e5dbb403295ce8507a461cdd0f200938c",
"shasum": ""
},
"type": "bower-asset-library",
"extra": {
"bower-asset-main": [
"css/perfect-scrollbar.css",
"js/perfect-scrollbar.js"
],
"bower-asset-ignore": [
"**/.*",
"bower_components"
]
},
"license": [
"MIT"
],
"description": "Minimalistic but perfect custom scrollbar plugin",
"time": "2017-01-10T01:04:09+00:00"
},
{
"name": "bower-asset/vue",
"version": "v2.6.10",
@ -485,7 +454,6 @@
"jsonld.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
@ -503,11 +471,11 @@
"description": "A JSON-LD Processor and API implementation in PHP.",
"homepage": "https://git.friendi.ca/friendica/php-json-ld",
"keywords": [
"JSON",
"JSON-LD",
"Linked Data",
"RDF",
"Semantic Web",
"json",
"jsonld"
],
"time": "2018-10-08T20:41:00+00:00"
@ -3925,7 +3893,7 @@
}
],
"description": "Provides the functionality to compare PHP values for equality",
"homepage": "https://github.com/sebastianbergmann/comparator",
"homepage": "http://www.github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
@ -4027,7 +3995,7 @@
}
],
"description": "Provides functionality to handle HHVM/PHP environments",
"homepage": "https://github.com/sebastianbergmann/environment",
"homepage": "http://www.github.com/sebastianbergmann/environment",
"keywords": [
"Xdebug",
"environment",
@ -4095,7 +4063,7 @@
}
],
"description": "Provides the functionality to export PHP variables for visualization",
"homepage": "https://github.com/sebastianbergmann/exporter",
"homepage": "http://www.github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
@ -4147,7 +4115,7 @@
}
],
"description": "Snapshotting of global state",
"homepage": "https://github.com/sebastianbergmann/global-state",
"homepage": "http://www.github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
@ -4249,7 +4217,7 @@
}
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "https://github.com/sebastianbergmann/recursion-context",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"time": "2016-11-19T07:33:16+00:00"
},
{

View file

@ -1,6 +1,6 @@
-- ------------------------------------------
-- Friendica 2020.06-dev (Red Hot Poker)
-- DB_UPDATE_VERSION 1341
-- DB_UPDATE_VERSION 1346
-- ------------------------------------------
@ -666,7 +666,8 @@ CREATE TABLE IF NOT EXISTS `item` (
INDEX `uid_eventid` (`uid`,`event-id`),
INDEX `icid` (`icid`),
INDEX `iaid` (`iaid`),
INDEX `psid_wall` (`psid`,`wall`)
INDEX `psid_wall` (`psid`,`wall`),
INDEX `uri-id` (`uri-id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Structure for all posts';
--
@ -714,24 +715,6 @@ CREATE TABLE IF NOT EXISTS `item-content` (
INDEX `uri-id` (`uri-id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Content for all posts';
--
-- TABLE item-delivery-data
--
CREATE TABLE IF NOT EXISTS `item-delivery-data` (
`iid` int unsigned NOT NULL COMMENT 'Item id',
`postopts` text COMMENT 'External post connectors add their network name to this comma-separated string to identify that they should be delivered to these networks during delivery',
`inform` mediumtext COMMENT 'Additional receivers of the linked item',
`queue_count` mediumint NOT NULL DEFAULT 0 COMMENT 'Initial number of delivery recipients, used as item.delivery_queue_count',
`queue_done` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries, used as item.delivery_queue_done',
`queue_failed` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of unsuccessful deliveries, used as item.delivery_queue_failed',
`activitypub` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via ActivityPub',
`dfrn` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via DFRN',
`legacy_dfrn` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via legacy DFRN',
`diaspora` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via Diaspora',
`ostatus` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via OStatus',
PRIMARY KEY(`iid`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Delivery data for items';
--
-- TABLE item-uri
--
@ -832,6 +815,8 @@ CREATE TABLE IF NOT EXISTS `notify` (
`link` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`iid` int unsigned NOT NULL DEFAULT 0 COMMENT 'item.id',
`parent` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`uri-id` int unsigned COMMENT 'Item-uri id of the related post',
`parent-uri-id` int unsigned COMMENT 'Item-uri id of the parent of the related post',
`seen` boolean NOT NULL DEFAULT '0' COMMENT '',
`verb` varchar(100) NOT NULL DEFAULT '' COMMENT '',
`otype` varchar(10) NOT NULL DEFAULT '' COMMENT '',
@ -850,6 +835,7 @@ CREATE TABLE IF NOT EXISTS `notify-threads` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`notify-id` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`master-parent-item` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`master-parent-uri-id` int unsigned COMMENT 'Item-uri id of the parent of the related post',
`parent-item` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`receiver-uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
PRIMARY KEY(`id`)
@ -1187,6 +1173,36 @@ CREATE TABLE IF NOT EXISTS `tag` (
INDEX `url` (`url`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='tags and mentions';
--
-- TABLE post-category
--
CREATE TABLE IF NOT EXISTS `post-category` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
`type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`tid` int unsigned NOT NULL DEFAULT 0 COMMENT '',
PRIMARY KEY(`uri-id`,`uid`,`type`,`tid`),
INDEX `uri-id` (`tid`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='post relation to categories';
--
-- TABLE post-delivery-data
--
CREATE TABLE IF NOT EXISTS `post-delivery-data` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`postopts` text COMMENT 'External post connectors add their network name to this comma-separated string to identify that they should be delivered to these networks during delivery',
`inform` mediumtext COMMENT 'Additional receivers of the linked item',
`queue_count` mediumint NOT NULL DEFAULT 0 COMMENT 'Initial number of delivery recipients, used as item.delivery_queue_count',
`queue_done` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries, used as item.delivery_queue_done',
`queue_failed` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of unsuccessful deliveries, used as item.delivery_queue_failed',
`activitypub` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via ActivityPub',
`dfrn` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via DFRN',
`legacy_dfrn` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via legacy DFRN',
`diaspora` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via Diaspora',
`ostatus` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via OStatus',
PRIMARY KEY(`uri-id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Delivery data for items';
--
-- TABLE post-tag
--
@ -1386,6 +1402,23 @@ CREATE TABLE IF NOT EXISTS `storage` (
PRIMARY KEY(`id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Data stored by Database storage backend';
--
-- VIEW category-view
--
DROP VIEW IF EXISTS `category-view`;
CREATE VIEW `category-view` AS SELECT
`post-category`.`uri-id` AS `uri-id`,
`post-category`.`uid` AS `uid`,
`item-uri`.`uri` AS `uri`,
`item-uri`.`guid` AS `guid`,
`post-category`.`type` AS `type`,
`post-category`.`tid` AS `tid`,
`tag`.`name` AS `name`,
`tag`.`url` AS `url`
FROM `post-category`
INNER JOIN `item-uri` ON `item-uri`.id = `post-category`.`uri-id`
LEFT JOIN `tag` ON `post-category`.`tid` = `tag`.`id`;
--
-- VIEW tag-view
--
@ -1539,22 +1572,6 @@ CREATE VIEW `owner-view` AS SELECT
INNER JOIN `contact` ON `contact`.`uid` = `user`.`uid` AND `contact`.`self`
INNER JOIN `profile` ON `profile`.`uid` = `user`.`uid`;
--
-- VIEW participation-view
--
DROP VIEW IF EXISTS `participation-view`;
CREATE VIEW `participation-view` AS SELECT
`participation`.`iid` AS `iid`,
`contact`.`id` AS `id`,
`contact`.`url` AS `url`,
`contact`.`name` AS `name`,
`contact`.`protocol` AS `protocol`,
CASE `contact`.`batch` WHEN '' THEN `fcontact`.`batch` ELSE `contact`.`batch` END AS `batch`,
CASE `fcontact`.`network` WHEN '' THEN `contact`.`network` ELSE `fcontact`.`network` END AS `network`
FROM `participation`
INNER JOIN `contact` ON `contact`.`id` = `participation`.`cid` AND NOT `contact`.`archive`
INNER JOIN `fcontact` ON `fcontact`.`id` = `participation`.`fid`;
--
-- VIEW pending-view
--
@ -1577,6 +1594,27 @@ CREATE VIEW `pending-view` AS SELECT
INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid`
INNER JOIN `user` ON `register`.`uid` = `user`.`uid`;
--
-- VIEW tag-search-view
--
DROP VIEW IF EXISTS `tag-search-view`;
CREATE VIEW `tag-search-view` AS SELECT
`post-tag`.`uri-id` AS `uri-id`,
`item`.`id` AS `iid`,
`item`.`uri` AS `uri`,
`item`.`guid` AS `guid`,
`item`.`uid` AS `uid`,
`item`.`private` AS `private`,
`item`.`wall` AS `wall`,
`item`.`origin` AS `origin`,
`item`.`gravity` AS `gravity`,
`item`.`received` AS `received`,
`tag`.`name` AS `name`
FROM `post-tag`
INNER JOIN `tag` ON `tag`.`id` = `post-tag`.`tid`
INNER JOIN `item` ON `item`.`uri-id` = `post-tag`.`uri-id`
WHERE `post-tag`.`type` = 1;
--
-- VIEW workerqueue-view
--

View file

@ -107,12 +107,24 @@ function notification($params)
$item_id = 0;
}
if (isset($params['item']['uri-id'])) {
$uri_id = $params['item']['uri-id'];
} else {
$uri_id = 0;
}
if (isset($params['parent'])) {
$parent_id = $params['parent'];
} else {
$parent_id = 0;
}
if (isset($params['item']['parent-uri-id'])) {
$parent_uri_id = $params['item']['parent-uri-id'];
} else {
$parent_uri_id = 0;
}
$epreamble = '';
$preamble = '';
$subject = '';
@ -458,8 +470,10 @@ function notification($params)
'photo' => $params['source_photo'] ?? '',
'link' => $itemlink ?? '',
'uid' => $params['uid'] ?? 0,
'iid' => $item_id ?? 0,
'parent' => $parent_id ?? 0,
'iid' => $item_id,
'uri-id' => $uri_id,
'parent' => $parent_id,
'parent-uri-id' => $parent_uri_id,
'type' => $params['type'] ?? '',
'verb' => $params['verb'] ?? '',
'otype' => $params['otype'] ?? '',
@ -487,6 +501,7 @@ function notification($params)
Logger::log("notify_id:" . intval($notify_id) . ", parent: " . intval($params['parent']) . "uid: " . intval($params['uid']), Logger::DEBUG);
$fields = ['notify-id' => $notify_id, 'master-parent-item' => $params['parent'],
'master-parent-uri-id' => $parent_uri_id,
'receiver-uid' => $params['uid'], 'parent-item' => 0];
DBA::insert('notify-threads', $fields);
@ -574,7 +589,7 @@ function check_user_notification($itemid) {
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
function check_item_notification($itemid, $uid, $notification_type) {
$fields = ['id', 'mention', 'parent', 'title', 'body',
$fields = ['id', 'uri-id', 'mention', 'parent', 'parent-uri-id', 'title', 'body',
'author-link', 'author-name', 'author-avatar', 'author-id',
'guid', 'parent-uri', 'uri', 'contact-id', 'network'];
$condition = ['id' => $itemid, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'deleted' => false];

View file

@ -425,13 +425,11 @@ function photos_post(App $a)
$item = Item::selectFirst(['tag', 'inform', 'uri-id'], ['id' => $item_id, 'uid' => $page_owner_uid]);
if (DBA::isResult($item)) {
$old_tag = $item['tag'];
$old_inform = $item['inform'];
}
}
if (strlen($rawtags)) {
$str_tags = '';
$inform = '';
// if the new tag doesn't have a namespace specifier (@foo or #foo) give it a hashtag
@ -513,15 +511,10 @@ function photos_post(App $a)
if (!empty($contact)) {
$taginfo[] = [$newname, $profile, $notify, $contact, '@[url=' . str_replace(',', '%2c', $profile) . ']' . $newname . '[/url]'];
} else {
$taginfo[] = [$newname, $profile, $notify, null, $str_tags .= '@[url=' . $profile . ']' . $newname . '[/url]'];
}
if (strlen($str_tags)) {
$str_tags .= ',';
$taginfo[] = [$newname, $profile, $notify, null, '@[url=' . $profile . ']' . $newname . '[/url]'];
}
$profile = str_replace(',', '%2c', $profile);
$str_tags .= '@[url=' . $profile . ']' . $newname . '[/url]';
if (!empty($item['uri-id'])) {
Tag::store($item['uri-id'], Tag::MENTION, $newname, $profile);
@ -529,7 +522,6 @@ function photos_post(App $a)
}
} elseif (strpos($tag, '#') === 0) {
$tagname = substr($tag, 1);
$str_tags .= '#[url=' . DI::baseUrl() . "/search?tag=" . $tagname . ']' . $tagname . '[/url],';
if (!empty($item['uri-id'])) {
Tag::store($item['uri-id'], Tag::HASHTAG, $tagname);
}
@ -537,19 +529,13 @@ function photos_post(App $a)
}
}
$newtag = $old_tag ?? '';
if (strlen($newtag) && strlen($str_tags)) {
$newtag .= ',';
}
$newtag .= $str_tags;
$newinform = $old_inform ?? '';
if (strlen($newinform) && strlen($inform)) {
$newinform .= ',';
}
$newinform .= $inform;
$fields = ['tag' => $newtag, 'inform' => $newinform, 'edited' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()];
$fields = ['inform' => $newinform, 'edited' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()];
$condition = ['id' => $item_id];
Item::update($fields, $condition);
@ -1322,8 +1308,9 @@ function photos_content(App $a)
$tags = null;
if (!empty($link_item['id']) && !empty($link_item['tag'])) {
$arr = explode(',', $link_item['tag']);
if (!empty($link_item['id'])) {
$tag_text = Tag::getCSVByURIId($link_item['uri-id']);
$arr = explode(',', $tag_text);
// parse tags and add links
$tag_arr = [];
foreach ($arr as $tag) {

View file

@ -97,15 +97,16 @@ function tagrm_content(App $a)
// NOTREACHED
}
$item = Item::selectFirst(['tag'], ['id' => $item_id, 'uid' => local_user()]);
$item = Item::selectFirst(['uri-id'], ['id' => $item_id, 'uid' => local_user()]);
if (!DBA::isResult($item)) {
DI::baseUrl()->redirect($_SESSION['photo_return']);
}
$arr = explode(',', $item['tag']);
$tag_text = Tag::getCSVByURIId($item['uri-id']);
$arr = explode(',', $tag_text);
if (empty($item['tag'])) {
if (empty($arr)) {
DI::baseUrl()->redirect($_SESSION['photo_return']);
}

View file

@ -122,9 +122,6 @@ class Markdown
// protect the recycle symbol from turning into a tag, but without unescaping angles and naked ampersands
$s = str_replace('♲', html_entity_decode('♲', ENT_QUOTES, 'UTF-8'), $s);
// Convert everything that looks like a link to a link
$s = preg_replace('/([^\]=]|^)(https?\:\/\/)([a-zA-Z0-9:\/\-?&;.=_~#%$!+,@]+(?<!,))/ism', '$1[url=$2$3]$2$3[/url]', $s);
//$s = preg_replace("/([^\]\=]|^)(https?\:\/\/)(vimeo|youtu|www\.youtube|soundcloud)([a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1[url=$2$3$4]$2$3$4[/url]',$s);
$s = BBCode::pregReplaceInTag('/\[url\=?(.*?)\]https?:\/\/www.youtube.com\/watch\?v\=(.*?)\[\/url\]/ism', '[youtube]$2[/youtube]', 'url', $s);
$s = BBCode::pregReplaceInTag('/\[url\=https?:\/\/www.youtube.com\/watch\?v\=(.*?)\].*?\[\/url\]/ism' , '[youtube]$1[/youtube]', 'url', $s);

View file

@ -741,6 +741,17 @@ class DBA
return DI::dba()->processlist();
}
/**
* Fetch a database variable
*
* @param string $name
* @return string content
*/
public static function getVariable(string $name)
{
return DI::dba()->getVariable($name);
}
/**
* Checks if $array is a filled array with at least one entry.
*

View file

@ -1645,6 +1645,18 @@ class Database
return (["list" => $statelist, "amount" => $processes]);
}
/**
* Fetch a database variable
*
* @param string $name
* @return string content
*/
public function getVariable(string $name)
{
$result = $this->fetchFirst("SHOW GLOBAL VARIABLES WHERE `Variable_name` = ?", $name);
return $result['Value'] ?? null;
}
/**
* Checks if $array is a filled array with at least one entry.
*

View file

@ -74,6 +74,9 @@ class PostUpdate
if (!self::update1342()) {
return false;
}
if (!self::update1345()) {
return false;
}
if (!self::update1346()) {
return false;
}
@ -564,7 +567,6 @@ class PostUpdate
Logger::info('Start', ['item' => $id]);
$start_id = $id;
$rows = 0;
$items = DBA::p("SELECT `uri-id`,`body` FROM `item-content` WHERE
@ -618,7 +620,6 @@ class PostUpdate
Logger::info('Start', ['item' => $id]);
$start_id = $id;
$rows = 0;
$terms = DBA::p("SELECT `term`.`tid`, `item`.`uri-id`, `term`.`type`, `term`.`term`, `term`.`url`, `item-content`.`body`
@ -673,6 +674,59 @@ class PostUpdate
return false;
}
/**
* Fill the "post-delivery-data" table with data from the "item-delivery-data" table
*
* @return bool "true" when the job is done
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function update1345()
{
// Was the script completed?
if (DI::config()->get('system', 'post_update_version') >= 1345) {
return true;
}
$id = DI::config()->get('system', 'post_update_version_1345_id', 0);
Logger::info('Start', ['item' => $id]);
$rows = 0;
$deliveries = DBA::p("SELECT `uri-id`, `iid`, `item-delivery-data`.`postopts`, `item-delivery-data`.`inform`,
`queue_count`, `queue_done`, `activitypub`, `dfrn`, `diaspora`, `ostatus`, `legacy_dfrn`, `queue_failed`
FROM `item-delivery-data`
INNER JOIN `item` ON `item`.`id` = `item-delivery-data`.`iid`
WHERE `iid` >= ? ORDER BY `iid` LIMIT 10000", $id);
if (DBA::errorNo() != 0) {
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
return false;
}
while ($delivery = DBA::fetch($deliveries)) {
$id = $delivery['iid'];
unset($delivery['iid']);
DBA::insert('post-delivery-data', $delivery, true);
++$rows;
}
DBA::close($deliveries);
DI::config()->set('system', 'post_update_version_1345_id', $id);
Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
// When there are less than 100 items processed this means that we reached the end
// The other entries will then be processed with the regular functionality
if ($rows < 100) {
DI::config()->set('system', 'post_update_version', 1345);
Logger::info('Done');
return true;
}
return false;
}
/**
* Fill the "tag" table with tags and mentions from the "term" table
*

View file

@ -847,7 +847,7 @@ class Contact
$item['body'] = '';
$item['title'] = '';
$item['guid'] = '';
$item['tag'] = '';
$item['uri-id'] = 0;
$item['attach'] = '';
$slap = OStatus::salmon($item, $user);
@ -2457,7 +2457,7 @@ class Contact
$item['body'] = '';
$item['title'] = '';
$item['guid'] = '';
$item['tag'] = '';
$item['uri-id'] = 0;
$item['attach'] = '';
$slap = OStatus::salmon($item, $owner);

View file

@ -284,7 +284,7 @@ class Item
// Fetch data from the item-content table whenever there is content there
if (self::isLegacyMode()) {
$legacy_fields = array_merge(ItemDeliveryData::LEGACY_FIELD_LIST, self::MIXED_CONTENT_FIELDLIST);
$legacy_fields = array_merge(Post\DeliveryData::LEGACY_FIELD_LIST, self::MIXED_CONTENT_FIELDLIST);
foreach ($legacy_fields as $field) {
if (empty($row[$field]) && !empty($row['internal-item-' . $field])) {
$row[$field] = $row['internal-item-' . $field];
@ -685,7 +685,7 @@ class Item
$fields['item-content'] = array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST);
$fields['item-delivery-data'] = array_merge(ItemDeliveryData::LEGACY_FIELD_LIST, ItemDeliveryData::FIELD_LIST);
$fields['post-delivery-data'] = array_merge(Post\DeliveryData::LEGACY_FIELD_LIST, Post\DeliveryData::FIELD_LIST);
$fields['permissionset'] = ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid'];
@ -807,8 +807,8 @@ class Item
$joins .= " LEFT JOIN `item-content` ON `item-content`.`uri-id` = `item`.`uri-id`";
}
if (strpos($sql_commands, "`item-delivery-data`.") !== false) {
$joins .= " LEFT JOIN `item-delivery-data` ON `item-delivery-data`.`iid` = `item`.`id`";
if (strpos($sql_commands, "`post-delivery-data`.") !== false) {
$joins .= " LEFT JOIN `post-delivery-data` ON `post-delivery-data`.`uri-id` = `item`.`uri-id` AND `item`.`origin`";
}
if (strpos($sql_commands, "`permissionset`.") !== false) {
@ -848,7 +848,7 @@ class Item
$selected[] = 'internal-user-ignored';
}
$legacy_fields = array_merge(ItemDeliveryData::LEGACY_FIELD_LIST, self::MIXED_CONTENT_FIELDLIST);
$legacy_fields = array_merge(Post\DeliveryData::LEGACY_FIELD_LIST, self::MIXED_CONTENT_FIELDLIST);
$selection = [];
foreach ($fields as $table => $table_fields) {
@ -934,7 +934,7 @@ class Item
}
}
$delivery_data = ItemDeliveryData::extractFields($fields);
$delivery_data = Post\DeliveryData::extractFields($fields);
$clear_fields = ['bookmark', 'type', 'author-name', 'author-avatar', 'author-link', 'owner-name', 'owner-avatar', 'owner-link', 'postopts', 'inform'];
foreach ($clear_fields as $field) {
@ -1022,7 +1022,7 @@ class Item
}
}
ItemDeliveryData::update($item['id'], $delivery_data);
Post\DeliveryData::update($item['uri-id'], $delivery_data);
self::updateThread($item['id']);
@ -1182,7 +1182,7 @@ class Item
self::markForDeletion(['uri' => $item['uri'], 'uid' => 0, 'deleted' => false], $priority);
}
ItemDeliveryData::delete($item['id']);
Post\DeliveryData::delete($item['uri-id']);
// We don't delete the item-activity here, since we need some of the data for ActivityPub
@ -1838,7 +1838,7 @@ class Item
self::insertContent($item);
}
$delivery_data = ItemDeliveryData::extractFields($item);
$delivery_data = Post\DeliveryData::extractFields($item);
unset($item['postopts']);
unset($item['inform']);
@ -1942,7 +1942,7 @@ class Item
}
if (!empty($item['origin']) || !empty($item['wall']) || !empty($delivery_data['postopts']) || !empty($delivery_data['inform'])) {
ItemDeliveryData::insert($current_post, $delivery_data);
Post\DeliveryData::insert($item['uri-id'], $delivery_data);
}
DBA::commit();

View file

@ -19,12 +19,12 @@
*
*/
namespace Friendica\Model;
namespace Friendica\Model\Post;
use Friendica\Database\DBA;
use \BadMethodCallException;
class ItemDeliveryData
class DeliveryData
{
const LEGACY_FIELD_LIST = [
// Legacy fields moved from item table
@ -55,7 +55,7 @@ class ItemDeliveryData
public static function extractFields(array &$fields)
{
$delivery_data = [];
foreach (array_merge(ItemDeliveryData::FIELD_LIST, ItemDeliveryData::LEGACY_FIELD_LIST) as $key => $field) {
foreach (array_merge(self::FIELD_LIST, self::LEGACY_FIELD_LIST) as $key => $field) {
if (is_int($key) && isset($fields[$field])) {
// Legacy field moved from item table
$delivery_data[$field] = $fields[$field];
@ -71,16 +71,16 @@ class ItemDeliveryData
}
/**
* Increments the queue_done for the given item ID.
* Increments the queue_done for the given URI ID.
*
* Avoids racing condition between multiple delivery threads.
*
* @param integer $item_id
* @param integer $uri_id
* @param integer $protocol
* @return bool
* @throws \Exception
*/
public static function incrementQueueDone($item_id, $protocol = 0)
public static function incrementQueueDone(int $uri_id, int $protocol = 0)
{
$sql = '';
@ -102,69 +102,69 @@ class ItemDeliveryData
break;
}
return DBA::e('UPDATE `item-delivery-data` SET `queue_done` = `queue_done` + 1' . $sql . ' WHERE `iid` = ?', $item_id);
return DBA::e('UPDATE `post-delivery-data` SET `queue_done` = `queue_done` + 1' . $sql . ' WHERE `uri-id` = ?', $uri_id);
}
/**
* Increments the queue_failed for the given item ID.
* Increments the queue_failed for the given URI ID.
*
* Avoids racing condition between multiple delivery threads.
*
* @param integer $item_id
* @param integer $uri_id
* @return bool
* @throws \Exception
*/
public static function incrementQueueFailed($item_id)
public static function incrementQueueFailed(int $uri_id)
{
return DBA::e('UPDATE `item-delivery-data` SET `queue_failed` = `queue_failed` + 1 WHERE `iid` = ?', $item_id);
return DBA::e('UPDATE `post-delivery-data` SET `queue_failed` = `queue_failed` + 1 WHERE `uri-id` = ?', $uri_id);
}
/**
* Increments the queue_count for the given item ID.
* Increments the queue_count for the given URI ID.
*
* @param integer $item_id
* @param integer $uri_id
* @param integer $increment
* @return bool
* @throws \Exception
*/
public static function incrementQueueCount(int $item_id, int $increment = 1)
public static function incrementQueueCount(int $uri_id, int $increment = 1)
{
return DBA::e('UPDATE `item-delivery-data` SET `queue_count` = `queue_count` + ? WHERE `iid` = ?', $increment, $item_id);
return DBA::e('UPDATE `post-delivery-data` SET `queue_count` = `queue_count` + ? WHERE `uri-id` = ?', $increment, $uri_id);
}
/**
* Insert a new item delivery data entry
* Insert a new URI delivery data entry
*
* @param integer $item_id
* @param integer $uri_id
* @param array $fields
* @return bool
* @throws \Exception
*/
public static function insert($item_id, array $fields)
public static function insert(int $uri_id, array $fields)
{
if (empty($item_id)) {
throw new BadMethodCallException('Empty item_id');
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
$fields['iid'] = $item_id;
$fields['uri-id'] = $uri_id;
return DBA::insert('item-delivery-data', $fields);
return DBA::insert('post-delivery-data', $fields);
}
/**
* Update/Insert item delivery data
* Update/Insert URI delivery data
*
* If you want to update queue_done, please use incrementQueueDone instead.
*
* @param integer $item_id
* @param integer $uri_id
* @param array $fields
* @return bool
* @throws \Exception
*/
public static function update($item_id, array $fields)
public static function update(int $uri_id, array $fields)
{
if (empty($item_id)) {
throw new BadMethodCallException('Empty item_id');
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
if (empty($fields)) {
@ -172,22 +172,22 @@ class ItemDeliveryData
return true;
}
return DBA::update('item-delivery-data', $fields, ['iid' => $item_id], true);
return DBA::update('post-delivery-data', $fields, ['uri-id' => $uri_id], true);
}
/**
* Delete item delivery data
* Delete URI delivery data
*
* @param integer $item_id
* @param integer $uri_id
* @return bool
* @throws \Exception
*/
public static function delete($item_id)
public static function delete(int $uri_id)
{
if (empty($item_id)) {
throw new BadMethodCallException('Empty item_id');
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
return DBA::delete('item-delivery-data', ['iid' => $item_id]);
return DBA::delete('post-delivery-data', ['uri-id' => $uri_id]);
}
}

View file

@ -339,6 +339,23 @@ class Tag
return DBA::selectToArray('tag-view', ['type', 'name', 'url'], $condition);
}
/**
* Return a string with all tags and mentions
*
* @param integer $uri_id
* @return string tags and mentions
*/
public static function getCSVByURIId(int $uri_id)
{
$tag_list = [];
$tags = self::getByURIId($uri_id);
foreach ($tags as $tag) {
$tag_list[] = self::TAG_CHARACTER[$tag['type']] . '[url=' . $tag['url'] . ']' . $tag['name'] . '[/url]';
}
return implode(',', $tag_list);
}
/**
* Sorts an item's tags into mentions, hashtags and other tags. Generate personalized URLs by user and modify the
* provided item's body with them.

View file

@ -55,6 +55,16 @@ class Summary extends BaseAdmin
$warningtext[] = DI::l10n()->t('Your DB still runs with InnoDB tables in the Antelope file format. You should change the file format to Barracuda. Friendica is using features that are not provided by the Antelope format. See <a href="%s">here</a> for a guide that may be helpful converting the table engines. You may also use the command <tt>php bin/console.php dbstructure toinnodb</tt> of your Friendica installation for an automatic conversion.<br />', 'https://dev.mysql.com/doc/refman/5.7/en/innodb-file-format.html');
}
// Avoid the database error 1615 "Prepared statement needs to be re-prepared", see https://github.com/friendica/friendica/issues/8550
$table_definition_cache = DBA::getVariable('table_definition_cache');
$table_open_cache = DBA::getVariable('table_open_cache');
if (!empty($table_definition_cache) && !empty($table_open_cache)) {
$suggested_definition_cache = min(400 + round($table_open_cache / 2, 1), 2000);
if ($suggested_definition_cache > $table_definition_cache) {
$warningtext[] = DI::l10n()->t('Your table_definition_cache is too low (%d). This can lead to the database error "Prepared statement needs to be re-prepared". Please set it at least to %d (or -1 for autosizing). See <a href="%s">here</a> for more information.<br />', $table_definition_cache, $suggested_definition_cache, 'https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_table_definition_cache');
}
}
// Check if github.com/friendica/master/VERSION is higher then
// the local version of Friendica. Check is opt-in, source may be master or devel branch
if (DI::config()->get('system', 'check_new_version_url', 'none') != 'none') {

View file

@ -79,35 +79,6 @@ class Processor
return $body;
}
/**
* Constructs a string with tags for a given tag array
*
* @param array $tags
* @param boolean $sensitive
* @return string with tags
*/
private static function constructTagString(array $tags = null, $sensitive = false)
{
if (empty($tags)) {
return '';
}
$tag_text = '';
foreach ($tags as $tag) {
if (in_array($tag['type'] ?? '', ['Mention', 'Hashtag'])) {
if (!empty($tag_text)) {
$tag_text .= ',';
}
$tag_text .= substr($tag['name'], 0, 1) . '[url=' . $tag['href'] . ']' . substr($tag['name'], 1) . '[/url]';
}
}
/// @todo add nsfw for $sensitive
return $tag_text;
}
/**
* Add attachment data to the item array
*
@ -263,18 +234,9 @@ class Processor
}
Tag::store($item['uri-id'], Tag::HASHTAG, $activity['object_content'], $activity['object_id']);
// To-Do:
// - Check if "blocktag" is set
// - Check if actor is a contact
if (!stristr($item['tag'], trim($activity['object_content']))) {
$tag = $item['tag'] . (strlen($item['tag']) ? ',' : '') . '#[url=' . $activity['object_id'] . ']'. $activity['object_content'] . '[/url]';
Item::update(['tag' => $tag], ['id' => $item['id']]);
Logger::info('Tagged item', ['id' => $item['id'], 'tag' => $activity['object_content'], 'uri' => $activity['target_id'], 'actor' => $activity['actor']]);
}
}
}
/**
* Add users to the receiver list of the given public activity.
@ -387,7 +349,7 @@ class Processor
if (empty($activity['directmessage']) && ($item['thr-parent'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) {
$item_private = !in_array(0, $activity['item_receiver']);
$parent = Item::selectFirst(['id', 'private', 'author-link', 'alias'], ['uri' => $item['thr-parent']]);
$parent = Item::selectFirst(['id', 'uri-id', 'private', 'author-link', 'alias'], ['uri' => $item['thr-parent']]);
if (!DBA::isResult($parent)) {
Logger::warning('Unknown parent item.', ['uri' => $item['thr-parent']]);
return false;
@ -405,8 +367,6 @@ class Processor
$item['body'] = $content;
}
$item['tag'] = self::constructTagString($activity['tags'], $activity['sensitive']);
self::storeFromBody($item);
self::storeTags($item['uri-id'], $activity['tags']);

View file

@ -1996,7 +1996,7 @@ class DFRN
}
$fields = ['title' => $item['title'] ?? '', 'body' => $item['body'] ?? '',
'tag' => $item['tag'] ?? '', 'changed' => DateTimeFormat::utcNow(),
'changed' => DateTimeFormat::utcNow(),
'edited' => DateTimeFormat::utc($item["edited"])];
$condition = ["`uri` = ? AND `uid` IN (0, ?)", $item["uri"], $importer["importer_uid"]];

View file

@ -36,8 +36,8 @@ use Friendica\Model\Conversation;
use Friendica\Model\GContact;
use Friendica\Model\Item;
use Friendica\Model\ItemURI;
use Friendica\Model\ItemDeliveryData;
use Friendica\Model\Mail;
use Friendica\Model\Post;
use Friendica\Model\Tag;
use Friendica\Model\User;
use Friendica\Network\Probe;
@ -252,19 +252,25 @@ class Diaspora
* One of the parameters is a contact array.
* This is done to avoid duplicates.
*
* @param integer $thread The id of the thread
* @param array $parent The parent post
* @param array $contacts The previously fetched contacts
*
* @return array of relay servers
* @throws \Exception
*/
public static function participantsForThread($thread, array $contacts)
public static function participantsForThread(array $parent, array $contacts)
{
$participation = DBA::select('participation-view', [], ['iid' => $thread]);
if (!in_array($parent['private'], [Item::PUBLIC, Item::UNLISTED])) {
return $contacts;
}
while ($contact = DBA::fetch($participation)) {
if (empty($contact['protocol'])) {
$contact['protocol'] = $contact['network'];
$items = Item::select(['author-id'], ['parent' => $parent['id']], ['group_by' => ['author-id']]);
while ($item = DBA::fetch($items)) {
$contact = DBA::selectFirst('contact', ['id', 'url', 'name', 'protocol', 'batch', 'network'],
['id' => $item['author-id']]);
if (!DBA::isResult($contact)) {
// Shouldn't happen
continue;
}
$exists = false;
@ -275,11 +281,11 @@ class Diaspora
}
if (!$exists) {
Logger::info('Add participant to receiver list', ['item' => $parent['guid'], 'participant' => $contact['url']]);
$contacts[] = $contact;
}
}
DBA::close($participation);
DBA::close($items);
return $contacts;
}
@ -2251,18 +2257,32 @@ class Diaspora
* @param array $importer Array of the importer user
* @param object $data The message object
*
* @return bool always true
* @return bool success
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
private static function receiveParticipation(array $importer, $data)
{
$author = strtolower(Strings::escapeTags(XML::unescape($data->author)));
$guid = Strings::escapeTags(XML::unescape($data->guid));
$parent_guid = Strings::escapeTags(XML::unescape($data->parent_guid));
$contact_id = Contact::getIdForURL($author);
if (!$contact_id) {
Logger::log('Contact not found: '.$author);
$contact = self::allowedContactByHandle($importer, $author, true);
if (!$contact) {
return false;
}
if (self::messageExists($importer["uid"], $guid)) {
return true;
}
$parent_item = self::parentItem($importer["uid"], $parent_guid, $author, $contact);
if (!$parent_item) {
return false;
}
if (!in_array($parent_item['private'], [Item::PUBLIC, Item::UNLISTED])) {
Logger::info('Item is not public, participation is ignored', ['parent_guid' => $parent_guid, 'guid' => $guid, 'author' => $author]);
return false;
}
@ -2272,36 +2292,48 @@ class Diaspora
return false;
}
$item = Item::selectFirst(['id'], ['guid' => $parent_guid, 'origin' => true, 'private' => [Item::PUBLIC, Item::UNLISTED]]);
if (!DBA::isResult($item)) {
Logger::log('Item not found, no origin or private: '.$parent_guid);
return false;
}
$author_contact = self::authorContactByUrl($contact, $person, $importer["uid"]);
$author_parts = explode('@', $author);
if (isset($author_parts[1])) {
$server = $author_parts[1];
} else {
// Should never happen
$server = $author;
}
// Store participation
$datarray = [];
Logger::log('Received participation for ID: '.$item['id'].' - Contact: '.$contact_id.' - Server: '.$server, Logger::DEBUG);
$datarray["protocol"] = Conversation::PARCEL_DIASPORA;
if (!DBA::exists('participation', ['iid' => $item['id'], 'server' => $server])) {
DBA::insert('participation', ['iid' => $item['id'], 'cid' => $contact_id, 'fid' => $person['id'], 'server' => $server]);
}
$datarray["uid"] = $importer["uid"];
$datarray["contact-id"] = $author_contact["cid"];
$datarray["network"] = $author_contact["network"];
$datarray["owner-link"] = $datarray["author-link"] = $person["url"];
$datarray["owner-id"] = $datarray["author-id"] = Contact::getIdForURL($person["url"], 0);
$datarray["guid"] = $guid;
$datarray["uri"] = self::getUriFromGuid($author, $guid);
$datarray["verb"] = Activity::FOLLOW;
$datarray["gravity"] = GRAVITY_ACTIVITY;
$datarray["parent-uri"] = $parent_item["uri"];
$datarray["object-type"] = Activity\ObjectType::NOTE;
$datarray["body"] = Activity::FOLLOW;
// Diaspora doesn't provide a date for a participation
$datarray["changed"] = $datarray["created"] = $datarray["edited"] = DateTimeFormat::utcNow();
$message_id = Item::insert($datarray);
Logger::info('Participation stored', ['id' => $message_id, 'guid' => $guid, 'parent_guid' => $parent_guid, 'author' => $author]);
// Send all existing comments and likes to the requesting server
$comments = Item::select(['id', 'parent', 'verb', 'self'], ['parent' => $item['id']]);
$comments = Item::select(['id', 'uri-id', 'parent'], ['parent' => $parent_item['id']]);
while ($comment = Item::fetch($comments)) {
if ($comment['id'] == $comment['parent']) {
continue;
}
Logger::info('Deliver participation', ['item' => $comment['id'], 'contact' => $contact_id]);
if (Worker::add(PRIORITY_HIGH, 'Delivery', Delivery::POST, $comment['id'], $contact_id)) {
ItemDeliveryData::incrementQueueCount($comment['id'], 1);
Logger::info('Deliver participation', ['item' => $comment['id'], 'contact' => $author_contact["cid"]]);
if (Worker::add(PRIORITY_HIGH, 'Delivery', Delivery::POST, $comment['id'], $author_contact["cid"])) {
Post\DeliveryData::incrementQueueCount($comment['uri-id'], 1);
}
}
DBA::close($comments);

View file

@ -655,17 +655,8 @@ class OStatus
foreach ($categories as $category) {
foreach ($category->attributes as $attributes) {
if ($attributes->name == 'term') {
$term = $attributes->textContent;
if (!empty($item['tag'])) {
$item['tag'] .= ',';
} else {
$item['tag'] = '';
}
$item['tag'] .= '#[url=' . DI::baseUrl() . '/search?tag=' . $term . ']' . $term . '[/url]';
// Store the hashtag
Tag::store($item['uri-id'], Tag::HASHTAG, $term);
Tag::store($item['uri-id'], Tag::HASHTAG, $attributes->textContent);
}
}
}
@ -2081,12 +2072,11 @@ class OStatus
XML::addElement($doc, $entry, "ostatus:conversation", $conversation_uri, $attributes);
}
$tags = Tag::getByURIId($item['uri-id']);
if (count($tags)) {
// uri-id isn't present for follow entry pseudo-items
$tags = Tag::getByURIId($item['uri-id'] ?? 0);
foreach ($tags as $tag) {
$mentioned[$tag['url']] = $tag['url'];
}
}
// Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS)
$newmentions = [];
@ -2134,13 +2124,11 @@ class OStatus
XML::addElement($doc, $entry, "mastodon:scope", "public");
}
if (count($tags)) {
foreach ($tags as $tag) {
if ($tag['type'] == Tag::HASHTAG) {
XML::addElement($doc, $entry, "category", "", ["term" => $tag['name']]);
}
}
}
self::getAttachment($doc, $entry, $item);

View file

@ -23,7 +23,8 @@ namespace Friendica\Worker;
use Friendica\Core\Logger;
use Friendica\Core\Worker;
use Friendica\Model\ItemDeliveryData;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\HTTPSignature;
@ -67,10 +68,13 @@ class APDelivery
}
}
// This should never fail and is temporariy (until the move to )
$item = Item::selectFirst(['uri-id'], ['id' => $target_id]);
if (!$success && !Worker::defer() && in_array($cmd, [Delivery::POST])) {
ItemDeliveryData::incrementQueueFailed($target_id);
Post\DeliveryData::incrementQueueFailed($item['uri-id']);
} elseif ($success && in_array($cmd, [Delivery::POST])) {
ItemDeliveryData::incrementQueueDone($target_id, ItemDeliveryData::ACTIVITYPUB);
Post\DeliveryData::incrementQueueDone($item['uri-id'], Post\DeliveryData::ACTIVITYPUB);
}
}
}

View file

@ -58,14 +58,14 @@ class Delivery
if ($cmd == self::MAIL) {
$target_item = DBA::selectFirst('mail', [], ['id' => $target_id]);
if (!DBA::isResult($target_item)) {
self::setFailedQueue($cmd, $target_id);
self::setFailedQueue($cmd, $target_item);
return;
}
$uid = $target_item['uid'];
} elseif ($cmd == self::SUGGESTION) {
$target_item = DBA::selectFirst('fsuggest', [], ['id' => $target_id]);
if (!DBA::isResult($target_item)) {
self::setFailedQueue($cmd, $target_id);
self::setFailedQueue($cmd, $target_item);
return;
}
$uid = $target_item['uid'];
@ -75,7 +75,7 @@ class Delivery
} else {
$item = Model\Item::selectFirst(['parent'], ['id' => $target_id]);
if (!DBA::isResult($item) || empty($item['parent'])) {
self::setFailedQueue($cmd, $target_id);
self::setFailedQueue($cmd, $target_item);
return;
}
$parent_id = intval($item['parent']);
@ -97,13 +97,13 @@ class Delivery
if (empty($target_item)) {
Logger::log('Item ' . $target_id . "wasn't found. Quitting here.");
self::setFailedQueue($cmd, $target_id);
self::setFailedQueue($cmd, $target_item);
return;
}
if (empty($parent)) {
Logger::log('Parent ' . $parent_id . ' for item ' . $target_id . "wasn't found. Quitting here.");
self::setFailedQueue($cmd, $target_id);
self::setFailedQueue($cmd, $target_item);
return;
}
@ -113,7 +113,7 @@ class Delivery
$uid = $target_item['uid'];
} else {
Logger::log('Only public users for item ' . $target_id, Logger::DEBUG);
self::setFailedQueue($cmd, $target_id);
self::setFailedQueue($cmd, $target_item);
return;
}
@ -127,7 +127,7 @@ class Delivery
if (!empty($contact_id) && Model\Contact::isArchived($contact_id)) {
Logger::info('Contact is archived', ['id' => $contact_id, 'cmd' => $cmd, 'item' => $target_item['id']]);
self::setFailedQueue($cmd, $target_id);
self::setFailedQueue($cmd, $target_item);
return;
}
@ -187,7 +187,7 @@ class Delivery
$owner = Model\User::getOwnerDataById($uid);
if (!DBA::isResult($owner)) {
self::setFailedQueue($cmd, $target_id);
self::setFailedQueue($cmd, $target_item);
return;
}
@ -196,12 +196,12 @@ class Delivery
['id' => $contact_id, 'blocked' => false, 'pending' => false, 'self' => false]
);
if (!DBA::isResult($contact)) {
self::setFailedQueue($cmd, $target_id);
self::setFailedQueue($cmd, $target_item);
return;
}
if (Network::isUrlBlocked($contact['url'])) {
self::setFailedQueue($cmd, $target_id);
self::setFailedQueue($cmd, $target_item);
return;
}
@ -243,15 +243,15 @@ class Delivery
* Increased the "failed" counter in the item delivery data
*
* @param string $cmd Command
* @param integer $id Item id
* @param array $item Item array
*/
private static function setFailedQueue(string $cmd, int $id)
private static function setFailedQueue(string $cmd, array $item)
{
if (!in_array($cmd, [Delivery::POST, Delivery::POKE])) {
return;
}
Model\ItemDeliveryData::incrementQueueFailed($id);
Model\Post\DeliveryData::incrementQueueFailed($item['uri-id'] ?? $item['id']);
}
/**
@ -335,13 +335,13 @@ class Delivery
DFRN::import($atom, $target_importer);
if (in_array($cmd, [Delivery::POST, Delivery::POKE])) {
Model\ItemDeliveryData::incrementQueueDone($target_item['id'], Model\ItemDeliveryData::DFRN);
Model\Post\DeliveryData::incrementQueueDone($target_item['uri-id'], Model\Post\DeliveryData::DFRN);
}
return;
}
$protocol = Model\ItemDeliveryData::DFRN;
$protocol = Model\Post\DeliveryData::DFRN;
// We don't have a relationship with contacts on a public post.
// Se we transmit with the new method and via Diaspora as a fallback
@ -357,9 +357,9 @@ class Delivery
if (in_array($cmd, [Delivery::POST, Delivery::POKE])) {
if (($deliver_status >= 200) && ($deliver_status <= 299)) {
Model\ItemDeliveryData::incrementQueueDone($target_item['id'], $protocol);
Model\Post\DeliveryData::incrementQueueDone($target_item['uri-id'], $protocol);
} else {
Model\ItemDeliveryData::incrementQueueFailed($target_item['id']);
Model\Post\DeliveryData::incrementQueueFailed($target_item['uri-id']);
}
}
return;
@ -376,11 +376,11 @@ class Delivery
if ($deliver_status < 200) {
// Legacy DFRN
$deliver_status = DFRN::deliver($owner, $contact, $atom);
$protocol = Model\ItemDeliveryData::LEGACY_DFRN;
$protocol = Model\Post\DeliveryData::LEGACY_DFRN;
}
} else {
$deliver_status = DFRN::deliver($owner, $contact, $atom);
$protocol = Model\ItemDeliveryData::LEGACY_DFRN;
$protocol = Model\Post\DeliveryData::LEGACY_DFRN;
}
Logger::info('DFRN Delivery', ['cmd' => $cmd, 'url' => $contact['url'], 'guid' => ($target_item['guid'] ?? '') ?: $target_item['id'], 'return' => $deliver_status]);
@ -390,7 +390,7 @@ class Delivery
Model\Contact::unmarkForArchival($contact);
if (in_array($cmd, [Delivery::POST, Delivery::POKE])) {
Model\ItemDeliveryData::incrementQueueDone($target_item['id'], $protocol);
Model\Post\DeliveryData::incrementQueueDone($target_item['uri-id'], $protocol);
}
} else {
// The message could not be delivered. We mark the contact as "dead"
@ -398,7 +398,7 @@ class Delivery
Logger::info('Delivery failed: defer message', ['id' => ($target_item['guid'] ?? '') ?: $target_item['id']]);
if (!Worker::defer() && in_array($cmd, [Delivery::POST, Delivery::POKE])) {
Model\ItemDeliveryData::incrementQueueFailed($target_item['id']);
Model\Post\DeliveryData::incrementQueueFailed($target_item['uri-id']);
}
}
}
@ -475,7 +475,7 @@ class Delivery
Model\Contact::unmarkForArchival($contact);
if (in_array($cmd, [Delivery::POST, Delivery::POKE])) {
Model\ItemDeliveryData::incrementQueueDone($target_item['id'], Model\ItemDeliveryData::DIASPORA);
Model\Post\DeliveryData::incrementQueueDone($target_item['uri-id'], Model\Post\DeliveryData::DIASPORA);
}
} else {
// The message could not be delivered. We mark the contact as "dead"
@ -490,10 +490,10 @@ class Delivery
Logger::info('Delivery failed: defer message', ['id' => ($target_item['guid'] ?? '') ?: $target_item['id']]);
// defer message for redelivery
if (!Worker::defer() && in_array($cmd, [Delivery::POST, Delivery::POKE])) {
Model\ItemDeliveryData::incrementQueueFailed($target_item['id']);
Model\Post\DeliveryData::incrementQueueFailed($target_item['uri-id']);
}
} elseif (in_array($cmd, [Delivery::POST, Delivery::POKE])) {
Model\ItemDeliveryData::incrementQueueFailed($target_item['id']);
Model\Post\DeliveryData::incrementQueueFailed($target_item['uri-id']);
}
}
}
@ -603,7 +603,7 @@ class Delivery
Email::send($addr, $subject, $headers, $target_item);
Model\ItemDeliveryData::incrementQueueDone($target_item['id'], Model\ItemDeliveryData::MAIL);
Model\Post\DeliveryData::incrementQueueDone($target_item['uri-id'], Model\Post\DeliveryData::MAIL);
Logger::info('Delivered via mail', ['guid' => $target_item['guid'], 'to' => $addr, 'subject' => $subject]);
}

View file

@ -32,8 +32,9 @@ use Friendica\Model\Contact;
use Friendica\Model\Conversation;
use Friendica\Model\Group;
use Friendica\Model\Item;
use Friendica\Model\ItemDeliveryData;
use Friendica\Model\Post;
use Friendica\Model\PushSubscriber;
use Friendica\Model\Tag;
use Friendica\Model\User;
use Friendica\Network\Probe;
use Friendica\Protocol\ActivityPub;
@ -367,18 +368,13 @@ class Notifier
}
// Send a salmon notification to every person we mentioned in the post
$arr = explode(',',$target_item['tag']);
foreach ($arr as $x) {
//Logger::log('Checking tag '.$x, Logger::DEBUG);
$matches = null;
if (preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
$probed_contact = Probe::uri($matches[1]);
foreach (Tag::getByURIId($target_item['uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION]) as $tag) {
$probed_contact = Probe::uri($tag['url']);
if ($probed_contact["notify"] != "") {
Logger::log('Notify mentioned user '.$probed_contact["url"].': '.$probed_contact["notify"]);
$url_recipients[$probed_contact["notify"]] = $probed_contact["notify"];
}
}
}
// It only makes sense to distribute answers to OStatus messages to Friendica and OStatus - but not Diaspora
$networks = [Protocol::DFRN];
@ -441,7 +437,7 @@ class Notifier
// Fetch the participation list
// The function will ensure that there are no duplicates
$relay_list = Diaspora::participantsForThread($target_id, $relay_list);
$relay_list = Diaspora::participantsForThread($parent, $relay_list);
// Add the relay to the list, avoid duplicates.
// Don't send community posts to the relay. Forum posts via the Diaspora protocol are looking ugly.
@ -573,7 +569,7 @@ class Notifier
/// @TODO Redeliver/queue these items on failure, though there is no contact record
$delivery_queue_count++;
Salmon::slapper($owner, $url, $slap);
ItemDeliveryData::incrementQueueDone($target_id, ItemDeliveryData::OSTATUS);
Post\DeliveryData::incrementQueueDone($target_item['uri-id'], Post\DeliveryData::OSTATUS);
}
}
@ -595,11 +591,11 @@ class Notifier
// Workaround for pure connector posts
if (in_array($cmd, [Delivery::POST, Delivery::POKE])) {
if ($delivery_queue_count == 0) {
ItemDeliveryData::incrementQueueDone($target_item['id']);
Post\DeliveryData::incrementQueueDone($target_item['uri-id']);
$delivery_queue_count = 1;
}
ItemDeliveryData::incrementQueueCount($target_item['id'], $delivery_queue_count);
Post\DeliveryData::incrementQueueCount($target_item['uri-id'], $delivery_queue_count);
}
}

View file

@ -51,7 +51,7 @@
use Friendica\Database\DBA;
if (!defined('DB_UPDATE_VERSION')) {
define('DB_UPDATE_VERSION', 1345);
define('DB_UPDATE_VERSION', 1346);
}
return [
@ -804,25 +804,6 @@ return [
"uri-id" => ["uri-id"]
]
],
"item-delivery-data" => [
"comment" => "Delivery data for items",
"fields" => [
"iid" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "relation" => ["item" => "id"], "comment" => "Item id"],
"postopts" => ["type" => "text", "comment" => "External post connectors add their network name to this comma-separated string to identify that they should be delivered to these networks during delivery"],
"inform" => ["type" => "mediumtext", "comment" => "Additional receivers of the linked item"],
"queue_count" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Initial number of delivery recipients, used as item.delivery_queue_count"],
"queue_done" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries, used as item.delivery_queue_done"],
"queue_failed" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of unsuccessful deliveries, used as item.delivery_queue_failed"],
"activitypub" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via ActivityPub"],
"dfrn" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via DFRN"],
"legacy_dfrn" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via legacy DFRN"],
"diaspora" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via Diaspora"],
"ostatus" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via OStatus"],
],
"indexes" => [
"PRIMARY" => ["iid"],
]
],
"item-uri" => [
"comment" => "URI and GUID for items",
"fields" => [
@ -927,6 +908,8 @@ return [
"link" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
"iid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["item" => "id"], "comment" => "item.id"],
"parent" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["item" => "id"], "comment" => ""],
"uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Item-uri id of the related post"],
"parent-uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Item-uri id of the parent of the related post"],
"seen" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
"verb" => ["type" => "varchar(100)", "not null" => "1", "default" => "", "comment" => ""],
"otype" => ["type" => "varchar(10)", "not null" => "1", "default" => "", "comment" => ""],
@ -945,8 +928,8 @@ return [
"fields" => [
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
"notify-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["notify" => "id"], "comment" => ""],
"master-parent-item" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["item" => "id"],
"comment" => ""],
"master-parent-item" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["item" => "id"], "comment" => ""],
"master-parent-uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Item-uri id of the parent of the related post"],
"parent-item" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "comment" => ""],
"receiver-uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"],
"comment" => "User id"],
@ -1319,6 +1302,25 @@ return [
"uri-id" => ["tid"]
]
],
"post-delivery-data" => [
"comment" => "Delivery data for items",
"fields" => [
"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
"postopts" => ["type" => "text", "comment" => "External post connectors add their network name to this comma-separated string to identify that they should be delivered to these networks during delivery"],
"inform" => ["type" => "mediumtext", "comment" => "Additional receivers of the linked item"],
"queue_count" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Initial number of delivery recipients, used as item.delivery_queue_count"],
"queue_done" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries, used as item.delivery_queue_done"],
"queue_failed" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of unsuccessful deliveries, used as item.delivery_queue_failed"],
"activitypub" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via ActivityPub"],
"dfrn" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via DFRN"],
"legacy_dfrn" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via legacy DFRN"],
"diaspora" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via Diaspora"],
"ostatus" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via OStatus"],
],
"indexes" => [
"PRIMARY" => ["uri-id"],
]
],
"post-tag" => [
"comment" => "post relation to tags",
"fields" => [

View file

@ -291,20 +291,6 @@ return [
INNER JOIN `contact` ON `contact`.`uid` = `user`.`uid` AND `contact`.`self`
INNER JOIN `profile` ON `profile`.`uid` = `user`.`uid`"
],
"participation-view" => [
"fields" => [
"iid" => ["participation", "iid"],
"id" => ["contact", "id"],
"url" => ["contact", "url"],
"name" => ["contact", "name"],
"protocol" => ["contact", "protocol"],
"batch" => "CASE `contact`.`batch` WHEN '' THEN `fcontact`.`batch` ELSE `contact`.`batch` END",
"network" => "CASE `fcontact`.`network` WHEN '' THEN `contact`.`network` ELSE `fcontact`.`network` END",
],
"query" => "FROM `participation`
INNER JOIN `contact` ON `contact`.`id` = `participation`.`cid` AND NOT `contact`.`archive`
INNER JOIN `fcontact` ON `fcontact`.`id` = `participation`.`fid`"
],
"pending-view" => [
"fields" => [
"id" => ["register", "id"],

View file

@ -27,7 +27,7 @@ return [
'photo',
'workerqueue',
'mail',
'item-delivery-data',
'post-delivery-data',
// Base test config to avoid notice messages
'config' => [
[

View file

@ -3849,7 +3849,7 @@ class ApiTest extends DatabaseTest
$assertXml=<<<XML
<?xml version="1.0"?>
<notes>
<note id="1" hash="" type="8" name="Reply to" url="http://localhost/display/1" photo="http://localhost/" date="2020-01-01 12:12:02" msg="A test reply from an item" uid="42" link="http://localhost/notification/1" iid="4" parent="0" seen="0" verb="" otype="item" name_cache="" msg_cache="A test reply from an item" timestamp="1577880722" date_rel="{$dateRel}" msg_html="A test reply from an item" msg_plain="A test reply from an item"/>
<note id="1" hash="" type="8" name="Reply to" url="http://localhost/display/1" photo="http://localhost/" date="2020-01-01 12:12:02" msg="A test reply from an item" uid="42" uri-id="" link="http://localhost/notification/1" iid="4" parent="0" parent-uri-id="" seen="0" verb="" otype="item" name_cache="" msg_cache="A test reply from an item" timestamp="1577880722" date_rel="{$dateRel}" msg_html="A test reply from an item" msg_plain="A test reply from an item"/>
</notes>
XML;
$this->assertXmlStringEqualsXmlString($assertXml, $result);

View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2016 Hyunje Alex Jun
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.

View file

@ -0,0 +1,5 @@
# Bower Package of perfect-scrollbar
This is the [Bower](https://bower.io/) package of perfect-scrollbar.
For details and usage please read more on [https://github.com/noraesae/perfect-scrollbar](https://github.com/noraesae/perfect-scrollbar).

View file

@ -0,0 +1,18 @@
{
"name": "perfect-scrollbar",
"version": "0.6.16",
"homepage": "http://noraesae.github.io/perfect-scrollbar/",
"authors": [
"Hyunje Jun <me@noraesae.net>"
],
"description": "Minimalistic but perfect custom scrollbar plugin",
"main": [
"css/perfect-scrollbar.css",
"js/perfect-scrollbar.js"
],
"license": "MIT",
"ignore": [
"**/.*",
"bower_components"
]
}

View file

@ -0,0 +1,113 @@
/* perfect-scrollbar v0.6.16 */
.ps-container {
-ms-touch-action: auto;
touch-action: auto;
overflow: hidden !important;
-ms-overflow-style: none; }
@supports (-ms-overflow-style: none) {
.ps-container {
overflow: auto !important; } }
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
.ps-container {
overflow: auto !important; } }
.ps-container.ps-active-x > .ps-scrollbar-x-rail,
.ps-container.ps-active-y > .ps-scrollbar-y-rail {
display: block;
background-color: transparent; }
.ps-container.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail {
background-color: #eee;
opacity: 0.9; }
.ps-container.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail > .ps-scrollbar-x {
background-color: #999;
height: 11px; }
.ps-container.ps-in-scrolling.ps-y > .ps-scrollbar-y-rail {
background-color: #eee;
opacity: 0.9; }
.ps-container.ps-in-scrolling.ps-y > .ps-scrollbar-y-rail > .ps-scrollbar-y {
background-color: #999;
width: 11px; }
.ps-container > .ps-scrollbar-x-rail {
display: none;
position: absolute;
/* please don't change 'position' */
opacity: 0;
-webkit-transition: background-color .2s linear, opacity .2s linear;
-o-transition: background-color .2s linear, opacity .2s linear;
-moz-transition: background-color .2s linear, opacity .2s linear;
transition: background-color .2s linear, opacity .2s linear;
bottom: 0px;
/* there must be 'bottom' for ps-scrollbar-x-rail */
height: 15px; }
.ps-container > .ps-scrollbar-x-rail > .ps-scrollbar-x {
position: absolute;
/* please don't change 'position' */
background-color: #aaa;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;
transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;
-o-transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;
-moz-transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;
transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;
transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -webkit-border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;
bottom: 2px;
/* there must be 'bottom' for ps-scrollbar-x */
height: 6px; }
.ps-container > .ps-scrollbar-x-rail:hover > .ps-scrollbar-x, .ps-container > .ps-scrollbar-x-rail:active > .ps-scrollbar-x {
height: 11px; }
.ps-container > .ps-scrollbar-y-rail {
display: none;
position: absolute;
/* please don't change 'position' */
opacity: 0;
-webkit-transition: background-color .2s linear, opacity .2s linear;
-o-transition: background-color .2s linear, opacity .2s linear;
-moz-transition: background-color .2s linear, opacity .2s linear;
transition: background-color .2s linear, opacity .2s linear;
right: 0;
/* there must be 'right' for ps-scrollbar-y-rail */
width: 15px; }
.ps-container > .ps-scrollbar-y-rail > .ps-scrollbar-y {
position: absolute;
/* please don't change 'position' */
background-color: #aaa;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;
transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;
-o-transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;
-moz-transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;
transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;
transition: background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -webkit-border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;
right: 2px;
/* there must be 'right' for ps-scrollbar-y */
width: 6px; }
.ps-container > .ps-scrollbar-y-rail:hover > .ps-scrollbar-y, .ps-container > .ps-scrollbar-y-rail:active > .ps-scrollbar-y {
width: 11px; }
.ps-container:hover.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail {
background-color: #eee;
opacity: 0.9; }
.ps-container:hover.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail > .ps-scrollbar-x {
background-color: #999;
height: 11px; }
.ps-container:hover.ps-in-scrolling.ps-y > .ps-scrollbar-y-rail {
background-color: #eee;
opacity: 0.9; }
.ps-container:hover.ps-in-scrolling.ps-y > .ps-scrollbar-y-rail > .ps-scrollbar-y {
background-color: #999;
width: 11px; }
.ps-container:hover > .ps-scrollbar-x-rail,
.ps-container:hover > .ps-scrollbar-y-rail {
opacity: 0.6; }
.ps-container:hover > .ps-scrollbar-x-rail:hover {
background-color: #eee;
opacity: 0.9; }
.ps-container:hover > .ps-scrollbar-x-rail:hover > .ps-scrollbar-x {
background-color: #999; }
.ps-container:hover > .ps-scrollbar-y-rail:hover {
background-color: #eee;
opacity: 0.9; }
.ps-container:hover > .ps-scrollbar-y-rail:hover > .ps-scrollbar-y {
background-color: #999; }

View file

@ -0,0 +1,2 @@
/* perfect-scrollbar v0.6.16 */
.ps-container{-ms-touch-action:auto;touch-action:auto;overflow:hidden !important;-ms-overflow-style:none}@supports (-ms-overflow-style: none){.ps-container{overflow:auto !important}}@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none){.ps-container{overflow:auto !important}}.ps-container.ps-active-x>.ps-scrollbar-x-rail,.ps-container.ps-active-y>.ps-scrollbar-y-rail{display:block;background-color:transparent}.ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:.9}.ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999;height:11px}.ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:.9}.ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999;width:11px}.ps-container>.ps-scrollbar-x-rail{display:none;position:absolute;opacity:0;-webkit-transition:background-color .2s linear, opacity .2s linear;-o-transition:background-color .2s linear, opacity .2s linear;-moz-transition:background-color .2s linear, opacity .2s linear;transition:background-color .2s linear, opacity .2s linear;bottom:0px;height:15px}.ps-container>.ps-scrollbar-x-rail>.ps-scrollbar-x{position:absolute;background-color:#aaa;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;-o-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;-moz-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -webkit-border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;bottom:2px;height:6px}.ps-container>.ps-scrollbar-x-rail:hover>.ps-scrollbar-x,.ps-container>.ps-scrollbar-x-rail:active>.ps-scrollbar-x{height:11px}.ps-container>.ps-scrollbar-y-rail{display:none;position:absolute;opacity:0;-webkit-transition:background-color .2s linear, opacity .2s linear;-o-transition:background-color .2s linear, opacity .2s linear;-moz-transition:background-color .2s linear, opacity .2s linear;transition:background-color .2s linear, opacity .2s linear;right:0;width:15px}.ps-container>.ps-scrollbar-y-rail>.ps-scrollbar-y{position:absolute;background-color:#aaa;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;-o-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;-moz-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -webkit-border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;right:2px;width:6px}.ps-container>.ps-scrollbar-y-rail:hover>.ps-scrollbar-y,.ps-container>.ps-scrollbar-y-rail:active>.ps-scrollbar-y{width:11px}.ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:.9}.ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999;height:11px}.ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:.9}.ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999;width:11px}.ps-container:hover>.ps-scrollbar-x-rail,.ps-container:hover>.ps-scrollbar-y-rail{opacity:.6}.ps-container:hover>.ps-scrollbar-x-rail:hover{background-color:#eee;opacity:.9}.ps-container:hover>.ps-scrollbar-x-rail:hover>.ps-scrollbar-x{background-color:#999}.ps-container:hover>.ps-scrollbar-y-rail:hover{background-color:#eee;opacity:.9}.ps-container:hover>.ps-scrollbar-y-rail:hover>.ps-scrollbar-y{background-color:#999}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,3 @@
@import 'variables';
@import 'mixins';
@import 'themes';

View file

@ -0,0 +1,128 @@
@mixin scrollbar-rail-default($theme) {
display: none;
position: absolute; /* please don't change 'position' */
opacity: map_get($theme, rail-default-opacity);
transition: background-color .2s linear, opacity .2s linear;
}
@mixin scrollbar-rail-hover($theme) {
background-color: map_get($theme, rail-hover-bg);
opacity: map_get($theme, rail-hover-opacity);
}
@mixin scrollbar-default($theme) {
position: absolute; /* please don't change 'position' */
background-color: map_get($theme, bar-container-hover-bg);
border-radius: map_get($theme, border-radius);
transition: background-color .2s linear, height .2s linear, width .2s ease-in-out,
border-radius .2s ease-in-out;
}
@mixin scrollbar-hover($theme) {
background-color: map_get($theme, bar-hover-bg);
}
@mixin in-scrolling($theme) {
&.ps-in-scrolling {
&.ps-x > .ps-scrollbar-x-rail {
@include scrollbar-rail-hover($theme);
> .ps-scrollbar-x {
@include scrollbar-hover($theme);
height: map_get($theme, scrollbar-x-hover-height);
}
}
&.ps-y > .ps-scrollbar-y-rail {
@include scrollbar-rail-hover($theme);
> .ps-scrollbar-y {
@include scrollbar-hover($theme);
width: map_get($theme, scrollbar-y-hover-width);
}
}
}
}
// Layout and theme mixin
@mixin ps-container($theme) {
-ms-touch-action: auto;
touch-action: auto;
overflow: hidden !important;
-ms-overflow-style: none;
// Edge
@supports (-ms-overflow-style: none) {
overflow: auto !important;
}
// IE10+
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
overflow: auto !important;
}
&.ps-active-x > .ps-scrollbar-x-rail,
&.ps-active-y > .ps-scrollbar-y-rail {
display: block;
background-color: map_get($theme, bar-bg);
}
@include in-scrolling($theme);
> .ps-scrollbar-x-rail {
@include scrollbar-rail-default($theme);
bottom: map_get($theme, scrollbar-x-rail-bottom); /* there must be 'bottom' for ps-scrollbar-x-rail */
height: map_get($theme, scrollbar-x-rail-height);
> .ps-scrollbar-x {
@include scrollbar-default($theme);
bottom: map_get($theme, scrollbar-x-bottom); /* there must be 'bottom' for ps-scrollbar-x */
height: map_get($theme, scrollbar-x-height);
}
&:hover,
&:active {
> .ps-scrollbar-x {
height: map_get($theme, scrollbar-x-hover-height);
}
}
}
> .ps-scrollbar-y-rail {
@include scrollbar-rail-default($theme);
right: map_get($theme, scrollbar-y-rail-right); /* there must be 'right' for ps-scrollbar-y-rail */
width: map_get($theme, scrollbar-y-rail-width);
> .ps-scrollbar-y {
@include scrollbar-default($theme);
right: map_get($theme, scrollbar-y-right); /* there must be 'right' for ps-scrollbar-y */
width: map_get($theme, scrollbar-y-width);
}
&:hover,
&:active {
> .ps-scrollbar-y {
width: map_get($theme, scrollbar-y-hover-width);
}
}
}
&:hover {
@include in-scrolling($theme);
> .ps-scrollbar-x-rail,
> .ps-scrollbar-y-rail {
opacity: map_get($theme, rail-container-hover-opacity);
}
> .ps-scrollbar-x-rail:hover {
@include scrollbar-rail-hover($theme);
> .ps-scrollbar-x {
@include scrollbar-hover($theme);
}
}
> .ps-scrollbar-y-rail:hover {
@include scrollbar-rail-hover($theme);
> .ps-scrollbar-y {
@include scrollbar-hover($theme);
}
}
}
}

View file

@ -0,0 +1,25 @@
$ps-theme-default: (
border-radius: $ps-border-radius,
rail-default-opacity: $ps-rail-default-opacity,
rail-container-hover-opacity: $ps-rail-container-hover-opacity,
rail-hover-opacity: $ps-rail-hover-opacity,
bar-bg: $ps-bar-bg,
bar-container-hover-bg: $ps-bar-container-hover-bg,
bar-hover-bg: $ps-bar-hover-bg,
rail-hover-bg: $ps-rail-hover-bg,
scrollbar-x-rail-bottom: $ps-scrollbar-x-rail-bottom,
scrollbar-x-rail-height: $ps-scrollbar-x-rail-height,
scrollbar-x-bottom: $ps-scrollbar-x-bottom,
scrollbar-x-height: $ps-scrollbar-x-height,
scrollbar-x-hover-height: $ps-scrollbar-x-hover-height,
scrollbar-y-rail-right: $ps-scrollbar-y-rail-right,
scrollbar-y-rail-width: $ps-scrollbar-y-rail-width,
scrollbar-y-right: $ps-scrollbar-y-right,
scrollbar-y-width: $ps-scrollbar-y-width,
scrollbar-y-hover-width: $ps-scrollbar-y-hover-width,
);
// Default theme
.ps-container {
@include ps-container($ps-theme-default);
}

View file

@ -0,0 +1,24 @@
// Colors
$ps-border-radius: 6px !default;
$ps-rail-default-opacity: 0 !default;
$ps-rail-container-hover-opacity: 0.6 !default;
$ps-rail-hover-opacity: 0.9 !default;
$ps-bar-bg: transparent !default;
$ps-bar-container-hover-bg: #aaa !default;
$ps-bar-hover-bg: #999 !default;
$ps-rail-hover-bg: #eee !default;
// Sizes
$ps-scrollbar-x-rail-bottom: 0px !default;
$ps-scrollbar-x-rail-height: 15px !default;
$ps-scrollbar-x-bottom: 2px !default;
$ps-scrollbar-x-height: 6px !default;
$ps-scrollbar-x-hover-height: 11px !default;
$ps-scrollbar-y-rail-right: 0 !default;
$ps-scrollbar-y-rail-width: 15px !default;
$ps-scrollbar-y-right: 2px !default;
$ps-scrollbar-y-width: 6px !default;
$ps-scrollbar-y-hover-width: 11px !default;

View file

@ -0,0 +1,14 @@
'use strict';
var ps = require('../main');
if (typeof define === 'function' && define.amd) {
// AMD
define(ps);
} else {
// Add to a global object.
window.PerfectScrollbar = ps;
if (typeof window.Ps === 'undefined') {
window.Ps = ps;
}
}

View file

@ -0,0 +1,41 @@
'use strict';
var ps = require('../main');
var psInstances = require('../plugin/instances');
function mountJQuery(jQuery) {
jQuery.fn.perfectScrollbar = function (settingOrCommand) {
return this.each(function () {
if (typeof settingOrCommand === 'object' ||
typeof settingOrCommand === 'undefined') {
// If it's an object or none, initialize.
var settings = settingOrCommand;
if (!psInstances.get(this)) {
ps.initialize(this, settings);
}
} else {
// Unless, it may be a command.
var command = settingOrCommand;
if (command === 'update') {
ps.update(this);
} else if (command === 'destroy') {
ps.destroy(this);
}
}
});
};
}
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], mountJQuery);
} else {
var jq = window.jQuery ? window.jQuery : window.$;
if (typeof jq !== 'undefined') {
mountJQuery(jq);
}
}
module.exports = mountJQuery;

View file

@ -0,0 +1,42 @@
'use strict';
function oldAdd(element, className) {
var classes = element.className.split(' ');
if (classes.indexOf(className) < 0) {
classes.push(className);
}
element.className = classes.join(' ');
}
function oldRemove(element, className) {
var classes = element.className.split(' ');
var idx = classes.indexOf(className);
if (idx >= 0) {
classes.splice(idx, 1);
}
element.className = classes.join(' ');
}
exports.add = function (element, className) {
if (element.classList) {
element.classList.add(className);
} else {
oldAdd(element, className);
}
};
exports.remove = function (element, className) {
if (element.classList) {
element.classList.remove(className);
} else {
oldRemove(element, className);
}
};
exports.list = function (element) {
if (element.classList) {
return Array.prototype.slice.apply(element.classList);
} else {
return element.className.split(' ');
}
};

View file

@ -0,0 +1,84 @@
'use strict';
var DOM = {};
DOM.e = function (tagName, className) {
var element = document.createElement(tagName);
element.className = className;
return element;
};
DOM.appendTo = function (child, parent) {
parent.appendChild(child);
return child;
};
function cssGet(element, styleName) {
return window.getComputedStyle(element)[styleName];
}
function cssSet(element, styleName, styleValue) {
if (typeof styleValue === 'number') {
styleValue = styleValue.toString() + 'px';
}
element.style[styleName] = styleValue;
return element;
}
function cssMultiSet(element, obj) {
for (var key in obj) {
var val = obj[key];
if (typeof val === 'number') {
val = val.toString() + 'px';
}
element.style[key] = val;
}
return element;
}
DOM.css = function (element, styleNameOrObject, styleValue) {
if (typeof styleNameOrObject === 'object') {
// multiple set with object
return cssMultiSet(element, styleNameOrObject);
} else {
if (typeof styleValue === 'undefined') {
return cssGet(element, styleNameOrObject);
} else {
return cssSet(element, styleNameOrObject, styleValue);
}
}
};
DOM.matches = function (element, query) {
if (typeof element.matches !== 'undefined') {
return element.matches(query);
} else {
if (typeof element.matchesSelector !== 'undefined') {
return element.matchesSelector(query);
} else if (typeof element.webkitMatchesSelector !== 'undefined') {
return element.webkitMatchesSelector(query);
} else if (typeof element.mozMatchesSelector !== 'undefined') {
return element.mozMatchesSelector(query);
} else if (typeof element.msMatchesSelector !== 'undefined') {
return element.msMatchesSelector(query);
}
}
};
DOM.remove = function (element) {
if (typeof element.remove !== 'undefined') {
element.remove();
} else {
if (element.parentNode) {
element.parentNode.removeChild(element);
}
}
};
DOM.queryChildren = function (element, selector) {
return Array.prototype.filter.call(element.childNodes, function (child) {
return DOM.matches(child, selector);
});
};
module.exports = DOM;

View file

@ -0,0 +1,71 @@
'use strict';
var EventElement = function (element) {
this.element = element;
this.events = {};
};
EventElement.prototype.bind = function (eventName, handler) {
if (typeof this.events[eventName] === 'undefined') {
this.events[eventName] = [];
}
this.events[eventName].push(handler);
this.element.addEventListener(eventName, handler, false);
};
EventElement.prototype.unbind = function (eventName, handler) {
var isHandlerProvided = (typeof handler !== 'undefined');
this.events[eventName] = this.events[eventName].filter(function (hdlr) {
if (isHandlerProvided && hdlr !== handler) {
return true;
}
this.element.removeEventListener(eventName, hdlr, false);
return false;
}, this);
};
EventElement.prototype.unbindAll = function () {
for (var name in this.events) {
this.unbind(name);
}
};
var EventManager = function () {
this.eventElements = [];
};
EventManager.prototype.eventElement = function (element) {
var ee = this.eventElements.filter(function (eventElement) {
return eventElement.element === element;
})[0];
if (typeof ee === 'undefined') {
ee = new EventElement(element);
this.eventElements.push(ee);
}
return ee;
};
EventManager.prototype.bind = function (element, eventName, handler) {
this.eventElement(element).bind(eventName, handler);
};
EventManager.prototype.unbind = function (element, eventName, handler) {
this.eventElement(element).unbind(eventName, handler);
};
EventManager.prototype.unbindAll = function () {
for (var i = 0; i < this.eventElements.length; i++) {
this.eventElements[i].unbindAll();
}
};
EventManager.prototype.once = function (element, eventName, handler) {
var ee = this.eventElement(element);
var onceHandler = function (e) {
ee.unbind(eventName, onceHandler);
handler(e);
};
ee.bind(eventName, onceHandler);
};
module.exports = EventManager;

View file

@ -0,0 +1,13 @@
'use strict';
module.exports = (function () {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return function () {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
};
})();

View file

@ -0,0 +1,83 @@
'use strict';
var cls = require('./class');
var dom = require('./dom');
var toInt = exports.toInt = function (x) {
return parseInt(x, 10) || 0;
};
var clone = exports.clone = function (obj) {
if (!obj) {
return null;
} else if (obj.constructor === Array) {
return obj.map(clone);
} else if (typeof obj === 'object') {
var result = {};
for (var key in obj) {
result[key] = clone(obj[key]);
}
return result;
} else {
return obj;
}
};
exports.extend = function (original, source) {
var result = clone(original);
for (var key in source) {
result[key] = clone(source[key]);
}
return result;
};
exports.isEditable = function (el) {
return dom.matches(el, "input,[contenteditable]") ||
dom.matches(el, "select,[contenteditable]") ||
dom.matches(el, "textarea,[contenteditable]") ||
dom.matches(el, "button,[contenteditable]");
};
exports.removePsClasses = function (element) {
var clsList = cls.list(element);
for (var i = 0; i < clsList.length; i++) {
var className = clsList[i];
if (className.indexOf('ps-') === 0) {
cls.remove(element, className);
}
}
};
exports.outerWidth = function (element) {
return toInt(dom.css(element, 'width')) +
toInt(dom.css(element, 'paddingLeft')) +
toInt(dom.css(element, 'paddingRight')) +
toInt(dom.css(element, 'borderLeftWidth')) +
toInt(dom.css(element, 'borderRightWidth'));
};
exports.startScrolling = function (element, axis) {
cls.add(element, 'ps-in-scrolling');
if (typeof axis !== 'undefined') {
cls.add(element, 'ps-' + axis);
} else {
cls.add(element, 'ps-x');
cls.add(element, 'ps-y');
}
};
exports.stopScrolling = function (element, axis) {
cls.remove(element, 'ps-in-scrolling');
if (typeof axis !== 'undefined') {
cls.remove(element, 'ps-' + axis);
} else {
cls.remove(element, 'ps-x');
cls.remove(element, 'ps-y');
}
};
exports.env = {
isWebKit: 'WebkitAppearance' in document.documentElement.style,
supportsTouch: (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch),
supportsIePointer: window.navigator.msMaxTouchPoints !== null
};

View file

@ -0,0 +1,11 @@
'use strict';
var destroy = require('./plugin/destroy');
var initialize = require('./plugin/initialize');
var update = require('./plugin/update');
module.exports = {
initialize: initialize,
update: update,
destroy: destroy
};

View file

@ -0,0 +1,16 @@
'use strict';
module.exports = {
handlers: ['click-rail', 'drag-scrollbar', 'keyboard', 'wheel', 'touch'],
maxScrollbarLength: null,
minScrollbarLength: null,
scrollXMarginOffset: 0,
scrollYMarginOffset: 0,
suppressScrollX: false,
suppressScrollY: false,
swipePropagation: true,
useBothWheelAxes: false,
wheelPropagation: false,
wheelSpeed: 1,
theme: 'default'
};

View file

@ -0,0 +1,22 @@
'use strict';
var _ = require('../lib/helper');
var dom = require('../lib/dom');
var instances = require('./instances');
module.exports = function (element) {
var i = instances.get(element);
if (!i) {
return;
}
i.event.unbindAll();
dom.remove(i.scrollbarX);
dom.remove(i.scrollbarY);
dom.remove(i.scrollbarXRail);
dom.remove(i.scrollbarYRail);
_.removePsClasses(element);
instances.remove(element);
};

View file

@ -0,0 +1,39 @@
'use strict';
var instances = require('../instances');
var updateGeometry = require('../update-geometry');
var updateScroll = require('../update-scroll');
function bindClickRailHandler(element, i) {
function pageOffset(el) {
return el.getBoundingClientRect();
}
var stopPropagation = function (e) { e.stopPropagation(); };
i.event.bind(i.scrollbarY, 'click', stopPropagation);
i.event.bind(i.scrollbarYRail, 'click', function (e) {
var positionTop = e.pageY - window.pageYOffset - pageOffset(i.scrollbarYRail).top;
var direction = positionTop > i.scrollbarYTop ? 1 : -1;
updateScroll(element, 'top', element.scrollTop + direction * i.containerHeight);
updateGeometry(element);
e.stopPropagation();
});
i.event.bind(i.scrollbarX, 'click', stopPropagation);
i.event.bind(i.scrollbarXRail, 'click', function (e) {
var positionLeft = e.pageX - window.pageXOffset - pageOffset(i.scrollbarXRail).left;
var direction = positionLeft > i.scrollbarXLeft ? 1 : -1;
updateScroll(element, 'left', element.scrollLeft + direction * i.containerWidth);
updateGeometry(element);
e.stopPropagation();
});
}
module.exports = function (element) {
var i = instances.get(element);
bindClickRailHandler(element, i);
};

View file

@ -0,0 +1,103 @@
'use strict';
var _ = require('../../lib/helper');
var dom = require('../../lib/dom');
var instances = require('../instances');
var updateGeometry = require('../update-geometry');
var updateScroll = require('../update-scroll');
function bindMouseScrollXHandler(element, i) {
var currentLeft = null;
var currentPageX = null;
function updateScrollLeft(deltaX) {
var newLeft = currentLeft + (deltaX * i.railXRatio);
var maxLeft = Math.max(0, i.scrollbarXRail.getBoundingClientRect().left) + (i.railXRatio * (i.railXWidth - i.scrollbarXWidth));
if (newLeft < 0) {
i.scrollbarXLeft = 0;
} else if (newLeft > maxLeft) {
i.scrollbarXLeft = maxLeft;
} else {
i.scrollbarXLeft = newLeft;
}
var scrollLeft = _.toInt(i.scrollbarXLeft * (i.contentWidth - i.containerWidth) / (i.containerWidth - (i.railXRatio * i.scrollbarXWidth))) - i.negativeScrollAdjustment;
updateScroll(element, 'left', scrollLeft);
}
var mouseMoveHandler = function (e) {
updateScrollLeft(e.pageX - currentPageX);
updateGeometry(element);
e.stopPropagation();
e.preventDefault();
};
var mouseUpHandler = function () {
_.stopScrolling(element, 'x');
i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler);
};
i.event.bind(i.scrollbarX, 'mousedown', function (e) {
currentPageX = e.pageX;
currentLeft = _.toInt(dom.css(i.scrollbarX, 'left')) * i.railXRatio;
_.startScrolling(element, 'x');
i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler);
i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler);
e.stopPropagation();
e.preventDefault();
});
}
function bindMouseScrollYHandler(element, i) {
var currentTop = null;
var currentPageY = null;
function updateScrollTop(deltaY) {
var newTop = currentTop + (deltaY * i.railYRatio);
var maxTop = Math.max(0, i.scrollbarYRail.getBoundingClientRect().top) + (i.railYRatio * (i.railYHeight - i.scrollbarYHeight));
if (newTop < 0) {
i.scrollbarYTop = 0;
} else if (newTop > maxTop) {
i.scrollbarYTop = maxTop;
} else {
i.scrollbarYTop = newTop;
}
var scrollTop = _.toInt(i.scrollbarYTop * (i.contentHeight - i.containerHeight) / (i.containerHeight - (i.railYRatio * i.scrollbarYHeight)));
updateScroll(element, 'top', scrollTop);
}
var mouseMoveHandler = function (e) {
updateScrollTop(e.pageY - currentPageY);
updateGeometry(element);
e.stopPropagation();
e.preventDefault();
};
var mouseUpHandler = function () {
_.stopScrolling(element, 'y');
i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler);
};
i.event.bind(i.scrollbarY, 'mousedown', function (e) {
currentPageY = e.pageY;
currentTop = _.toInt(dom.css(i.scrollbarY, 'top')) * i.railYRatio;
_.startScrolling(element, 'y');
i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler);
i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler);
e.stopPropagation();
e.preventDefault();
});
}
module.exports = function (element) {
var i = instances.get(element);
bindMouseScrollXHandler(element, i);
bindMouseScrollYHandler(element, i);
};

View file

@ -0,0 +1,154 @@
'use strict';
var _ = require('../../lib/helper');
var dom = require('../../lib/dom');
var instances = require('../instances');
var updateGeometry = require('../update-geometry');
var updateScroll = require('../update-scroll');
function bindKeyboardHandler(element, i) {
var hovered = false;
i.event.bind(element, 'mouseenter', function () {
hovered = true;
});
i.event.bind(element, 'mouseleave', function () {
hovered = false;
});
var shouldPrevent = false;
function shouldPreventDefault(deltaX, deltaY) {
var scrollTop = element.scrollTop;
if (deltaX === 0) {
if (!i.scrollbarYActive) {
return false;
}
if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)) {
return !i.settings.wheelPropagation;
}
}
var scrollLeft = element.scrollLeft;
if (deltaY === 0) {
if (!i.scrollbarXActive) {
return false;
}
if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)) {
return !i.settings.wheelPropagation;
}
}
return true;
}
i.event.bind(i.ownerDocument, 'keydown', function (e) {
if ((e.isDefaultPrevented && e.isDefaultPrevented()) || e.defaultPrevented) {
return;
}
var focused = dom.matches(i.scrollbarX, ':focus') ||
dom.matches(i.scrollbarY, ':focus');
if (!hovered && !focused) {
return;
}
var activeElement = document.activeElement ? document.activeElement : i.ownerDocument.activeElement;
if (activeElement) {
if (activeElement.tagName === 'IFRAME') {
activeElement = activeElement.contentDocument.activeElement;
} else {
// go deeper if element is a webcomponent
while (activeElement.shadowRoot) {
activeElement = activeElement.shadowRoot.activeElement;
}
}
if (_.isEditable(activeElement)) {
return;
}
}
var deltaX = 0;
var deltaY = 0;
switch (e.which) {
case 37: // left
if (e.metaKey) {
deltaX = -i.contentWidth;
} else if (e.altKey) {
deltaX = -i.containerWidth;
} else {
deltaX = -30;
}
break;
case 38: // up
if (e.metaKey) {
deltaY = i.contentHeight;
} else if (e.altKey) {
deltaY = i.containerHeight;
} else {
deltaY = 30;
}
break;
case 39: // right
if (e.metaKey) {
deltaX = i.contentWidth;
} else if (e.altKey) {
deltaX = i.containerWidth;
} else {
deltaX = 30;
}
break;
case 40: // down
if (e.metaKey) {
deltaY = -i.contentHeight;
} else if (e.altKey) {
deltaY = -i.containerHeight;
} else {
deltaY = -30;
}
break;
case 33: // page up
deltaY = 90;
break;
case 32: // space bar
if (e.shiftKey) {
deltaY = 90;
} else {
deltaY = -90;
}
break;
case 34: // page down
deltaY = -90;
break;
case 35: // end
if (e.ctrlKey) {
deltaY = -i.contentHeight;
} else {
deltaY = -i.containerHeight;
}
break;
case 36: // home
if (e.ctrlKey) {
deltaY = element.scrollTop;
} else {
deltaY = i.containerHeight;
}
break;
default:
return;
}
updateScroll(element, 'top', element.scrollTop - deltaY);
updateScroll(element, 'left', element.scrollLeft + deltaX);
updateGeometry(element);
shouldPrevent = shouldPreventDefault(deltaX, deltaY);
if (shouldPrevent) {
e.preventDefault();
}
});
}
module.exports = function (element) {
var i = instances.get(element);
bindKeyboardHandler(element, i);
};

View file

@ -0,0 +1,141 @@
'use strict';
var instances = require('../instances');
var updateGeometry = require('../update-geometry');
var updateScroll = require('../update-scroll');
function bindMouseWheelHandler(element, i) {
var shouldPrevent = false;
function shouldPreventDefault(deltaX, deltaY) {
var scrollTop = element.scrollTop;
if (deltaX === 0) {
if (!i.scrollbarYActive) {
return false;
}
if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)) {
return !i.settings.wheelPropagation;
}
}
var scrollLeft = element.scrollLeft;
if (deltaY === 0) {
if (!i.scrollbarXActive) {
return false;
}
if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)) {
return !i.settings.wheelPropagation;
}
}
return true;
}
function getDeltaFromEvent(e) {
var deltaX = e.deltaX;
var deltaY = -1 * e.deltaY;
if (typeof deltaX === "undefined" || typeof deltaY === "undefined") {
// OS X Safari
deltaX = -1 * e.wheelDeltaX / 6;
deltaY = e.wheelDeltaY / 6;
}
if (e.deltaMode && e.deltaMode === 1) {
// Firefox in deltaMode 1: Line scrolling
deltaX *= 10;
deltaY *= 10;
}
if (deltaX !== deltaX && deltaY !== deltaY/* NaN checks */) {
// IE in some mouse drivers
deltaX = 0;
deltaY = e.wheelDelta;
}
if (e.shiftKey) {
// reverse axis with shift key
return [-deltaY, -deltaX];
}
return [deltaX, deltaY];
}
function shouldBeConsumedByChild(deltaX, deltaY) {
var child = element.querySelector('textarea:hover, select[multiple]:hover, .ps-child:hover');
if (child) {
if (!window.getComputedStyle(child).overflow.match(/(scroll|auto)/)) {
// if not scrollable
return false;
}
var maxScrollTop = child.scrollHeight - child.clientHeight;
if (maxScrollTop > 0) {
if (!(child.scrollTop === 0 && deltaY > 0) && !(child.scrollTop === maxScrollTop && deltaY < 0)) {
return true;
}
}
var maxScrollLeft = child.scrollLeft - child.clientWidth;
if (maxScrollLeft > 0) {
if (!(child.scrollLeft === 0 && deltaX < 0) && !(child.scrollLeft === maxScrollLeft && deltaX > 0)) {
return true;
}
}
}
return false;
}
function mousewheelHandler(e) {
var delta = getDeltaFromEvent(e);
var deltaX = delta[0];
var deltaY = delta[1];
if (shouldBeConsumedByChild(deltaX, deltaY)) {
return;
}
shouldPrevent = false;
if (!i.settings.useBothWheelAxes) {
// deltaX will only be used for horizontal scrolling and deltaY will
// only be used for vertical scrolling - this is the default
updateScroll(element, 'top', element.scrollTop - (deltaY * i.settings.wheelSpeed));
updateScroll(element, 'left', element.scrollLeft + (deltaX * i.settings.wheelSpeed));
} else if (i.scrollbarYActive && !i.scrollbarXActive) {
// only vertical scrollbar is active and useBothWheelAxes option is
// active, so let's scroll vertical bar using both mouse wheel axes
if (deltaY) {
updateScroll(element, 'top', element.scrollTop - (deltaY * i.settings.wheelSpeed));
} else {
updateScroll(element, 'top', element.scrollTop + (deltaX * i.settings.wheelSpeed));
}
shouldPrevent = true;
} else if (i.scrollbarXActive && !i.scrollbarYActive) {
// useBothWheelAxes and only horizontal bar is active, so use both
// wheel axes for horizontal bar
if (deltaX) {
updateScroll(element, 'left', element.scrollLeft + (deltaX * i.settings.wheelSpeed));
} else {
updateScroll(element, 'left', element.scrollLeft - (deltaY * i.settings.wheelSpeed));
}
shouldPrevent = true;
}
updateGeometry(element);
shouldPrevent = (shouldPrevent || shouldPreventDefault(deltaX, deltaY));
if (shouldPrevent) {
e.stopPropagation();
e.preventDefault();
}
}
if (typeof window.onwheel !== "undefined") {
i.event.bind(element, 'wheel', mousewheelHandler);
} else if (typeof window.onmousewheel !== "undefined") {
i.event.bind(element, 'mousewheel', mousewheelHandler);
}
}
module.exports = function (element) {
var i = instances.get(element);
bindMouseWheelHandler(element, i);
};

View file

@ -0,0 +1,15 @@
'use strict';
var instances = require('../instances');
var updateGeometry = require('../update-geometry');
function bindNativeScrollHandler(element, i) {
i.event.bind(element, 'scroll', function () {
updateGeometry(element);
});
}
module.exports = function (element) {
var i = instances.get(element);
bindNativeScrollHandler(element, i);
};

View file

@ -0,0 +1,115 @@
'use strict';
var _ = require('../../lib/helper');
var instances = require('../instances');
var updateGeometry = require('../update-geometry');
var updateScroll = require('../update-scroll');
function bindSelectionHandler(element, i) {
function getRangeNode() {
var selection = window.getSelection ? window.getSelection() :
document.getSelection ? document.getSelection() : '';
if (selection.toString().length === 0) {
return null;
} else {
return selection.getRangeAt(0).commonAncestorContainer;
}
}
var scrollingLoop = null;
var scrollDiff = {top: 0, left: 0};
function startScrolling() {
if (!scrollingLoop) {
scrollingLoop = setInterval(function () {
if (!instances.get(element)) {
clearInterval(scrollingLoop);
return;
}
updateScroll(element, 'top', element.scrollTop + scrollDiff.top);
updateScroll(element, 'left', element.scrollLeft + scrollDiff.left);
updateGeometry(element);
}, 50); // every .1 sec
}
}
function stopScrolling() {
if (scrollingLoop) {
clearInterval(scrollingLoop);
scrollingLoop = null;
}
_.stopScrolling(element);
}
var isSelected = false;
i.event.bind(i.ownerDocument, 'selectionchange', function () {
if (element.contains(getRangeNode())) {
isSelected = true;
} else {
isSelected = false;
stopScrolling();
}
});
i.event.bind(window, 'mouseup', function () {
if (isSelected) {
isSelected = false;
stopScrolling();
}
});
i.event.bind(window, 'keyup', function () {
if (isSelected) {
isSelected = false;
stopScrolling();
}
});
i.event.bind(window, 'mousemove', function (e) {
if (isSelected) {
var mousePosition = {x: e.pageX, y: e.pageY};
var containerGeometry = {
left: element.offsetLeft,
right: element.offsetLeft + element.offsetWidth,
top: element.offsetTop,
bottom: element.offsetTop + element.offsetHeight
};
if (mousePosition.x < containerGeometry.left + 3) {
scrollDiff.left = -5;
_.startScrolling(element, 'x');
} else if (mousePosition.x > containerGeometry.right - 3) {
scrollDiff.left = 5;
_.startScrolling(element, 'x');
} else {
scrollDiff.left = 0;
}
if (mousePosition.y < containerGeometry.top + 3) {
if (containerGeometry.top + 3 - mousePosition.y < 5) {
scrollDiff.top = -5;
} else {
scrollDiff.top = -20;
}
_.startScrolling(element, 'y');
} else if (mousePosition.y > containerGeometry.bottom - 3) {
if (mousePosition.y - containerGeometry.bottom + 3 < 5) {
scrollDiff.top = 5;
} else {
scrollDiff.top = 20;
}
_.startScrolling(element, 'y');
} else {
scrollDiff.top = 0;
}
if (scrollDiff.top === 0 && scrollDiff.left === 0) {
stopScrolling();
} else {
startScrolling();
}
}
});
}
module.exports = function (element) {
var i = instances.get(element);
bindSelectionHandler(element, i);
};

View file

@ -0,0 +1,179 @@
'use strict';
var _ = require('../../lib/helper');
var instances = require('../instances');
var updateGeometry = require('../update-geometry');
var updateScroll = require('../update-scroll');
function bindTouchHandler(element, i, supportsTouch, supportsIePointer) {
function shouldPreventDefault(deltaX, deltaY) {
var scrollTop = element.scrollTop;
var scrollLeft = element.scrollLeft;
var magnitudeX = Math.abs(deltaX);
var magnitudeY = Math.abs(deltaY);
if (magnitudeY > magnitudeX) {
// user is perhaps trying to swipe up/down the page
if (((deltaY < 0) && (scrollTop === i.contentHeight - i.containerHeight)) ||
((deltaY > 0) && (scrollTop === 0))) {
return !i.settings.swipePropagation;
}
} else if (magnitudeX > magnitudeY) {
// user is perhaps trying to swipe left/right across the page
if (((deltaX < 0) && (scrollLeft === i.contentWidth - i.containerWidth)) ||
((deltaX > 0) && (scrollLeft === 0))) {
return !i.settings.swipePropagation;
}
}
return true;
}
function applyTouchMove(differenceX, differenceY) {
updateScroll(element, 'top', element.scrollTop - differenceY);
updateScroll(element, 'left', element.scrollLeft - differenceX);
updateGeometry(element);
}
var startOffset = {};
var startTime = 0;
var speed = {};
var easingLoop = null;
var inGlobalTouch = false;
var inLocalTouch = false;
function globalTouchStart() {
inGlobalTouch = true;
}
function globalTouchEnd() {
inGlobalTouch = false;
}
function getTouch(e) {
if (e.targetTouches) {
return e.targetTouches[0];
} else {
// Maybe IE pointer
return e;
}
}
function shouldHandle(e) {
if (e.targetTouches && e.targetTouches.length === 1) {
return true;
}
if (e.pointerType && e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
return true;
}
return false;
}
function touchStart(e) {
if (shouldHandle(e)) {
inLocalTouch = true;
var touch = getTouch(e);
startOffset.pageX = touch.pageX;
startOffset.pageY = touch.pageY;
startTime = (new Date()).getTime();
if (easingLoop !== null) {
clearInterval(easingLoop);
}
e.stopPropagation();
}
}
function touchMove(e) {
if (!inLocalTouch && i.settings.swipePropagation) {
touchStart(e);
}
if (!inGlobalTouch && inLocalTouch && shouldHandle(e)) {
var touch = getTouch(e);
var currentOffset = {pageX: touch.pageX, pageY: touch.pageY};
var differenceX = currentOffset.pageX - startOffset.pageX;
var differenceY = currentOffset.pageY - startOffset.pageY;
applyTouchMove(differenceX, differenceY);
startOffset = currentOffset;
var currentTime = (new Date()).getTime();
var timeGap = currentTime - startTime;
if (timeGap > 0) {
speed.x = differenceX / timeGap;
speed.y = differenceY / timeGap;
startTime = currentTime;
}
if (shouldPreventDefault(differenceX, differenceY)) {
e.stopPropagation();
e.preventDefault();
}
}
}
function touchEnd() {
if (!inGlobalTouch && inLocalTouch) {
inLocalTouch = false;
clearInterval(easingLoop);
easingLoop = setInterval(function () {
if (!instances.get(element)) {
clearInterval(easingLoop);
return;
}
if (!speed.x && !speed.y) {
clearInterval(easingLoop);
return;
}
if (Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) {
clearInterval(easingLoop);
return;
}
applyTouchMove(speed.x * 30, speed.y * 30);
speed.x *= 0.8;
speed.y *= 0.8;
}, 10);
}
}
if (supportsTouch) {
i.event.bind(window, 'touchstart', globalTouchStart);
i.event.bind(window, 'touchend', globalTouchEnd);
i.event.bind(element, 'touchstart', touchStart);
i.event.bind(element, 'touchmove', touchMove);
i.event.bind(element, 'touchend', touchEnd);
} else if (supportsIePointer) {
if (window.PointerEvent) {
i.event.bind(window, 'pointerdown', globalTouchStart);
i.event.bind(window, 'pointerup', globalTouchEnd);
i.event.bind(element, 'pointerdown', touchStart);
i.event.bind(element, 'pointermove', touchMove);
i.event.bind(element, 'pointerup', touchEnd);
} else if (window.MSPointerEvent) {
i.event.bind(window, 'MSPointerDown', globalTouchStart);
i.event.bind(window, 'MSPointerUp', globalTouchEnd);
i.event.bind(element, 'MSPointerDown', touchStart);
i.event.bind(element, 'MSPointerMove', touchMove);
i.event.bind(element, 'MSPointerUp', touchEnd);
}
}
}
module.exports = function (element) {
if (!_.env.supportsTouch && !_.env.supportsIePointer) {
return;
}
var i = instances.get(element);
bindTouchHandler(element, i, _.env.supportsTouch, _.env.supportsIePointer);
};

View file

@ -0,0 +1,37 @@
'use strict';
var _ = require('../lib/helper');
var cls = require('../lib/class');
var instances = require('./instances');
var updateGeometry = require('./update-geometry');
// Handlers
var handlers = {
'click-rail': require('./handler/click-rail'),
'drag-scrollbar': require('./handler/drag-scrollbar'),
'keyboard': require('./handler/keyboard'),
'wheel': require('./handler/mouse-wheel'),
'touch': require('./handler/touch'),
'selection': require('./handler/selection')
};
var nativeScrollHandler = require('./handler/native-scroll');
module.exports = function (element, userSettings) {
userSettings = typeof userSettings === 'object' ? userSettings : {};
cls.add(element, 'ps-container');
// Create a plugin instance.
var i = instances.add(element);
i.settings = _.extend(i.settings, userSettings);
cls.add(element, 'ps-theme-' + i.settings.theme);
i.settings.handlers.forEach(function (handlerName) {
handlers[handlerName](element);
});
nativeScrollHandler(element);
updateGeometry(element);
};

View file

@ -0,0 +1,107 @@
'use strict';
var _ = require('../lib/helper');
var cls = require('../lib/class');
var defaultSettings = require('./default-setting');
var dom = require('../lib/dom');
var EventManager = require('../lib/event-manager');
var guid = require('../lib/guid');
var instances = {};
function Instance(element) {
var i = this;
i.settings = _.clone(defaultSettings);
i.containerWidth = null;
i.containerHeight = null;
i.contentWidth = null;
i.contentHeight = null;
i.isRtl = dom.css(element, 'direction') === "rtl";
i.isNegativeScroll = (function () {
var originalScrollLeft = element.scrollLeft;
var result = null;
element.scrollLeft = -1;
result = element.scrollLeft < 0;
element.scrollLeft = originalScrollLeft;
return result;
})();
i.negativeScrollAdjustment = i.isNegativeScroll ? element.scrollWidth - element.clientWidth : 0;
i.event = new EventManager();
i.ownerDocument = element.ownerDocument || document;
function focus() {
cls.add(element, 'ps-focus');
}
function blur() {
cls.remove(element, 'ps-focus');
}
i.scrollbarXRail = dom.appendTo(dom.e('div', 'ps-scrollbar-x-rail'), element);
i.scrollbarX = dom.appendTo(dom.e('div', 'ps-scrollbar-x'), i.scrollbarXRail);
i.scrollbarX.setAttribute('tabindex', 0);
i.event.bind(i.scrollbarX, 'focus', focus);
i.event.bind(i.scrollbarX, 'blur', blur);
i.scrollbarXActive = null;
i.scrollbarXWidth = null;
i.scrollbarXLeft = null;
i.scrollbarXBottom = _.toInt(dom.css(i.scrollbarXRail, 'bottom'));
i.isScrollbarXUsingBottom = i.scrollbarXBottom === i.scrollbarXBottom; // !isNaN
i.scrollbarXTop = i.isScrollbarXUsingBottom ? null : _.toInt(dom.css(i.scrollbarXRail, 'top'));
i.railBorderXWidth = _.toInt(dom.css(i.scrollbarXRail, 'borderLeftWidth')) + _.toInt(dom.css(i.scrollbarXRail, 'borderRightWidth'));
// Set rail to display:block to calculate margins
dom.css(i.scrollbarXRail, 'display', 'block');
i.railXMarginWidth = _.toInt(dom.css(i.scrollbarXRail, 'marginLeft')) + _.toInt(dom.css(i.scrollbarXRail, 'marginRight'));
dom.css(i.scrollbarXRail, 'display', '');
i.railXWidth = null;
i.railXRatio = null;
i.scrollbarYRail = dom.appendTo(dom.e('div', 'ps-scrollbar-y-rail'), element);
i.scrollbarY = dom.appendTo(dom.e('div', 'ps-scrollbar-y'), i.scrollbarYRail);
i.scrollbarY.setAttribute('tabindex', 0);
i.event.bind(i.scrollbarY, 'focus', focus);
i.event.bind(i.scrollbarY, 'blur', blur);
i.scrollbarYActive = null;
i.scrollbarYHeight = null;
i.scrollbarYTop = null;
i.scrollbarYRight = _.toInt(dom.css(i.scrollbarYRail, 'right'));
i.isScrollbarYUsingRight = i.scrollbarYRight === i.scrollbarYRight; // !isNaN
i.scrollbarYLeft = i.isScrollbarYUsingRight ? null : _.toInt(dom.css(i.scrollbarYRail, 'left'));
i.scrollbarYOuterWidth = i.isRtl ? _.outerWidth(i.scrollbarY) : null;
i.railBorderYWidth = _.toInt(dom.css(i.scrollbarYRail, 'borderTopWidth')) + _.toInt(dom.css(i.scrollbarYRail, 'borderBottomWidth'));
dom.css(i.scrollbarYRail, 'display', 'block');
i.railYMarginHeight = _.toInt(dom.css(i.scrollbarYRail, 'marginTop')) + _.toInt(dom.css(i.scrollbarYRail, 'marginBottom'));
dom.css(i.scrollbarYRail, 'display', '');
i.railYHeight = null;
i.railYRatio = null;
}
function getId(element) {
return element.getAttribute('data-ps-id');
}
function setId(element, id) {
element.setAttribute('data-ps-id', id);
}
function removeId(element) {
element.removeAttribute('data-ps-id');
}
exports.add = function (element) {
var newId = guid();
setId(element, newId);
instances[newId] = new Instance(element);
return instances[newId];
};
exports.remove = function (element) {
delete instances[getId(element)];
removeId(element);
};
exports.get = function (element) {
return instances[getId(element)];
};

View file

@ -0,0 +1,126 @@
'use strict';
var _ = require('../lib/helper');
var cls = require('../lib/class');
var dom = require('../lib/dom');
var instances = require('./instances');
var updateScroll = require('./update-scroll');
function getThumbSize(i, thumbSize) {
if (i.settings.minScrollbarLength) {
thumbSize = Math.max(thumbSize, i.settings.minScrollbarLength);
}
if (i.settings.maxScrollbarLength) {
thumbSize = Math.min(thumbSize, i.settings.maxScrollbarLength);
}
return thumbSize;
}
function updateCss(element, i) {
var xRailOffset = {width: i.railXWidth};
if (i.isRtl) {
xRailOffset.left = i.negativeScrollAdjustment + element.scrollLeft + i.containerWidth - i.contentWidth;
} else {
xRailOffset.left = element.scrollLeft;
}
if (i.isScrollbarXUsingBottom) {
xRailOffset.bottom = i.scrollbarXBottom - element.scrollTop;
} else {
xRailOffset.top = i.scrollbarXTop + element.scrollTop;
}
dom.css(i.scrollbarXRail, xRailOffset);
var yRailOffset = {top: element.scrollTop, height: i.railYHeight};
if (i.isScrollbarYUsingRight) {
if (i.isRtl) {
yRailOffset.right = i.contentWidth - (i.negativeScrollAdjustment + element.scrollLeft) - i.scrollbarYRight - i.scrollbarYOuterWidth;
} else {
yRailOffset.right = i.scrollbarYRight - element.scrollLeft;
}
} else {
if (i.isRtl) {
yRailOffset.left = i.negativeScrollAdjustment + element.scrollLeft + i.containerWidth * 2 - i.contentWidth - i.scrollbarYLeft - i.scrollbarYOuterWidth;
} else {
yRailOffset.left = i.scrollbarYLeft + element.scrollLeft;
}
}
dom.css(i.scrollbarYRail, yRailOffset);
dom.css(i.scrollbarX, {left: i.scrollbarXLeft, width: i.scrollbarXWidth - i.railBorderXWidth});
dom.css(i.scrollbarY, {top: i.scrollbarYTop, height: i.scrollbarYHeight - i.railBorderYWidth});
}
module.exports = function (element) {
var i = instances.get(element);
i.containerWidth = element.clientWidth;
i.containerHeight = element.clientHeight;
i.contentWidth = element.scrollWidth;
i.contentHeight = element.scrollHeight;
var existingRails;
if (!element.contains(i.scrollbarXRail)) {
existingRails = dom.queryChildren(element, '.ps-scrollbar-x-rail');
if (existingRails.length > 0) {
existingRails.forEach(function (rail) {
dom.remove(rail);
});
}
dom.appendTo(i.scrollbarXRail, element);
}
if (!element.contains(i.scrollbarYRail)) {
existingRails = dom.queryChildren(element, '.ps-scrollbar-y-rail');
if (existingRails.length > 0) {
existingRails.forEach(function (rail) {
dom.remove(rail);
});
}
dom.appendTo(i.scrollbarYRail, element);
}
if (!i.settings.suppressScrollX && i.containerWidth + i.settings.scrollXMarginOffset < i.contentWidth) {
i.scrollbarXActive = true;
i.railXWidth = i.containerWidth - i.railXMarginWidth;
i.railXRatio = i.containerWidth / i.railXWidth;
i.scrollbarXWidth = getThumbSize(i, _.toInt(i.railXWidth * i.containerWidth / i.contentWidth));
i.scrollbarXLeft = _.toInt((i.negativeScrollAdjustment + element.scrollLeft) * (i.railXWidth - i.scrollbarXWidth) / (i.contentWidth - i.containerWidth));
} else {
i.scrollbarXActive = false;
}
if (!i.settings.suppressScrollY && i.containerHeight + i.settings.scrollYMarginOffset < i.contentHeight) {
i.scrollbarYActive = true;
i.railYHeight = i.containerHeight - i.railYMarginHeight;
i.railYRatio = i.containerHeight / i.railYHeight;
i.scrollbarYHeight = getThumbSize(i, _.toInt(i.railYHeight * i.containerHeight / i.contentHeight));
i.scrollbarYTop = _.toInt(element.scrollTop * (i.railYHeight - i.scrollbarYHeight) / (i.contentHeight - i.containerHeight));
} else {
i.scrollbarYActive = false;
}
if (i.scrollbarXLeft >= i.railXWidth - i.scrollbarXWidth) {
i.scrollbarXLeft = i.railXWidth - i.scrollbarXWidth;
}
if (i.scrollbarYTop >= i.railYHeight - i.scrollbarYHeight) {
i.scrollbarYTop = i.railYHeight - i.scrollbarYHeight;
}
updateCss(element, i);
if (i.scrollbarXActive) {
cls.add(element, 'ps-active-x');
} else {
cls.remove(element, 'ps-active-x');
i.scrollbarXWidth = 0;
i.scrollbarXLeft = 0;
updateScroll(element, 'left', 0);
}
if (i.scrollbarYActive) {
cls.add(element, 'ps-active-y');
} else {
cls.remove(element, 'ps-active-y');
i.scrollbarYHeight = 0;
i.scrollbarYTop = 0;
updateScroll(element, 'top', 0);
}
};

View file

@ -0,0 +1,97 @@
'use strict';
var instances = require('./instances');
var lastTop;
var lastLeft;
var createDOMEvent = function (name) {
var event = document.createEvent("Event");
event.initEvent(name, true, true);
return event;
};
module.exports = function (element, axis, value) {
if (typeof element === 'undefined') {
throw 'You must provide an element to the update-scroll function';
}
if (typeof axis === 'undefined') {
throw 'You must provide an axis to the update-scroll function';
}
if (typeof value === 'undefined') {
throw 'You must provide a value to the update-scroll function';
}
if (axis === 'top' && value <= 0) {
element.scrollTop = value = 0; // don't allow negative scroll
element.dispatchEvent(createDOMEvent('ps-y-reach-start'));
}
if (axis === 'left' && value <= 0) {
element.scrollLeft = value = 0; // don't allow negative scroll
element.dispatchEvent(createDOMEvent('ps-x-reach-start'));
}
var i = instances.get(element);
if (axis === 'top' && value >= i.contentHeight - i.containerHeight) {
// don't allow scroll past container
value = i.contentHeight - i.containerHeight;
if (value - element.scrollTop <= 1) {
// mitigates rounding errors on non-subpixel scroll values
value = element.scrollTop;
} else {
element.scrollTop = value;
}
element.dispatchEvent(createDOMEvent('ps-y-reach-end'));
}
if (axis === 'left' && value >= i.contentWidth - i.containerWidth) {
// don't allow scroll past container
value = i.contentWidth - i.containerWidth;
if (value - element.scrollLeft <= 1) {
// mitigates rounding errors on non-subpixel scroll values
value = element.scrollLeft;
} else {
element.scrollLeft = value;
}
element.dispatchEvent(createDOMEvent('ps-x-reach-end'));
}
if (!lastTop) {
lastTop = element.scrollTop;
}
if (!lastLeft) {
lastLeft = element.scrollLeft;
}
if (axis === 'top' && value < lastTop) {
element.dispatchEvent(createDOMEvent('ps-scroll-up'));
}
if (axis === 'top' && value > lastTop) {
element.dispatchEvent(createDOMEvent('ps-scroll-down'));
}
if (axis === 'left' && value < lastLeft) {
element.dispatchEvent(createDOMEvent('ps-scroll-left'));
}
if (axis === 'left' && value > lastLeft) {
element.dispatchEvent(createDOMEvent('ps-scroll-right'));
}
if (axis === 'top') {
element.scrollTop = lastTop = value;
element.dispatchEvent(createDOMEvent('ps-scroll-y'));
}
if (axis === 'left') {
element.scrollLeft = lastLeft = value;
element.dispatchEvent(createDOMEvent('ps-scroll-x'));
}
};

View file

@ -0,0 +1,37 @@
'use strict';
var _ = require('../lib/helper');
var dom = require('../lib/dom');
var instances = require('./instances');
var updateGeometry = require('./update-geometry');
var updateScroll = require('./update-scroll');
module.exports = function (element) {
var i = instances.get(element);
if (!i) {
return;
}
// Recalcuate negative scrollLeft adjustment
i.negativeScrollAdjustment = i.isNegativeScroll ? element.scrollWidth - element.clientWidth : 0;
// Recalculate rail margins
dom.css(i.scrollbarXRail, 'display', 'block');
dom.css(i.scrollbarYRail, 'display', 'block');
i.railXMarginWidth = _.toInt(dom.css(i.scrollbarXRail, 'marginLeft')) + _.toInt(dom.css(i.scrollbarXRail, 'marginRight'));
i.railYMarginHeight = _.toInt(dom.css(i.scrollbarYRail, 'marginTop')) + _.toInt(dom.css(i.scrollbarYRail, 'marginBottom'));
// Hide scrollbars not to affect scrollWidth and scrollHeight
dom.css(i.scrollbarXRail, 'display', 'none');
dom.css(i.scrollbarYRail, 'display', 'none');
updateGeometry(element);
// Update top/left scroll to trigger events
updateScroll(element, 'top', element.scrollTop);
updateScroll(element, 'left', element.scrollLeft);
dom.css(i.scrollbarXRail, 'display', '');
dom.css(i.scrollbarYRail, 'display', '');
};

View file

@ -6,7 +6,7 @@
<link rel="stylesheet" href="view/asset/jquery-colorbox/example5/colorbox.css?v={{$smarty.const.FRIENDICA_VERSION}}" type="text/css" media="screen" />
<link rel="stylesheet" href="view/asset/jgrowl/jquery.jgrowl.min.css?v={{$smarty.const.FRIENDICA_VERSION}}" type="text/css" media="screen" />
<link rel="stylesheet" href="view/asset/jquery-datetimepicker/build/jquery.datetimepicker.min.css?v={{$smarty.const.FRIENDICA_VERSION}}" type="text/css" media="screen" />
<link rel="stylesheet" href="view/asset/perfect-scrollbar/css/perfect-scrollbar.min.css?v={{$smarty.const.FRIENDICA_VERSION}}" type="text/css" media="screen" />
<link rel="stylesheet" href="view/js/perfect-scrollbar/css/perfect-scrollbar.min.css?v={{$smarty.const.FRIENDICA_VERSION}}" type="text/css" media="screen" />
{{foreach $stylesheets as $stylesheetUrl}}
<link rel="stylesheet" href="{{$stylesheetUrl}}" type="text/css" media="screen" />
@ -40,7 +40,7 @@
<script type="text/javascript" src="view/asset/jquery-colorbox/jquery.colorbox-min.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="view/asset/jgrowl/jquery.jgrowl.min.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="view/asset/jquery-datetimepicker/build/jquery.datetimepicker.full.min.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="view/asset/perfect-scrollbar/js/perfect-scrollbar.jquery.min.js?v={{$smarty.const.FRIENDICA_VERSION}}" ></script>
<script type="text/javascript" src="view/js/perfect-scrollbar/js/perfect-scrollbar.jquery.min.js?v={{$smarty.const.FRIENDICA_VERSION}}" ></script>
<script type="text/javascript" src="view/asset/imagesloaded/imagesloaded.pkgd.min.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="view/asset/base64/base64.min.js?v={{$smarty.const.FRIENDICA_VERSION}}" ></script>
<script type="text/javascript" src="view/asset/dompurify/dist/purify.min.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>

View file

@ -11,7 +11,7 @@
<link rel="stylesheet" href="view/asset/jquery-colorbox/example5/colorbox.css?v={{$smarty.const.FRIENDICA_VERSION}}" type="text/css" media="screen" />
<link rel="stylesheet" href="view/asset/jgrowl/jquery.jgrowl.min.css?v={{$smarty.const.FRIENDICA_VERSION}}" type="text/css" media="screen" />
<link rel="stylesheet" href="view/asset/jquery-datetimepicker/build/jquery.datetimepicker.min.css?v={{$smarty.const.FRIENDICA_VERSION}}" type="text/css" media="screen" />
<link rel="stylesheet" href="view/asset/perfect-scrollbar/css/perfect-scrollbar.min.css?v={{$smarty.const.FRIENDICA_VERSION}}" type="text/css" media="screen" />
<link rel="stylesheet" href="view/js/perfect-scrollbar/css/perfect-scrollbar.min.css?v={{$smarty.const.FRIENDICA_VERSION}}" type="text/css" media="screen" />
<link rel="stylesheet" href="view/theme/frio/frameworks/bootstrap/css/bootstrap.min.css?v={{$smarty.const.FRIENDICA_VERSION}}" type="text/css" media="screen"/>
<link rel="stylesheet" href="view/theme/frio/frameworks/bootstrap/css/bootstrap-theme.min.css?v={{$smarty.const.FRIENDICA_VERSION}}" type="text/css" media="screen"/>
@ -61,7 +61,7 @@
<script type="text/javascript" src="view/asset/jquery-colorbox/jquery.colorbox-min.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="view/asset/jgrowl/jquery.jgrowl.min.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="view/asset/jquery-datetimepicker/build/jquery.datetimepicker.full.min.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="view/asset/perfect-scrollbar/js/perfect-scrollbar.jquery.min.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="view/js/perfect-scrollbar/js/perfect-scrollbar.jquery.min.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="view/asset/imagesloaded/imagesloaded.pkgd.min.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="view/asset/base64/base64.min.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="view/asset/dompurify/dist/purify.min.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>