Merge remote-tracking branch 'upstream/develop' into issue-8550
This commit is contained in:
commit
7de6e60328
1
boot.php
1
boot.php
|
@ -33,7 +33,6 @@ use Friendica\Database\DBA;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Notify;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Util\BasePath;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
78
database.sql
78
database.sql
|
@ -1,6 +1,6 @@
|
|||
-- ------------------------------------------
|
||||
-- Friendica 2020.06-dev (Red Hot Poker)
|
||||
-- DB_UPDATE_VERSION 1341
|
||||
-- DB_UPDATE_VERSION 1345
|
||||
-- ------------------------------------------
|
||||
|
||||
|
||||
|
@ -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
|
||||
--
|
||||
|
@ -1187,6 +1170,24 @@ CREATE TABLE IF NOT EXISTS `tag` (
|
|||
INDEX `url` (`url`)
|
||||
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='tags and mentions';
|
||||
|
||||
--
|
||||
-- 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
|
||||
--
|
||||
|
@ -1539,22 +1540,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 +1562,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
|
||||
--
|
||||
|
|
|
@ -41,7 +41,6 @@ use Friendica\Model\Item;
|
|||
use Friendica\Model\Mail;
|
||||
use Friendica\Model\Notify;
|
||||
use Friendica\Model\Photo;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Model\UserItem;
|
||||
use Friendica\Network\FKOAuth1;
|
||||
|
@ -1539,31 +1538,24 @@ function api_search($type)
|
|||
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
|
||||
if (preg_match('/^#(\w+)$/', $searchTerm, $matches) === 1 && isset($matches[1])) {
|
||||
$searchTerm = $matches[1];
|
||||
$condition = ["`oid` > ?
|
||||
AND (`uid` = 0 OR (`uid` = ? AND NOT `global`))
|
||||
AND `otype` = ? AND `type` = ? AND `term` = ?",
|
||||
$since_id, local_user(), Term::OBJECT_TYPE_POST, Term::HASHTAG, $searchTerm];
|
||||
if ($max_id > 0) {
|
||||
$condition[0] .= ' AND `oid` <= ?';
|
||||
$condition[] = $max_id;
|
||||
$condition = ["`iid` > ? AND `name` = ? AND (NOT `private` OR (`private` AND `uid` = ?))", $since_id, $searchTerm, local_user()];
|
||||
$tags = DBA::select('tag-search-view', ['uri-id'], $condition);
|
||||
$uriids = [];
|
||||
while ($tag = DBA::fetch($tags)) {
|
||||
$uriids[] = $tag['uri-id'];
|
||||
}
|
||||
$terms = DBA::select('term', ['oid'], $condition, []);
|
||||
$itemIds = [];
|
||||
while ($term = DBA::fetch($terms)) {
|
||||
$itemIds[] = $term['oid'];
|
||||
}
|
||||
DBA::close($terms);
|
||||
DBA::close($tags);
|
||||
|
||||
if (empty($itemIds)) {
|
||||
if (empty($uriids)) {
|
||||
return api_format_data('statuses', $type, $data);
|
||||
}
|
||||
|
||||
$preCondition = ['`id` IN (' . implode(', ', $itemIds) . ')'];
|
||||
$condition = ['uri-id' => $uriids];
|
||||
if ($exclude_replies) {
|
||||
$preCondition[] = '`id` = `parent`';
|
||||
$condition['gravity'] = GRAVITY_PARENT;
|
||||
}
|
||||
|
||||
$condition = [implode(' AND ', $preCondition)];
|
||||
$params['group_by'] = ['uri-id'];
|
||||
} else {
|
||||
$condition = ["`id` > ?
|
||||
" . ($exclude_replies ? " AND `id` = `parent` " : ' ') . "
|
||||
|
@ -2041,7 +2033,7 @@ function api_statuses_repeat($type)
|
|||
|
||||
Logger::log('API: api_statuses_repeat: '.$id);
|
||||
|
||||
$fields = ['uri-id', 'body', 'title', 'attach', 'tag', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink'];
|
||||
$fields = ['uri-id', 'body', 'title', 'attach', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink'];
|
||||
$item = Item::selectFirst($fields, ['id' => $id, 'private' => [Item::PUBLIC, Item::UNLISTED]]);
|
||||
|
||||
if (DBA::isResult($item) && $item['body'] != "") {
|
||||
|
@ -2059,7 +2051,6 @@ function api_statuses_repeat($type)
|
|||
$post .= "[/share]";
|
||||
}
|
||||
$_REQUEST['body'] = $post;
|
||||
$_REQUEST['tag'] = $item['tag'];
|
||||
$_REQUEST['attach'] = $item['attach'];
|
||||
$_REQUEST['profile_uid'] = api_user();
|
||||
$_REQUEST['api_source'] = true;
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
use Friendica\App;
|
||||
use Friendica\Content\ContactSelector;
|
||||
use Friendica\Content\Feature;
|
||||
use Friendica\Content\Pager;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Logger;
|
||||
|
@ -34,7 +33,7 @@ use Friendica\DI;
|
|||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Object\Post;
|
||||
use Friendica\Object\Thread;
|
||||
use Friendica\Protocol\Activity;
|
||||
|
@ -527,7 +526,7 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
|
|||
$profile_name = $item['author-link'];
|
||||
}
|
||||
|
||||
$tags = Term::populateTagsFromItem($item);
|
||||
$tags = Tag::populateFromItem($item);
|
||||
|
||||
$author = ['uid' => 0, 'id' => $item['author-id'],
|
||||
'network' => $item['author-network'], 'url' => $item['author-link']];
|
||||
|
|
|
@ -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 = '';
|
||||
|
@ -452,17 +464,19 @@ function notification($params)
|
|||
|
||||
if ($show_in_notification_page) {
|
||||
$notification = DI::notify()->insert([
|
||||
'name' => $params['source_name'] ?? '',
|
||||
'name_cache' => substr(strip_tags(BBCode::convert($params['source_name'] ?? '')), 0, 255),
|
||||
'url' => $params['source_link'] ?? '',
|
||||
'photo' => $params['source_photo'] ?? '',
|
||||
'link' => $itemlink ?? '',
|
||||
'uid' => $params['uid'] ?? 0,
|
||||
'iid' => $item_id ?? 0,
|
||||
'parent' => $parent_id ?? 0,
|
||||
'type' => $params['type'] ?? '',
|
||||
'verb' => $params['verb'] ?? '',
|
||||
'otype' => $params['otype'] ?? '',
|
||||
'name' => $params['source_name'] ?? '',
|
||||
'name_cache' => substr(strip_tags(BBCode::convert($params['source_name'] ?? '')), 0, 255),
|
||||
'url' => $params['source_link'] ?? '',
|
||||
'photo' => $params['source_photo'] ?? '',
|
||||
'link' => $itemlink ?? '',
|
||||
'uid' => $params['uid'] ?? 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'] ?? '',
|
||||
]);
|
||||
|
||||
$notification->msg = Renderer::replaceMacros($epreamble, ['$itemlink' => $notification->link]);
|
||||
|
@ -486,8 +500,9 @@ function notification($params)
|
|||
if (!DBA::exists('notify-threads', ['master-parent-item' => $params['parent'], 'receiver-uid' => $params['uid']])) {
|
||||
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'],
|
||||
'receiver-uid' => $params['uid'], 'parent-item' => 0];
|
||||
$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);
|
||||
|
||||
$additional_mail_header .= "Message-ID: <${id_for_parent}>\n";
|
||||
|
@ -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', 'tag', '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];
|
||||
|
|
|
@ -141,28 +141,6 @@ function query_page_info($url, $photo = "", $keywords = false, $keyword_blacklis
|
|||
return $data;
|
||||
}
|
||||
|
||||
function add_page_keywords($url, $photo = "", $keywords = false, $keyword_blacklist = "")
|
||||
{
|
||||
$data = query_page_info($url, $photo, $keywords, $keyword_blacklist);
|
||||
if (empty($data["keywords"]) || !is_array($data["keywords"])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$tags = "";
|
||||
foreach ($data["keywords"] as $keyword) {
|
||||
$hashtag = str_replace([" ", "+", "/", ".", "#", "'"],
|
||||
["", "", "", "", "", ""], $keyword);
|
||||
|
||||
if ($tags != "") {
|
||||
$tags .= ", ";
|
||||
}
|
||||
|
||||
$tags .= "#[url=" . DI::baseUrl() . "/search?tag=" . $hashtag . "]" . $hashtag . "[/url]";
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
function get_page_keywords($url, $photo = "", $keywords = false, $keyword_blacklist = "")
|
||||
{
|
||||
$data = query_page_info($url, $photo, $keywords, $keyword_blacklist);
|
||||
|
|
75
mod/item.php
75
mod/item.php
|
@ -29,7 +29,6 @@
|
|||
*/
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Content\Pager;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Logger;
|
||||
|
@ -47,7 +46,6 @@ use Friendica\Model\Item;
|
|||
use Friendica\Model\Notify\Type;
|
||||
use Friendica\Model\Photo;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Object\EMail\ItemCCEMail;
|
||||
use Friendica\Protocol\Activity;
|
||||
|
@ -102,7 +100,7 @@ function item_post(App $a) {
|
|||
$toplevel_item_id = intval($_REQUEST['parent'] ?? 0);
|
||||
$thr_parent_uri = trim($_REQUEST['parent_uri'] ?? '');
|
||||
|
||||
$thread_parent_id = 0;
|
||||
$thread_parent_uriid = 0;
|
||||
$thread_parent_contact = null;
|
||||
|
||||
$toplevel_item = null;
|
||||
|
@ -124,7 +122,7 @@ function item_post(App $a) {
|
|||
// if this isn't the top-level parent of the conversation, find it
|
||||
if (DBA::isResult($toplevel_item)) {
|
||||
// The URI and the contact is taken from the direct parent which needn't to be the top parent
|
||||
$thread_parent_id = $toplevel_item['id'];
|
||||
$thread_parent_uriid = $toplevel_item['uri-id'];
|
||||
$thr_parent_uri = $toplevel_item['uri'];
|
||||
$thread_parent_contact = Contact::getDetailsByURL($toplevel_item["author-link"]);
|
||||
|
||||
|
@ -377,13 +375,12 @@ function item_post(App $a) {
|
|||
}
|
||||
|
||||
// Look for any tags and linkify them
|
||||
$str_tags = '';
|
||||
$inform = '';
|
||||
|
||||
$tags = BBCode::getTags($body);
|
||||
|
||||
if ($thread_parent_id && !\Friendica\Content\Feature::isEnabled($uid, 'explicit_mentions')) {
|
||||
$tags = item_add_implicit_mentions($tags, $thread_parent_contact, $thread_parent_id);
|
||||
if ($thread_parent_uriid && !\Friendica\Content\Feature::isEnabled($uid, 'explicit_mentions')) {
|
||||
$tags = item_add_implicit_mentions($tags, $thread_parent_contact, $thread_parent_uriid);
|
||||
}
|
||||
|
||||
$tagged = [];
|
||||
|
@ -396,7 +393,7 @@ function item_post(App $a) {
|
|||
foreach ($tags as $tag) {
|
||||
$tag_type = substr($tag, 0, 1);
|
||||
|
||||
if ($tag_type == Term::TAG_CHARACTER[Term::HASHTAG]) {
|
||||
if ($tag_type == Tag::TAG_CHARACTER[Tag::HASHTAG]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -416,14 +413,14 @@ function item_post(App $a) {
|
|||
continue;
|
||||
}
|
||||
|
||||
$success = handle_tag($body, $inform, $str_tags, local_user() ? local_user() : $profile_uid, $tag, $network);
|
||||
$success = handle_tag($body, $inform, local_user() ? local_user() : $profile_uid, $tag, $network);
|
||||
if ($success['replaced']) {
|
||||
$tagged[] = $tag;
|
||||
}
|
||||
// When the forum is private or the forum is addressed with a "!" make the post private
|
||||
if (is_array($success['contact']) && (!empty($success['contact']['prv']) || ($tag_type == Term::TAG_CHARACTER[Term::EXCLUSIVE_MENTION]))) {
|
||||
if (is_array($success['contact']) && (!empty($success['contact']['prv']) || ($tag_type == Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION]))) {
|
||||
$private_forum = $success['contact']['prv'];
|
||||
$only_to_forum = ($tag_type == Term::TAG_CHARACTER[Term::EXCLUSIVE_MENTION]);
|
||||
$only_to_forum = ($tag_type == Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION]);
|
||||
$private_id = $success['contact']['id'];
|
||||
$forum_contact = $success['contact'];
|
||||
} elseif (is_array($success['contact']) && !empty($success['contact']['forum']) &&
|
||||
|
@ -600,7 +597,6 @@ function item_post(App $a) {
|
|||
$datarray['app'] = $app;
|
||||
$datarray['location'] = $location;
|
||||
$datarray['coord'] = $coord;
|
||||
$datarray['tag'] = $str_tags;
|
||||
$datarray['file'] = $categories;
|
||||
$datarray['inform'] = $inform;
|
||||
$datarray['verb'] = $verb;
|
||||
|
@ -697,7 +693,6 @@ function item_post(App $a) {
|
|||
$fields = [
|
||||
'title' => $datarray['title'],
|
||||
'body' => $datarray['body'],
|
||||
'tag' => $datarray['tag'],
|
||||
'attach' => $datarray['attach'],
|
||||
'file' => $datarray['file'],
|
||||
'rendered-html' => $datarray['rendered-html'],
|
||||
|
@ -892,7 +887,6 @@ function item_content(App $a)
|
|||
* @param App $a
|
||||
* @param string $body the text to replace the tag in
|
||||
* @param string $inform a comma-seperated string containing everybody to inform
|
||||
* @param string $str_tags string to add the tag to
|
||||
* @param integer $profile_uid
|
||||
* @param string $tag the tag to replace
|
||||
* @param string $network The network of the post
|
||||
|
@ -901,24 +895,15 @@ function item_content(App $a)
|
|||
* @throws ImagickException
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
function handle_tag(&$body, &$inform, &$str_tags, $profile_uid, $tag, $network = "")
|
||||
function handle_tag(&$body, &$inform, $profile_uid, $tag, $network = "")
|
||||
{
|
||||
$replaced = false;
|
||||
$r = null;
|
||||
|
||||
//is it a person tag?
|
||||
if (Term::isType($tag, Term::MENTION, Term::IMPLICIT_MENTION, Term::EXCLUSIVE_MENTION)) {
|
||||
if (Tag::isType($tag, Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION)) {
|
||||
$tag_type = substr($tag, 0, 1);
|
||||
//is it already replaced?
|
||||
if (strpos($tag, '[url=')) {
|
||||
//append tag to str_tags
|
||||
if (!stristr($str_tags, $tag)) {
|
||||
if (strlen($str_tags)) {
|
||||
$str_tags .= ',';
|
||||
}
|
||||
$str_tags .= $tag;
|
||||
}
|
||||
|
||||
// Checking for the alias that is used for OStatus
|
||||
$pattern = "/[@!]\[url\=(.*?)\](.*?)\[\/url\]/ism";
|
||||
if (preg_match($pattern, $tag, $matches)) {
|
||||
|
@ -926,14 +911,6 @@ function handle_tag(&$body, &$inform, &$str_tags, $profile_uid, $tag, $network =
|
|||
|
||||
if ($data["alias"] != "") {
|
||||
$newtag = '@[url=' . $data["alias"] . ']' . $data["nick"] . '[/url]';
|
||||
|
||||
if (!stripos($str_tags, '[url=' . $data["alias"] . ']')) {
|
||||
if (strlen($str_tags)) {
|
||||
$str_tags .= ',';
|
||||
}
|
||||
|
||||
$str_tags .= $newtag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1007,7 +984,6 @@ function handle_tag(&$body, &$inform, &$str_tags, $profile_uid, $tag, $network =
|
|||
}
|
||||
|
||||
$profile = $contact["url"];
|
||||
$alias = $contact["alias"];
|
||||
$newname = ($contact["name"] ?? '') ?: $contact["nick"];
|
||||
}
|
||||
|
||||
|
@ -1018,39 +994,18 @@ function handle_tag(&$body, &$inform, &$str_tags, $profile_uid, $tag, $network =
|
|||
$profile = str_replace(',', '%2c', $profile);
|
||||
$newtag = $tag_type.'[url=' . $profile . ']' . $newname . '[/url]';
|
||||
$body = str_replace($tag_type . $name, $newtag, $body);
|
||||
// append tag to str_tags
|
||||
if (!stristr($str_tags, $newtag)) {
|
||||
if (strlen($str_tags)) {
|
||||
$str_tags .= ',';
|
||||
}
|
||||
$str_tags .= $newtag;
|
||||
}
|
||||
|
||||
/*
|
||||
* Status.Net seems to require the numeric ID URL in a mention if the person isn't
|
||||
* subscribed to you. But the nickname URL is OK if they are. Grrr. We'll tag both.
|
||||
*/
|
||||
if (!empty($alias)) {
|
||||
$newtag = '@[url=' . $alias . ']' . $newname . '[/url]';
|
||||
if (!stripos($str_tags, '[url=' . $alias . ']')) {
|
||||
if (strlen($str_tags)) {
|
||||
$str_tags .= ',';
|
||||
}
|
||||
$str_tags .= $newtag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ['replaced' => $replaced, 'contact' => $contact];
|
||||
}
|
||||
|
||||
function item_add_implicit_mentions(array $tags, array $thread_parent_contact, $thread_parent_id)
|
||||
function item_add_implicit_mentions(array $tags, array $thread_parent_contact, $thread_parent_uriid)
|
||||
{
|
||||
if (DI::config()->get('system', 'disable_implicit_mentions')) {
|
||||
// Add a tag if the parent contact is from ActivityPub or OStatus (This will notify them)
|
||||
if (in_array($thread_parent_contact['network'], [Protocol::OSTATUS, Protocol::ACTIVITYPUB])) {
|
||||
$contact = Term::TAG_CHARACTER[Term::MENTION] . '[url=' . $thread_parent_contact['url'] . ']' . $thread_parent_contact['nick'] . '[/url]';
|
||||
$contact = Tag::TAG_CHARACTER[Tag::MENTION] . '[url=' . $thread_parent_contact['url'] . ']' . $thread_parent_contact['nick'] . '[/url]';
|
||||
if (!stripos(implode($tags), '[url=' . $thread_parent_contact['url'] . ']')) {
|
||||
$tags[] = $contact;
|
||||
}
|
||||
|
@ -1060,15 +1015,15 @@ function item_add_implicit_mentions(array $tags, array $thread_parent_contact, $
|
|||
$thread_parent_contact['url'] => $thread_parent_contact['nick']
|
||||
];
|
||||
|
||||
$parent_terms = Term::tagArrayFromItemId($thread_parent_id, [Term::MENTION, Term::IMPLICIT_MENTION]);
|
||||
$parent_terms = Tag::getByURIId($thread_parent_uriid, [Tag::MENTION, Tag::IMPLICIT_MENTION]);
|
||||
|
||||
foreach ($parent_terms as $parent_term) {
|
||||
$implicit_mentions[$parent_term['url']] = $parent_term['term'];
|
||||
$implicit_mentions[$parent_term['url']] = $parent_term['name'];
|
||||
}
|
||||
|
||||
foreach ($implicit_mentions as $url => $label) {
|
||||
if ($url != \Friendica\Model\Profile::getMyURL() && !stripos(implode($tags), '[url=' . $url . ']')) {
|
||||
$tags[] = Term::TAG_CHARACTER[Term::IMPLICIT_MENTION] . '[url=' . $url . ']' . $label . '[/url]';
|
||||
$tags[] = Tag::TAG_CHARACTER[Tag::IMPLICIT_MENTION] . '[url=' . $url . ']' . $label . '[/url]';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -787,14 +787,13 @@ function networkThreadedView(App $a, $update, $parent)
|
|||
}
|
||||
|
||||
$items = DBA::p("SELECT `item`.`parent-uri` AS `uri`, 0 AS `item_id`, `item`.$ordering AS `order_date`, `author`.`url` AS `author-link` FROM `item`
|
||||
STRAIGHT_JOIN (SELECT `oid` FROM `term` WHERE `term` IN
|
||||
(SELECT SUBSTR(`term`, 2) FROM `search` WHERE `uid` = ? AND `term` LIKE '#%') AND `otype` = ? AND `type` = ? AND `uid` = 0) AS `term`
|
||||
ON `item`.`id` = `term`.`oid`
|
||||
STRAIGHT_JOIN (SELECT `uri-id` FROM `tag-search-view` WHERE `name` IN
|
||||
(SELECT SUBSTR(`term`, 2) FROM `search` WHERE `uid` = ? AND `term` LIKE '#%') AND `uid` = 0) AS `tag-search`
|
||||
ON `item`.`uri-id` = `tag-search`.`uri-id`
|
||||
STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `item`.`author-id`
|
||||
WHERE `item`.`uid` = 0 AND `item`.$ordering < ? AND `item`.$ordering > ? AND `item`.`gravity` = ?
|
||||
AND NOT `author`.`hidden` AND NOT `author`.`blocked`" . $sql_tag_nets,
|
||||
local_user(), Term::OBJECT_TYPE_POST, Term::HASHTAG,
|
||||
$top_limit, $bottom_limit, GRAVITY_PARENT);
|
||||
local_user(), $top_limit, $bottom_limit, GRAVITY_PARENT);
|
||||
|
||||
$data = DBA::toArray($items);
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ use Friendica\Database\DBA;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\XML;
|
||||
|
@ -170,50 +169,8 @@ EOT;
|
|||
Item::update(['visible' => true], ['id' => $item['id']]);
|
||||
}
|
||||
|
||||
$term_objtype = ($item['resource-id'] ? Term::OBJECT_TYPE_PHOTO : Term::OBJECT_TYPE_POST);
|
||||
|
||||
Tag::store($item['uri-id'], Tag::HASHTAG, $term);
|
||||
|
||||
$t = q("SELECT count(tid) as tcount FROM term WHERE oid=%d AND term='%s'",
|
||||
intval($item['id']),
|
||||
DBA::escape($term)
|
||||
);
|
||||
|
||||
if (!$blocktags && $t[0]['tcount'] == 0) {
|
||||
q("INSERT INTO term (oid, otype, type, term, url, uid) VALUE (%d, %d, %d, '%s', '%s', %d)",
|
||||
intval($item['id']),
|
||||
$term_objtype,
|
||||
Term::HASHTAG,
|
||||
DBA::escape($term),
|
||||
'',
|
||||
intval($owner_uid)
|
||||
);
|
||||
}
|
||||
|
||||
// if the original post is on this site, update it.
|
||||
$original_item = Item::selectFirst(['tag', 'id', 'uid'], ['origin' => true, 'uri' => $item['uri']]);
|
||||
if (DBA::isResult($original_item)) {
|
||||
$x = q("SELECT `blocktags` FROM `user` WHERE `uid`=%d LIMIT 1",
|
||||
intval($original_item['uid'])
|
||||
);
|
||||
$t = q("SELECT COUNT(`tid`) AS `tcount` FROM `term` WHERE `oid`=%d AND `term`='%s'",
|
||||
intval($original_item['id']),
|
||||
DBA::escape($term)
|
||||
);
|
||||
|
||||
if (DBA::isResult($x) && !$x[0]['blocktags'] && $t[0]['tcount'] == 0){
|
||||
q("INSERT INTO term (`oid`, `otype`, `type`, `term`, `url`, `uid`) VALUE (%d, %d, %d, '%s', '%s', %d)",
|
||||
intval($original_item['id']),
|
||||
$term_objtype,
|
||||
Term::HASHTAG,
|
||||
DBA::escape($term),
|
||||
'',
|
||||
intval($owner_uid)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$arr['id'] = $post_id;
|
||||
|
||||
Hook::callAll('post_local_end', $arr);
|
||||
|
|
|
@ -25,7 +25,6 @@ use Friendica\Database\DBA;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
function tagrm_post(App $a)
|
||||
|
@ -58,35 +57,24 @@ function tagrm_post(App $a)
|
|||
* @param $tags array
|
||||
* @throws Exception
|
||||
*/
|
||||
function update_tags($item_id, $tags){
|
||||
if (empty($item_id) || empty($tags)){
|
||||
function update_tags($item_id, $tags)
|
||||
{
|
||||
if (empty($item_id) || empty($tags)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$item = Item::selectFirst(['tag', 'uri-id'], ['id' => $item_id, 'uid' => local_user()]);
|
||||
$item = Item::selectFirst(['uri-id'], ['id' => $item_id, 'uid' => local_user()]);
|
||||
if (!DBA::isResult($item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$old_tags = explode(',', $item['tag']);
|
||||
|
||||
foreach ($tags as $new_tag) {
|
||||
if (preg_match_all('/([#@!])\[url\=([^\[\]]*)\]([^\[\]]*)\[\/url\]/ism', $new_tag, $results, PREG_SET_ORDER)) {
|
||||
foreach ($results as $tag) {
|
||||
Tag::removeByHash($item['uri-id'], $tag[1], $tag[3], $tag[2]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($old_tags as $index => $old_tag) {
|
||||
if (strcmp($old_tag, $new_tag) == 0) {
|
||||
unset($old_tags[$index]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tag_str = implode(',', $old_tags);
|
||||
Term::insertFromTagFieldByItemId($item_id, $tag_str);
|
||||
}
|
||||
|
||||
function tagrm_content(App $a)
|
||||
|
|
|
@ -25,7 +25,7 @@ use Friendica\Core\Renderer;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
|
||||
/**
|
||||
* TagCloud widget
|
||||
|
@ -46,7 +46,7 @@ class TagCloud
|
|||
* @return string HTML formatted output.
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function getHTML($uid, $count = 0, $owner_id = 0, $flags = '', $type = Term::HASHTAG)
|
||||
public static function getHTML($uid, $count = 0, $owner_id = 0, $flags = '', $type = Tag::HASHTAG)
|
||||
{
|
||||
$o = '';
|
||||
$r = self::tagadelic($uid, $count, $owner_id, $flags, $type);
|
||||
|
@ -85,7 +85,7 @@ class TagCloud
|
|||
* @return array Alphabetical sorted array of used tags of an user.
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function tagadelic($uid, $count = 0, $owner_id = 0, $flags = '', $type = Term::HASHTAG)
|
||||
private static function tagadelic($uid, $count = 0, $owner_id = 0, $flags = '', $type = Tag::HASHTAG)
|
||||
{
|
||||
$sql_options = Item::getPermissionsSQLByUserId($uid);
|
||||
$limit = $count ? sprintf('LIMIT %d', intval($count)) : '';
|
||||
|
@ -101,16 +101,13 @@ class TagCloud
|
|||
}
|
||||
|
||||
// Fetch tags
|
||||
$tag_stmt = DBA::p("SELECT `term`, COUNT(`term`) AS `total` FROM `term`
|
||||
LEFT JOIN `item` ON `term`.`oid` = `item`.`id`
|
||||
WHERE `term`.`uid` = ? AND `term`.`type` = ?
|
||||
AND `term`.`otype` = ?
|
||||
$tag_stmt = DBA::p("SELECT `name`, COUNT(`name`) AS `total` FROM `tag-search-view`
|
||||
LEFT JOIN `item` ON `tag-search-view`.`uri-id` = `item`.`uri-id`
|
||||
WHERE `tag-search-view`.`uid` = ?
|
||||
AND `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated`
|
||||
$sql_options
|
||||
GROUP BY `term` ORDER BY `total` DESC $limit",
|
||||
$uid,
|
||||
$type,
|
||||
Term::OBJECT_TYPE_POST
|
||||
GROUP BY `name` ORDER BY `total` DESC $limit",
|
||||
$uid
|
||||
);
|
||||
if (!DBA::isResult($tag_stmt)) {
|
||||
return [];
|
||||
|
@ -139,7 +136,7 @@ class TagCloud
|
|||
}
|
||||
|
||||
foreach ($arr as $rr) {
|
||||
$tags[$x][0] = $rr['term'];
|
||||
$tags[$x][0] = $rr['name'];
|
||||
$tags[$x][1] = log($rr['total']);
|
||||
$tags[$x][2] = 0;
|
||||
$min = min($min, $tags[$x][1]);
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace Friendica\Content\Widget;
|
|||
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
|
||||
/**
|
||||
* Trending tags aside widget for the community pages, handles both local and global scopes
|
||||
|
@ -41,9 +41,9 @@ class TrendingTags
|
|||
public static function getHTML($content = 'global', int $period = 24)
|
||||
{
|
||||
if ($content == 'local') {
|
||||
$tags = Term::getLocalTrendingHashtags($period, 20);
|
||||
$tags = Tag::getLocalTrendingHashtags($period, 20);
|
||||
} else {
|
||||
$tags = Term::getGlobalTrendingHashtags($period, 20);
|
||||
$tags = Tag::getGlobalTrendingHashtags($period, 20);
|
||||
}
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('widget/trending_tags.tpl');
|
||||
|
|
|
@ -28,7 +28,9 @@ use Friendica\Model\Contact;
|
|||
use Friendica\Model\Item;
|
||||
use Friendica\Model\ItemURI;
|
||||
use Friendica\Model\PermissionSet;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\UserItem;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
* These database-intensive post update routines are meant to be executed in the background by the cronjob.
|
||||
|
@ -64,6 +66,15 @@ class PostUpdate
|
|||
if (!self::update1329()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::update1341()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::update1342()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::update1345()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -533,4 +544,181 @@ class PostUpdate
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the "tag" table with tags and mentions from the body
|
||||
*
|
||||
* @return bool "true" when the job is done
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function update1341()
|
||||
{
|
||||
// Was the script completed?
|
||||
if (DI::config()->get('system', 'post_update_version') >= 1341) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$id = DI::config()->get('system', 'post_update_version_1341_id', 0);
|
||||
|
||||
Logger::info('Start', ['item' => $id]);
|
||||
|
||||
$rows = 0;
|
||||
|
||||
$items = DBA::p("SELECT `uri-id`,`body` FROM `item-content` WHERE
|
||||
(`body` LIKE ? OR `body` LIKE ? OR `body` LIKE ?) AND `uri-id` >= ?
|
||||
ORDER BY `uri-id` LIMIT 100000", '%#%', '%@%', '%!%', $id);
|
||||
|
||||
if (DBA::errorNo() != 0) {
|
||||
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
|
||||
return false;
|
||||
}
|
||||
|
||||
while ($item = DBA::fetch($items)) {
|
||||
Tag::storeFromBody($item['uri-id'], $item['body'], '#!@', false);
|
||||
$id = $item['uri-id'];
|
||||
++$rows;
|
||||
if ($rows % 1000 == 0) {
|
||||
DI::config()->set('system', 'post_update_version_1341_id', $id);
|
||||
}
|
||||
}
|
||||
DBA::close($items);
|
||||
|
||||
DI::config()->set('system', 'post_update_version_1341_id', $id);
|
||||
|
||||
Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
|
||||
|
||||
// When there are less than 1,000 items processed this means that we reached the end
|
||||
// The other entries will then be processed with the regular functionality
|
||||
if ($rows < 1000) {
|
||||
DI::config()->set('system', 'post_update_version', 1341);
|
||||
Logger::info('Done');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the "tag" table with tags and mentions from the "term" table
|
||||
*
|
||||
* @return bool "true" when the job is done
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function update1342()
|
||||
{
|
||||
// Was the script completed?
|
||||
if (DI::config()->get('system', 'post_update_version') >= 1342) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$id = DI::config()->get('system', 'post_update_version_1342_id', 0);
|
||||
|
||||
Logger::info('Start', ['item' => $id]);
|
||||
|
||||
$rows = 0;
|
||||
|
||||
$terms = DBA::p("SELECT `term`.`tid`, `item`.`uri-id`, `term`.`type`, `term`.`term`, `term`.`url`, `item-content`.`body`
|
||||
FROM `term`
|
||||
INNER JOIN `item` ON `item`.`id` = `term`.`oid`
|
||||
INNER JOIN `item-content` ON `item-content`.`uri-id` = `item`.`uri-id`
|
||||
WHERE term.type IN (?, ?, ?, ?) AND `tid` >= ? ORDER BY `tid` LIMIT 100000",
|
||||
Tag::HASHTAG, Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION, $id);
|
||||
|
||||
if (DBA::errorNo() != 0) {
|
||||
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
|
||||
return false;
|
||||
}
|
||||
|
||||
while ($term = DBA::fetch($terms)) {
|
||||
if (($term['type'] == Tag::MENTION) && !empty($term['url']) && !strstr($term['body'], $term['url'])) {
|
||||
$condition = ['nurl' => Strings::normaliseLink($term['url']), 'uid' => 0, 'deleted' => false];
|
||||
$contact = DBA::selectFirst('contact', ['url', 'alias'], $condition, ['order' => ['id']]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
$ssl_url = str_replace('http://', 'https://', $term['url']);
|
||||
$condition = ['`alias` IN (?, ?, ?) AND `uid` = ? AND NOT `deleted`', $term['url'], Strings::normaliseLink($term['url']), $ssl_url, 0];
|
||||
$contact = DBA::selectFirst('contact', ['url', 'alias'], $condition, ['order' => ['id']]);
|
||||
}
|
||||
|
||||
if (DBA::isResult($contact) && (!strstr($term['body'], $contact['url']) && (empty($contact['alias']) || !strstr($term['body'], $contact['alias'])))) {
|
||||
$term['type'] = Tag::IMPLICIT_MENTION;
|
||||
}
|
||||
}
|
||||
|
||||
Tag::store($term['uri-id'], $term['type'], $term['term'], $term['url'], false);
|
||||
|
||||
$id = $term['tid'];
|
||||
++$rows;
|
||||
if ($rows % 1000 == 0) {
|
||||
DI::config()->set('system', 'post_update_version_1342_id', $id);
|
||||
}
|
||||
}
|
||||
DBA::close($terms);
|
||||
|
||||
DI::config()->set('system', 'post_update_version_1342_id', $id);
|
||||
|
||||
Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
|
||||
|
||||
// When there are less than 1,000 items processed this means that we reached the end
|
||||
// The other entries will then be processed with the regular functionality
|
||||
if ($rows < 1000) {
|
||||
DI::config()->set('system', 'post_update_version', 1342);
|
||||
Logger::info('Done');
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
++$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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ class Item
|
|||
|
||||
// Field list that is used to display the items
|
||||
const DISPLAY_FIELDLIST = [
|
||||
'uid', 'id', 'parent', 'uri', 'thr-parent', 'parent-uri', 'guid', 'network', 'gravity',
|
||||
'uid', 'id', 'parent', 'uri-id', 'uri', 'thr-parent', 'parent-uri', 'guid', 'network', 'gravity',
|
||||
'commented', 'created', 'edited', 'received', 'verb', 'object-type', 'postopts', 'plink',
|
||||
'wall', 'private', 'starred', 'origin', 'title', 'body', 'file', 'attach', 'language',
|
||||
'content-warning', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object',
|
||||
|
@ -77,10 +77,10 @@ class Item
|
|||
];
|
||||
|
||||
// Field list that is used to deliver items via the protocols
|
||||
const DELIVER_FIELDLIST = ['uid', 'id', 'parent', 'uri', 'thr-parent', 'parent-uri', 'guid',
|
||||
const DELIVER_FIELDLIST = ['uid', 'id', 'parent', 'uri-id', 'uri', 'thr-parent', 'parent-uri', 'guid',
|
||||
'parent-guid', 'created', 'edited', 'verb', 'object-type', 'object', 'target',
|
||||
'private', 'title', 'body', 'location', 'coord', 'app',
|
||||
'attach', 'tag', 'deleted', 'extid', 'post-type',
|
||||
'attach', 'deleted', 'extid', 'post-type',
|
||||
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
|
||||
'author-id', 'author-link', 'owner-link', 'contact-uid',
|
||||
'signed_text', 'signature', 'signer', 'network'];
|
||||
|
@ -98,7 +98,7 @@ class Item
|
|||
'guid', 'uri-id', 'parent-uri-id', 'thr-parent-id',
|
||||
'contact-id', 'type', 'wall', 'gravity', 'extid', 'icid', 'iaid', 'psid',
|
||||
'created', 'edited', 'commented', 'received', 'changed', 'verb',
|
||||
'postopts', 'plink', 'resource-id', 'event-id', 'tag', 'attach', 'inform',
|
||||
'postopts', 'plink', 'resource-id', 'event-id', 'attach', 'inform',
|
||||
'file', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type',
|
||||
'private', 'pubmail', 'moderated', 'visible', 'starred', 'bookmark',
|
||||
'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global', 'network',
|
||||
|
@ -283,7 +283,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];
|
||||
|
@ -317,11 +317,6 @@ class Item
|
|||
}
|
||||
|
||||
if (!array_key_exists('verb', $row) || in_array($row['verb'], ['', Activity::POST, Activity::SHARE])) {
|
||||
// Build the tag string out of the term entries
|
||||
if (array_key_exists('tag', $row) && empty($row['tag'])) {
|
||||
$row['tag'] = Term::tagTextFromItemId($row['internal-iid']);
|
||||
}
|
||||
|
||||
// Build the file string out of the term entries
|
||||
if (array_key_exists('file', $row) && empty($row['file'])) {
|
||||
$row['file'] = Term::fileTextFromItemId($row['internal-iid']);
|
||||
|
@ -673,7 +668,7 @@ class Item
|
|||
'guid', 'uri-id', 'parent-uri-id', 'thr-parent-id',
|
||||
'contact-id', 'owner-id', 'author-id', 'type', 'wall', 'gravity', 'extid',
|
||||
'created', 'edited', 'commented', 'received', 'changed', 'psid',
|
||||
'resource-id', 'event-id', 'tag', 'attach', 'post-type', 'file',
|
||||
'resource-id', 'event-id', 'attach', 'post-type', 'file',
|
||||
'private', 'pubmail', 'moderated', 'visible', 'starred', 'bookmark',
|
||||
'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global',
|
||||
'id' => 'item_id', 'network', 'icid', 'iaid', 'id' => 'internal-iid',
|
||||
|
@ -687,7 +682,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'];
|
||||
|
||||
|
@ -809,8 +804,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) {
|
||||
|
@ -850,7 +845,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) {
|
||||
|
@ -922,7 +917,7 @@ class Item
|
|||
// We cannot simply expand the condition to check for origin entries
|
||||
// The condition needn't to be a simple array but could be a complex condition.
|
||||
// And we have to execute this query before the update to ensure to fetch the same data.
|
||||
$items = DBA::select('item', ['id', 'origin', 'uri', 'uri-id', 'iaid', 'icid', 'tag', 'file'], $condition);
|
||||
$items = DBA::select('item', ['id', 'origin', 'uri', 'uri-id', 'iaid', 'icid', 'file'], $condition);
|
||||
|
||||
$content_fields = [];
|
||||
foreach (array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST) as $field) {
|
||||
|
@ -936,7 +931,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) {
|
||||
|
@ -945,13 +940,6 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('tag', $fields)) {
|
||||
$tags = $fields['tag'];
|
||||
$fields['tag'] = null;
|
||||
} else {
|
||||
$tags = null;
|
||||
}
|
||||
|
||||
if (array_key_exists('file', $fields)) {
|
||||
$files = $fields['file'];
|
||||
$fields['file'] = null;
|
||||
|
@ -1024,13 +1012,6 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
if (!is_null($tags)) {
|
||||
Term::insertFromTagFieldByItemId($item['id'], $tags);
|
||||
if (!empty($item['tag'])) {
|
||||
DBA::update('item', ['tag' => ''], ['id' => $item['id']]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($files)) {
|
||||
Term::insertFromFileFieldByItemId($item['id'], $files);
|
||||
if (!empty($item['file'])) {
|
||||
|
@ -1038,7 +1019,7 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
ItemDeliveryData::update($item['id'], $delivery_data);
|
||||
Post\DeliveryData::update($item['uri-id'], $delivery_data);
|
||||
|
||||
self::updateThread($item['id']);
|
||||
|
||||
|
@ -1118,7 +1099,7 @@ class Item
|
|||
{
|
||||
Logger::info('Mark item for deletion by id', ['id' => $item_id, 'callstack' => System::callstack()]);
|
||||
// locate item to be deleted
|
||||
$fields = ['id', 'uri', 'uid', 'parent', 'parent-uri', 'origin',
|
||||
$fields = ['id', 'uri', 'uri-id', 'uid', 'parent', 'parent-uri', 'origin',
|
||||
'deleted', 'file', 'resource-id', 'event-id', 'attach',
|
||||
'verb', 'object-type', 'object', 'target', 'contact-id',
|
||||
'icid', 'iaid', 'psid'];
|
||||
|
@ -1184,9 +1165,6 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
// Delete tags that had been attached to other items
|
||||
self::deleteTagsFromItem($item);
|
||||
|
||||
// Delete notifications
|
||||
DBA::delete('notify', ['iid' => $item['id'], 'uid' => $item['uid']]);
|
||||
|
||||
|
@ -1194,7 +1172,6 @@ class Item
|
|||
$item_fields = ['deleted' => true, 'edited' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()];
|
||||
DBA::update('item', $item_fields, ['id' => $item['id']]);
|
||||
|
||||
Term::insertFromTagFieldByItemId($item['id'], '');
|
||||
Term::insertFromFileFieldByItemId($item['id'], '');
|
||||
self::deleteThread($item['id'], $item['parent-uri']);
|
||||
|
||||
|
@ -1202,7 +1179,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
|
||||
|
||||
|
@ -1243,43 +1220,6 @@ class Item
|
|||
return true;
|
||||
}
|
||||
|
||||
private static function deleteTagsFromItem($item)
|
||||
{
|
||||
if (($item["verb"] != Activity::TAG) || ($item["object-type"] != Activity\ObjectType::TAGTERM)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$xo = XML::parseString($item["object"]);
|
||||
$xt = XML::parseString($item["target"]);
|
||||
|
||||
if ($xt->type != Activity\ObjectType::NOTE) {
|
||||
return;
|
||||
}
|
||||
|
||||
$i = self::selectFirst(['id', 'contact-id', 'tag'], ['uri' => $xt->id, 'uid' => $item['uid']]);
|
||||
if (!DBA::isResult($i)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For tags, the owner cannot remove the tag on the author's copy of the post.
|
||||
$owner_remove = ($item["contact-id"] == $i["contact-id"]);
|
||||
$author_copy = $item["origin"];
|
||||
|
||||
if (($owner_remove && $author_copy) || !$owner_remove) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tags = explode(',', $i["tag"]);
|
||||
$newtags = [];
|
||||
if (count($tags)) {
|
||||
foreach ($tags as $tag) {
|
||||
if (trim($tag) !== trim($xo->body)) {
|
||||
$newtags[] = trim($tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
self::update(['tag' => implode(',', $newtags)], ['id' => $i["id"]]);
|
||||
}
|
||||
|
||||
private static function guid($item, $notify)
|
||||
{
|
||||
|
@ -1547,7 +1487,6 @@ class Item
|
|||
$item['deny_gid'] = trim($item['deny_gid'] ?? '');
|
||||
$item['private'] = intval($item['private'] ?? self::PUBLIC);
|
||||
$item['body'] = trim($item['body'] ?? '');
|
||||
$item['tag'] = trim($item['tag'] ?? '');
|
||||
$item['attach'] = trim($item['attach'] ?? '');
|
||||
$item['app'] = trim($item['app'] ?? '');
|
||||
$item['origin'] = intval($item['origin'] ?? 0);
|
||||
|
@ -1674,6 +1613,11 @@ class Item
|
|||
// Check for hashtags in the body and repair or add hashtag links
|
||||
self::setHashtags($item);
|
||||
|
||||
// Store tags from the body if this hadn't been handled previously in the protocol classes
|
||||
if (!Tag::existsForPost($item['uri-id'])) {
|
||||
Tag::storeFromBody($item['uri-id'], $item['body']);
|
||||
}
|
||||
|
||||
$item['thr-parent'] = $item['parent-uri'];
|
||||
|
||||
$notify_type = Delivery::POST;
|
||||
|
@ -1865,13 +1809,6 @@ class Item
|
|||
|
||||
Logger::log('' . print_r($item,true), Logger::DATA);
|
||||
|
||||
if (array_key_exists('tag', $item)) {
|
||||
$tags = $item['tag'];
|
||||
unset($item['tag']);
|
||||
} else {
|
||||
$tags = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('file', $item)) {
|
||||
$files = $item['file'];
|
||||
unset($item['file']);
|
||||
|
@ -1898,7 +1835,7 @@ class Item
|
|||
self::insertContent($item);
|
||||
}
|
||||
|
||||
$delivery_data = ItemDeliveryData::extractFields($item);
|
||||
$delivery_data = Post\DeliveryData::extractFields($item);
|
||||
|
||||
unset($item['postopts']);
|
||||
unset($item['inform']);
|
||||
|
@ -2002,7 +1939,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();
|
||||
|
@ -2011,10 +1948,6 @@ class Item
|
|||
* Due to deadlock issues with the "term" table we are doing these steps after the commit.
|
||||
* This is not perfect - but a workable solution until we found the reason for the problem.
|
||||
*/
|
||||
if (!empty($tags)) {
|
||||
Term::insertFromTagFieldByItemId($current_post, $tags);
|
||||
}
|
||||
|
||||
if (!empty($files)) {
|
||||
Term::insertFromFileFieldByItemId($current_post, $files);
|
||||
}
|
||||
|
@ -2629,9 +2562,6 @@ class Item
|
|||
if (DI::config()->get('system', 'local_tags')) {
|
||||
$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
|
||||
"#[url=".DI::baseUrl()."/search?tag=$2]$2[/url]", $item["body"]);
|
||||
|
||||
$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
|
||||
"#[url=".DI::baseUrl()."/search?tag=$2]$2[/url]", $item["tag"]);
|
||||
}
|
||||
|
||||
// mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
|
||||
|
@ -2663,13 +2593,6 @@ class Item
|
|||
$newtag = '#[url=' . DI::baseUrl() . '/search?tag=' . $basetag . ']' . $basetag . '[/url]';
|
||||
|
||||
$item["body"] = str_replace($tag, $newtag, $item["body"]);
|
||||
|
||||
if (!stristr($item["tag"], "/search?tag=" . $basetag . "]" . $basetag . "[/url]")) {
|
||||
if (strlen($item["tag"])) {
|
||||
$item["tag"] = ',' . $item["tag"];
|
||||
}
|
||||
$item["tag"] = $newtag . $item["tag"];
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back the masked hashtags
|
||||
|
@ -3027,30 +2950,6 @@ class Item
|
|||
return $recipients;
|
||||
}
|
||||
|
||||
public static function getFeedTags($item)
|
||||
{
|
||||
$ret = [];
|
||||
$matches = false;
|
||||
$cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|', $item['tag'], $matches);
|
||||
if ($cnt) {
|
||||
for ($x = 0; $x < $cnt; $x ++) {
|
||||
if ($matches[1][$x]) {
|
||||
$ret[$matches[2][$x]] = ['#', $matches[1][$x], $matches[2][$x]];
|
||||
}
|
||||
}
|
||||
}
|
||||
$matches = false;
|
||||
$cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|', $item['tag'], $matches);
|
||||
if ($cnt) {
|
||||
for ($x = 0; $x < $cnt; $x ++) {
|
||||
if ($matches[1][$x]) {
|
||||
$ret[] = ['@', $matches[1][$x], $matches[2][$x]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public static function expire($uid, $days, $network = "", $force = false)
|
||||
{
|
||||
if (!$uid || ($days < 1)) {
|
||||
|
@ -3558,7 +3457,7 @@ class Item
|
|||
return $ev;
|
||||
}
|
||||
|
||||
$tags = Term::populateTagsFromItem($item);
|
||||
$tags = Tag::populateFromItem($item);
|
||||
|
||||
$item['tags'] = $tags['tags'];
|
||||
$item['hashtags'] = $tags['hashtags'];
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
|
@ -22,9 +22,11 @@
|
|||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Cache\Duration;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
|
@ -38,8 +40,6 @@ class Tag
|
|||
const UNKNOWN = 0;
|
||||
const HASHTAG = 1;
|
||||
const MENTION = 2;
|
||||
const CATEGORY = 3;
|
||||
const FILE = 5;
|
||||
/**
|
||||
* An implicit mention is a mention in a comment body that is redundant with the threading information.
|
||||
*/
|
||||
|
@ -67,7 +67,19 @@ class Tag
|
|||
*/
|
||||
public static function store(int $uriid, int $type, string $name, string $url = '', $probing = true)
|
||||
{
|
||||
$name = trim($name, "\x00..\x20\xFF#!@");
|
||||
if ($type == self::HASHTAG) {
|
||||
// Remove some common "garbarge" from tags
|
||||
$name = trim($name, "\x00..\x20\xFF#!@,;.:'/?!^°$%".'"');
|
||||
|
||||
$tags = explode(self::TAG_CHARACTER[self::HASHTAG], $name);
|
||||
if (count($tags) > 1) {
|
||||
foreach ($tags as $tag) {
|
||||
self::store($uriid, $type, $tag, $url, $probing);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
return;
|
||||
}
|
||||
|
@ -75,7 +87,7 @@ class Tag
|
|||
$cid = 0;
|
||||
$tagid = 0;
|
||||
|
||||
if (in_array($type, [Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION])) {
|
||||
if (in_array($type, [self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION])) {
|
||||
if (empty($url)) {
|
||||
// No mention without a contact url
|
||||
return;
|
||||
|
@ -114,7 +126,7 @@ class Tag
|
|||
if (empty($cid)) {
|
||||
$fields = ['name' => substr($name, 0, 96), 'url' => ''];
|
||||
|
||||
if (($type != Tag::HASHTAG) && !empty($url) && ($url != $name)) {
|
||||
if (($type != self::HASHTAG) && !empty($url) && ($url != $name)) {
|
||||
$fields['url'] = strtolower($url);
|
||||
}
|
||||
|
||||
|
@ -134,9 +146,9 @@ class Tag
|
|||
|
||||
$fields = ['uri-id' => $uriid, 'type' => $type, 'tid' => $tagid, 'cid' => $cid];
|
||||
|
||||
if (in_array($type, [Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION])) {
|
||||
if (in_array($type, [self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION])) {
|
||||
$condition = $fields;
|
||||
$condition['type'] = [Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION];
|
||||
$condition['type'] = [self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION];
|
||||
if (DBA::exists('post-tag', $condition)) {
|
||||
Logger::info('Tag already exists', $fields);
|
||||
return;
|
||||
|
@ -221,6 +233,17 @@ class Tag
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for stored hashtags and mentions for the given post
|
||||
*
|
||||
* @param integer $uriid
|
||||
* @return bool
|
||||
*/
|
||||
public static function existsForPost(int $uriid)
|
||||
{
|
||||
return DBA::exists('post-tag', ['uri-id' => $uriid, 'type' => [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION, self::EXCLUSIVE_MENTION]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove tag/mention
|
||||
*
|
||||
|
@ -282,6 +305,176 @@ class Tag
|
|||
} else {
|
||||
return self::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the terms from the provided type(s) associated with the provided item ID.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @param int|array $type
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getByURIId(int $uri_id, array $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION, self::EXCLUSIVE_MENTION])
|
||||
{
|
||||
$condition = ['uri-id' => $uri_id, 'type' => $type];
|
||||
return DBA::selectToArray('tag-view', ['type', 'name', 'url'], $condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param array $item
|
||||
* @return array
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function populateFromItem(&$item)
|
||||
{
|
||||
$return = [
|
||||
'tags' => [],
|
||||
'hashtags' => [],
|
||||
'mentions' => [],
|
||||
'implicit_mentions' => [],
|
||||
];
|
||||
|
||||
$searchpath = DI::baseUrl() . "/search?tag=";
|
||||
|
||||
$taglist = DBA::select('tag-view', ['type', 'name', 'url'],
|
||||
['uri-id' => $item['uri-id'], 'type' => [self::HASHTAG, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION]]);
|
||||
while ($tag = DBA::fetch($taglist)) {
|
||||
if ($tag['url'] == '') {
|
||||
$tag['url'] = $searchpath . rawurlencode($tag['name']);
|
||||
}
|
||||
|
||||
$orig_tag = $tag['url'];
|
||||
|
||||
$prefix = self::TAG_CHARACTER[$tag['type']];
|
||||
switch($tag['type']) {
|
||||
case self::HASHTAG:
|
||||
if ($orig_tag != $tag['url']) {
|
||||
$item['body'] = str_replace($orig_tag, $tag['url'], $item['body']);
|
||||
}
|
||||
|
||||
$return['hashtags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['name']) . '</a>';
|
||||
$return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['name']) . '</a>';
|
||||
break;
|
||||
case self::MENTION:
|
||||
case self::EXCLUSIVE_MENTION:
|
||||
$tag['url'] = Contact::magicLink($tag['url']);
|
||||
$return['mentions'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['name']) . '</a>';
|
||||
$return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['name']) . '</a>';
|
||||
break;
|
||||
case self::IMPLICIT_MENTION:
|
||||
$return['implicit_mentions'][] = $prefix . $tag['name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
DBA::close($taglist);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search posts for given tag
|
||||
*
|
||||
* @param string $search
|
||||
* @param integer $uid
|
||||
* @param integer $start
|
||||
* @param integer $limit
|
||||
* @return array with URI-ID
|
||||
*/
|
||||
public static function getURIIdListByTag(string $search, int $uid = 0, int $start = 0, int $limit = 100)
|
||||
{
|
||||
$condition = ["`name` = ? AND (NOT `private` OR (`private` AND `uid` = ?))", $search, $uid];
|
||||
$params = [
|
||||
'order' => ['uri-id' => true],
|
||||
'group_by' => ['uri-id'],
|
||||
'limit' => [$start, $limit]
|
||||
];
|
||||
|
||||
$tags = DBA::select('tag-search-view', ['uri-id'], $condition, $params);
|
||||
|
||||
$uriids = [];
|
||||
while ($tag = DBA::fetch($tags)) {
|
||||
$uriids[] = $tag['uri-id'];
|
||||
}
|
||||
DBA::close($tags);
|
||||
|
||||
return $uriids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the most frequent global hashtags over the given period
|
||||
*
|
||||
* @param int $period Period in hours to consider posts
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getGlobalTrendingHashtags(int $period, $limit = 10)
|
||||
{
|
||||
$tags = DI::cache()->get('global_trending_tags');
|
||||
|
||||
if (empty($tags)) {
|
||||
$tagsStmt = DBA::p("SELECT `name` AS `term`, COUNT(*) AS `score`
|
||||
FROM `tag-search-view`
|
||||
WHERE `private` = ? AND `received` > DATE_SUB(NOW(), INTERVAL ? HOUR)
|
||||
GROUP BY `term` ORDER BY `score` DESC LIMIT ?",
|
||||
Item::PUBLIC, $period, $limit);
|
||||
|
||||
if (DBA::isResult($tagsStmt)) {
|
||||
$tags = DBA::toArray($tagsStmt);
|
||||
DI::cache()->set('global_trending_tags', $tags, Duration::HOUR);
|
||||
}
|
||||
}
|
||||
|
||||
return $tags ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the most frequent local hashtags over the given period
|
||||
*
|
||||
* @param int $period Period in hours to consider posts
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getLocalTrendingHashtags(int $period, $limit = 10)
|
||||
{
|
||||
$tags = DI::cache()->get('local_trending_tags');
|
||||
|
||||
if (empty($tags)) {
|
||||
$tagsStmt = DBA::p("SELECT `name` AS `term`, COUNT(*) AS `score`
|
||||
FROM `tag-search-view`
|
||||
WHERE `private` = ? AND `wall` AND `origin` AND `received` > DATE_SUB(NOW(), INTERVAL ? HOUR)
|
||||
GROUP BY `term` ORDER BY `score` DESC LIMIT ?",
|
||||
Item::PUBLIC, $period, $limit);
|
||||
|
||||
if (DBA::isResult($tagsStmt)) {
|
||||
$tags = DBA::toArray($tagsStmt);
|
||||
DI::cache()->set('local_trending_tags', $tags, Duration::HOUR);
|
||||
}
|
||||
}
|
||||
|
||||
return $tags ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided tag is of one of the provided term types.
|
||||
*
|
||||
* @param string $tag
|
||||
* @param int ...$types
|
||||
* @return bool
|
||||
*/
|
||||
public static function isType($tag, ...$types)
|
||||
{
|
||||
$tag_chars = [];
|
||||
foreach ($types as $type) {
|
||||
if (array_key_exists($type, self::TAG_CHARACTER)) {
|
||||
$tag_chars[] = self::TAG_CHARACTER[$type];
|
||||
}
|
||||
}
|
||||
|
||||
return Strings::startsWith($tag, $tag_chars);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,7 @@
|
|||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Core\Cache\Duration;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
* Class Term
|
||||
|
@ -37,155 +33,10 @@ use Friendica\Util\Strings;
|
|||
class Term
|
||||
{
|
||||
const UNKNOWN = 0;
|
||||
const HASHTAG = 1;
|
||||
const MENTION = 2;
|
||||
const CATEGORY = 3;
|
||||
const FILE = 5;
|
||||
/**
|
||||
* An implicit mention is a mention in a comment body that is redundant with the threading information.
|
||||
*/
|
||||
const IMPLICIT_MENTION = 8;
|
||||
/**
|
||||
* An exclusive mention transfers the ownership of the post to the target account, usually a forum.
|
||||
*/
|
||||
const EXCLUSIVE_MENTION = 9;
|
||||
|
||||
const TAG_CHARACTER = [
|
||||
self::HASHTAG => '#',
|
||||
self::MENTION => '@',
|
||||
self::IMPLICIT_MENTION => '%',
|
||||
self::EXCLUSIVE_MENTION => '!',
|
||||
];
|
||||
|
||||
const OBJECT_TYPE_POST = 1;
|
||||
const OBJECT_TYPE_PHOTO = 2;
|
||||
|
||||
/**
|
||||
* Returns a list of the most frequent global hashtags over the given period
|
||||
*
|
||||
* @param int $period Period in hours to consider posts
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getGlobalTrendingHashtags(int $period, $limit = 10)
|
||||
{
|
||||
$tags = DI::cache()->get('global_trending_tags');
|
||||
|
||||
if (!$tags) {
|
||||
$tagsStmt = DBA::p("SELECT t.`term`, COUNT(*) AS `score`
|
||||
FROM `term` t
|
||||
JOIN `item` i ON i.`id` = t.`oid` AND i.`uid` = t.`uid`
|
||||
JOIN `thread` ON `thread`.`iid` = i.`id`
|
||||
WHERE `thread`.`visible`
|
||||
AND NOT `thread`.`deleted`
|
||||
AND NOT `thread`.`moderated`
|
||||
AND `thread`.`private` = ?
|
||||
AND t.`uid` = 0
|
||||
AND t.`otype` = ?
|
||||
AND t.`type` = ?
|
||||
AND t.`term` != ''
|
||||
AND i.`received` > DATE_SUB(NOW(), INTERVAL ? HOUR)
|
||||
GROUP BY `term`
|
||||
ORDER BY `score` DESC
|
||||
LIMIT ?",
|
||||
Item::PUBLIC,
|
||||
Term::OBJECT_TYPE_POST,
|
||||
Term::HASHTAG,
|
||||
$period,
|
||||
$limit
|
||||
);
|
||||
|
||||
if (DBA::isResult($tagsStmt)) {
|
||||
$tags = DBA::toArray($tagsStmt);
|
||||
DI::cache()->set('global_trending_tags', $tags, Duration::HOUR);
|
||||
}
|
||||
}
|
||||
|
||||
return $tags ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the most frequent local hashtags over the given period
|
||||
*
|
||||
* @param int $period Period in hours to consider posts
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getLocalTrendingHashtags(int $period, $limit = 10)
|
||||
{
|
||||
$tags = DI::cache()->get('local_trending_tags');
|
||||
|
||||
if (!$tags) {
|
||||
$tagsStmt = DBA::p("SELECT t.`term`, COUNT(*) AS `score`
|
||||
FROM `term` t
|
||||
JOIN `item` i ON i.`id` = t.`oid` AND i.`uid` = t.`uid`
|
||||
JOIN `thread` ON `thread`.`iid` = i.`id`
|
||||
WHERE `thread`.`visible`
|
||||
AND NOT `thread`.`deleted`
|
||||
AND NOT `thread`.`moderated`
|
||||
AND `thread`.`private` = ?
|
||||
AND `thread`.`wall`
|
||||
AND `thread`.`origin`
|
||||
AND t.`otype` = ?
|
||||
AND t.`type` = ?
|
||||
AND t.`term` != ''
|
||||
AND i.`received` > DATE_SUB(NOW(), INTERVAL ? HOUR)
|
||||
GROUP BY `term`
|
||||
ORDER BY `score` DESC
|
||||
LIMIT ?",
|
||||
Item::PUBLIC,
|
||||
Term::OBJECT_TYPE_POST,
|
||||
Term::HASHTAG,
|
||||
$period,
|
||||
$limit
|
||||
);
|
||||
|
||||
if (DBA::isResult($tagsStmt)) {
|
||||
$tags = DBA::toArray($tagsStmt);
|
||||
DI::cache()->set('local_trending_tags', $tags, Duration::HOUR);
|
||||
}
|
||||
}
|
||||
|
||||
return $tags ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the legacy item.tag field comma-separated BBCode string from an item ID.
|
||||
* Includes only hashtags, implicit and explicit mentions.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function tagTextFromItemId($item_id)
|
||||
{
|
||||
$tag_list = [];
|
||||
$tags = self::tagArrayFromItemId($item_id, [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION]);
|
||||
foreach ($tags as $tag) {
|
||||
$tag_list[] = self::TAG_CHARACTER[$tag['type']] . '[url=' . $tag['url'] . ']' . $tag['term'] . '[/url]';
|
||||
}
|
||||
|
||||
return implode(',', $tag_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the terms from the provided type(s) associated with the provided item ID.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @param int|array $type
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function tagArrayFromItemId($item_id, $type = [self::HASHTAG, self::MENTION])
|
||||
{
|
||||
$condition = ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => $type];
|
||||
$tags = DBA::select('term', ['type', 'term', 'url'], $condition);
|
||||
if (!DBA::isResult($tags)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return DBA::toArray($tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the legacy item.file field string from an item ID.
|
||||
|
@ -198,7 +49,9 @@ class Term
|
|||
public static function fileTextFromItemId($item_id)
|
||||
{
|
||||
$file_text = '';
|
||||
$tags = self::tagArrayFromItemId($item_id, [self::FILE, self::CATEGORY]);
|
||||
|
||||
$condition = ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => [self::FILE, self::CATEGORY]];
|
||||
$tags = DBA::selectToArray('term', ['type', 'term', 'url'], $condition);
|
||||
foreach ($tags as $tag) {
|
||||
if ($tag['type'] == self::CATEGORY) {
|
||||
$file_text .= '<' . $tag['term'] . '>';
|
||||
|
@ -210,170 +63,6 @@ class Term
|
|||
return $file_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts new terms for the provided item ID based on the legacy item.tag field BBCode content.
|
||||
* Deletes all previous tag terms for the same item ID.
|
||||
* Sets both the item.mention and thread.mentions field flags if a mention concerning the item UID is found.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @param string $tag_str
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function insertFromTagFieldByItemId($item_id, $tag_str)
|
||||
{
|
||||
$profile_base = DI::baseUrl();
|
||||
$profile_data = parse_url($profile_base);
|
||||
$profile_path = $profile_data['path'] ?? '';
|
||||
$profile_base_friendica = $profile_data['host'] . $profile_path . '/profile/';
|
||||
$profile_base_diaspora = $profile_data['host'] . $profile_path . '/u/';
|
||||
|
||||
$fields = ['guid', 'uid', 'id', 'edited', 'deleted', 'created', 'received', 'title', 'body', 'parent'];
|
||||
$item = Item::selectFirst($fields, ['id' => $item_id]);
|
||||
if (!DBA::isResult($item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$item['tag'] = $tag_str;
|
||||
|
||||
// Clean up all tags
|
||||
self::deleteByItemId($item_id);
|
||||
|
||||
if ($item['deleted']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$taglist = explode(',', $item['tag']);
|
||||
|
||||
$tags_string = '';
|
||||
foreach ($taglist as $tag) {
|
||||
if (Strings::startsWith($tag, self::TAG_CHARACTER)) {
|
||||
$tags_string .= ' ' . trim($tag);
|
||||
} else {
|
||||
$tags_string .= ' #' . trim($tag);
|
||||
}
|
||||
}
|
||||
|
||||
$data = ' ' . $item['title'] . ' ' . $item['body'] . ' ' . $tags_string . ' ';
|
||||
|
||||
// ignore anything in a code block
|
||||
$data = preg_replace('/\[code\](.*?)\[\/code\]/sm', '', $data);
|
||||
|
||||
$tags = [];
|
||||
|
||||
$pattern = '/\W\#([^\[].*?)[\s\'".,:;\?!\[\]\/]/ism';
|
||||
if (preg_match_all($pattern, $data, $matches)) {
|
||||
foreach ($matches[1] as $match) {
|
||||
$tags['#' . $match] = '';
|
||||
}
|
||||
}
|
||||
|
||||
$pattern = '/\W([\#@!%])\[url\=(.*?)\](.*?)\[\/url\]/ism';
|
||||
if (preg_match_all($pattern, $data, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
|
||||
if (in_array($match[1], [
|
||||
self::TAG_CHARACTER[self::MENTION],
|
||||
self::TAG_CHARACTER[self::IMPLICIT_MENTION],
|
||||
self::TAG_CHARACTER[self::EXCLUSIVE_MENTION]
|
||||
])) {
|
||||
$contact = Contact::getDetailsByURL($match[2], 0);
|
||||
if (!empty($contact['addr'])) {
|
||||
$match[3] = $contact['addr'];
|
||||
}
|
||||
|
||||
if (!empty($contact['url'])) {
|
||||
$match[2] = $contact['url'];
|
||||
}
|
||||
}
|
||||
|
||||
$tags[$match[2]] = $match[1] . trim($match[3], ',.:;[]/\"?!');
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($tags as $link => $tag) {
|
||||
if (self::isType($tag, self::HASHTAG)) {
|
||||
// try to ignore #039 or #1 or anything like that
|
||||
if (ctype_digit(substr(trim($tag), 1))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// try to ignore html hex escapes, e.g. #x2317
|
||||
if ((substr(trim($tag), 1, 1) == 'x' || substr(trim($tag), 1, 1) == 'X') && ctype_digit(substr(trim($tag), 2))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = self::HASHTAG;
|
||||
$term = substr($tag, 1);
|
||||
$link = '';
|
||||
} elseif (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION)) {
|
||||
if (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION)) {
|
||||
$type = self::MENTION;
|
||||
} else {
|
||||
$type = self::IMPLICIT_MENTION;
|
||||
}
|
||||
|
||||
$contact = Contact::getDetailsByURL($link, 0);
|
||||
if (!empty($contact['name'])) {
|
||||
$term = $contact['name'];
|
||||
} else {
|
||||
$term = substr($tag, 1);
|
||||
}
|
||||
} else { // This shouldn't happen
|
||||
$type = self::HASHTAG;
|
||||
$term = $tag;
|
||||
$link = '';
|
||||
|
||||
Logger::notice('Unknown term type', ['tag' => $tag]);
|
||||
}
|
||||
|
||||
if (DBA::exists('term', ['uid' => $item['uid'], 'otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'term' => $term, 'type' => $type])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($term)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($item['uid'] == 0) {
|
||||
$global = true;
|
||||
DBA::update('term', ['global' => true], ['otype' => self::OBJECT_TYPE_POST, 'guid' => $item['guid']]);
|
||||
} else {
|
||||
$global = DBA::exists('term', ['uid' => 0, 'otype' => self::OBJECT_TYPE_POST, 'guid' => $item['guid']]);
|
||||
}
|
||||
|
||||
DBA::insert('term', [
|
||||
'uid' => $item['uid'],
|
||||
'oid' => $item_id,
|
||||
'otype' => self::OBJECT_TYPE_POST,
|
||||
'type' => $type,
|
||||
'term' => substr($term, 0, 255),
|
||||
'url' => $link,
|
||||
'guid' => $item['guid'],
|
||||
'created' => $item['created'],
|
||||
'received' => $item['received'],
|
||||
'global' => $global
|
||||
]);
|
||||
|
||||
// Search for mentions
|
||||
if (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION)
|
||||
&& (
|
||||
strpos($link, $profile_base_friendica) !== false
|
||||
|| strpos($link, $profile_base_diaspora) !== false
|
||||
)
|
||||
) {
|
||||
$users_stmt = DBA::p("SELECT `uid` FROM `contact` WHERE self AND (`url` = ? OR `nurl` = ?)", $link, $link);
|
||||
$users = DBA::toArray($users_stmt);
|
||||
foreach ($users AS $user) {
|
||||
if ($user['uid'] == $item['uid']) {
|
||||
/// @todo This function is called from Item::update - so we mustn't call that function here
|
||||
DBA::update('item', ['mention' => true], ['id' => $item_id]);
|
||||
DBA::update('thread', ['mention' => true], ['iid' => $item['parent']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts new terms for the provided item ID based on the legacy item.file field BBCode content.
|
||||
* Deletes all previous file terms for the same item ID.
|
||||
|
@ -423,98 +112,4 @@ class Term
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param array $item
|
||||
* @return array
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function populateTagsFromItem(&$item)
|
||||
{
|
||||
$return = [
|
||||
'tags' => [],
|
||||
'hashtags' => [],
|
||||
'mentions' => [],
|
||||
'implicit_mentions' => [],
|
||||
];
|
||||
|
||||
$searchpath = DI::baseUrl() . "/search?tag=";
|
||||
|
||||
$taglist = DBA::select(
|
||||
'term',
|
||||
['type', 'term', 'url'],
|
||||
['otype' => self::OBJECT_TYPE_POST, 'oid' => $item['id'], 'type' => [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION]],
|
||||
['order' => ['tid']]
|
||||
);
|
||||
while ($tag = DBA::fetch($taglist)) {
|
||||
if ($tag['url'] == '') {
|
||||
$tag['url'] = $searchpath . rawurlencode($tag['term']);
|
||||
}
|
||||
|
||||
$orig_tag = $tag['url'];
|
||||
|
||||
$prefix = self::TAG_CHARACTER[$tag['type']];
|
||||
switch($tag['type']) {
|
||||
case self::HASHTAG:
|
||||
if ($orig_tag != $tag['url']) {
|
||||
$item['body'] = str_replace($orig_tag, $tag['url'], $item['body']);
|
||||
}
|
||||
|
||||
$return['hashtags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['term']) . '</a>';
|
||||
$return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['term']) . '</a>';
|
||||
break;
|
||||
case self::MENTION:
|
||||
$tag['url'] = Contact::magicLink($tag['url']);
|
||||
$return['mentions'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['term']) . '</a>';
|
||||
$return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['term']) . '</a>';
|
||||
break;
|
||||
case self::IMPLICIT_MENTION:
|
||||
$return['implicit_mentions'][] = $prefix . $tag['term'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
DBA::close($taglist);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete tags of the specific type(s) from an item
|
||||
*
|
||||
* @param int $item_id
|
||||
* @param int|array $type
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function deleteByItemId($item_id, $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION])
|
||||
{
|
||||
if (empty($item_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up all tags
|
||||
DBA::delete('term', ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => $type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided tag is of one of the provided term types.
|
||||
*
|
||||
* @param string $tag
|
||||
* @param int ...$types
|
||||
* @return bool
|
||||
*/
|
||||
public static function isType($tag, ...$types)
|
||||
{
|
||||
$tag_chars = [];
|
||||
foreach ($types as $type) {
|
||||
if (array_key_exists($type, self::TAG_CHARACTER)) {
|
||||
$tag_chars[] = self::TAG_CHARACTER[$type];
|
||||
}
|
||||
}
|
||||
|
||||
return Strings::startsWith($tag, $tag_chars);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ use Friendica\Core\Hook;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
|
||||
class UserItem
|
||||
{
|
||||
|
@ -50,7 +50,7 @@ class UserItem
|
|||
*/
|
||||
public static function setNotification(int $iid)
|
||||
{
|
||||
$fields = ['id', 'uid', 'body', 'parent', 'gravity', 'tag', 'contact-id', 'thr-parent', 'parent-uri', 'author-id'];
|
||||
$fields = ['id', 'uri-id', 'uid', 'body', 'parent', 'gravity', 'tag', 'contact-id', 'thr-parent', 'parent-uri', 'author-id'];
|
||||
$item = Item::selectFirst($fields, ['id' => $iid, 'origin' => false]);
|
||||
if (!DBA::isResult($item)) {
|
||||
return;
|
||||
|
@ -207,7 +207,7 @@ class UserItem
|
|||
}
|
||||
|
||||
// Or the contact is a mentioned forum
|
||||
$tags = DBA::select('term', ['url'], ['otype' => Term::OBJECT_TYPE_POST, 'oid' => $item['id'], 'type' => Term::MENTION, 'uid' => $uid]);
|
||||
$tags = DBA::select('tag-view', ['url'], ['uri-id' => $item['uri-id'], 'type' => [Tag::MENTION, Tag::EXCLUSIVE_MENTION]]);
|
||||
while ($tag = DBA::fetch($tags)) {
|
||||
$condition = ['nurl' => Strings::normaliseLink($tag['url']), 'uid' => $uid, 'notify_new_posts' => true, 'contact-type' => Contact::TYPE_COMMUNITY];
|
||||
if (DBA::exists('contact', $condition)) {
|
||||
|
@ -227,9 +227,10 @@ class UserItem
|
|||
*/
|
||||
private static function checkImplicitMention(array $item, array $profiles)
|
||||
{
|
||||
foreach ($profiles AS $profile) {
|
||||
if (strpos($item['tag'], '=' . $profile.']') || strpos($item['body'], '=' . $profile . ']')) {
|
||||
if (strpos($item['body'], $profile) === false) {
|
||||
$mentions = Tag::getByURIId($item['uri-id'], [Tag::IMPLICIT_MENTION]);
|
||||
foreach ($mentions as $mention) {
|
||||
foreach ($profiles as $profile) {
|
||||
if (Strings::compareLink($profile, $mention['url'])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -246,9 +247,10 @@ class UserItem
|
|||
*/
|
||||
private static function checkExplicitMention(array $item, array $profiles)
|
||||
{
|
||||
foreach ($profiles AS $profile) {
|
||||
if (strpos($item['tag'], '=' . $profile.']') || strpos($item['body'], '=' . $profile . ']')) {
|
||||
if (!(strpos($item['body'], $profile) === false)) {
|
||||
$mentions = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]);
|
||||
foreach ($mentions as $mention) {
|
||||
foreach ($profiles as $profile) {
|
||||
if (Strings::compareLink($profile, $mention['url'])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,14 +48,14 @@ class Source extends BaseAdmin
|
|||
$item_id = '';
|
||||
$terms = [];
|
||||
if (!empty($guid)) {
|
||||
$item = Model\Item::selectFirst(['id', 'guid', 'uri'], ['guid' => $guid]);
|
||||
$item = Model\Item::selectFirst(['id', 'uri-id', 'guid', 'uri'], ['guid' => $guid]);
|
||||
|
||||
$conversation = Model\Conversation::getByItemUri($item['uri']);
|
||||
|
||||
$item_id = $item['id'];
|
||||
$item_uri = $item['uri'];
|
||||
$source = $conversation['source'];
|
||||
$terms = Model\Term::tagArrayFromItemId($item['id'], [Model\Term::HASHTAG, Model\Term::MENTION, Model\Term::IMPLICIT_MENTION]);
|
||||
$terms = Model\Tag::getByURIId($item['uri-id'], [Model\Tag::HASHTAG, Model\Tag::MENTION, Model\Tag::IMPLICIT_MENTION]);
|
||||
}
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('admin/item/source.tpl');
|
||||
|
|
|
@ -25,14 +25,12 @@ use Friendica\BaseModule;
|
|||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Model\Term;
|
||||
|
||||
/**
|
||||
* Hashtag module.
|
||||
*/
|
||||
class Hashtag extends BaseModule
|
||||
{
|
||||
|
||||
public static function content(array $parameters = [])
|
||||
{
|
||||
$result = [];
|
||||
|
@ -42,12 +40,9 @@ class Hashtag extends BaseModule
|
|||
System::jsonExit($result);
|
||||
}
|
||||
|
||||
$taglist = DBA::p("SELECT DISTINCT(`term`) FROM `term` WHERE `term` LIKE ? AND `type` = ? ORDER BY `term`",
|
||||
$t . '%',
|
||||
intval(Term::HASHTAG)
|
||||
);
|
||||
$taglist = DBA::select('tag', ['name'], ["`name` LIKE ?", $t . "%"], ['order' => ['name'], 'limit' => 100]);
|
||||
while ($tag = DBA::fetch($taglist)) {
|
||||
$result[] = ['text' => $tag['term']];
|
||||
$result[] = ['text' => $tag['name']];
|
||||
}
|
||||
DBA::close($taglist);
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ use Friendica\Database\DBA;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Profile as ProfileModel;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\BaseProfile;
|
||||
use Friendica\Module\Security\Login;
|
||||
|
@ -184,7 +184,7 @@ class Profile extends BaseProfile
|
|||
foreach (explode(',', $a->profile['pub_keywords']) as $tag_label) {
|
||||
$tags[] = [
|
||||
'url' => '/search?tag=' . $tag_label,
|
||||
'label' => Term::TAG_CHARACTER[Term::HASHTAG] . $tag_label,
|
||||
'label' => Tag::TAG_CHARACTER[Tag::HASHTAG] . $tag_label,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -147,8 +147,8 @@ class Status extends BaseProfile
|
|||
}
|
||||
|
||||
if (!empty($hashtags)) {
|
||||
$sql_post_table .= sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
|
||||
DBA::escape(Strings::protectSprintf($hashtags)), intval(Term::OBJECT_TYPE_POST), intval(Term::HASHTAG), intval($a->profile['uid']));
|
||||
$sql_post_table .= sprintf("INNER JOIN (SELECT `uri-id` FROM `tag-search-view` WHERE `name` = '%s' AND `uid` = %d ORDER BY `uri-id` DESC) AS `tag-search` ON `item`.`uri-id` = `tag-search`.`uri-id` ",
|
||||
DBA::escape(Strings::protectSprintf($hashtags)), intval($a->profile['uid']));
|
||||
}
|
||||
|
||||
if (!empty($datequery)) {
|
||||
|
|
|
@ -33,7 +33,7 @@ use Friendica\Database\DBA;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Module\BaseSearch;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\Strings;
|
||||
|
@ -149,28 +149,11 @@ class Index extends BaseSearch
|
|||
|
||||
if ($tag) {
|
||||
Logger::info('Start tag search.', ['q' => $search]);
|
||||
$uriids = Tag::getURIIdListByTag($search, local_user(), $pager->getStart(), $pager->getItemsPerPage());
|
||||
|
||||
$condition = [
|
||||
"(`uid` = 0 OR (`uid` = ? AND NOT `global`))
|
||||
AND `otype` = ? AND `type` = ? AND `term` = ?",
|
||||
local_user(), Term::OBJECT_TYPE_POST, Term::HASHTAG, $search
|
||||
];
|
||||
$params = [
|
||||
'order' => ['received' => true],
|
||||
'limit' => [$pager->getStart(), $pager->getItemsPerPage()]
|
||||
];
|
||||
$terms = DBA::select('term', ['oid'], $condition, $params);
|
||||
|
||||
$itemids = [];
|
||||
while ($term = DBA::fetch($terms)) {
|
||||
$itemids[] = $term['oid'];
|
||||
}
|
||||
|
||||
DBA::close($terms);
|
||||
|
||||
if (!empty($itemids)) {
|
||||
$params = ['order' => ['id' => true]];
|
||||
$items = Item::selectForUser(local_user(), [], ['id' => $itemids], $params);
|
||||
if (!empty($uriids)) {
|
||||
$params = ['order' => ['id' => true], 'group_by' => ['uri-id']];
|
||||
$items = Item::selectForUser(local_user(), [], ['uri-id' => $uriids], $params);
|
||||
$r = Item::inArray($items);
|
||||
} else {
|
||||
$r = [];
|
||||
|
|
|
@ -33,7 +33,7 @@ use Friendica\Database\DBA;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Util\Crypto;
|
||||
|
@ -390,7 +390,7 @@ class Post
|
|||
$buttons["like"] = false;
|
||||
}
|
||||
|
||||
$tags = Term::populateTagsFromItem($item);
|
||||
$tags = Tag::populateFromItem($item);
|
||||
|
||||
$ago = Temporal::getRelativeDate($item['created']);
|
||||
$ago_received = Temporal::getRelativeDate($item['received']);
|
||||
|
@ -860,7 +860,7 @@ class Post
|
|||
return '';
|
||||
}
|
||||
|
||||
$item = Item::selectFirst(['author-addr'], ['id' => $this->getId()]);
|
||||
$item = Item::selectFirst(['author-addr', 'uri-id'], ['id' => $this->getId()]);
|
||||
if (!DBA::isResult($item) || empty($item['author-addr'])) {
|
||||
// Should not happen
|
||||
return '';
|
||||
|
@ -872,7 +872,7 @@ class Post
|
|||
$text = '';
|
||||
}
|
||||
|
||||
$terms = Term::tagArrayFromItemId($this->getId(), [Term::MENTION, Term::IMPLICIT_MENTION]);
|
||||
$terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
|
||||
foreach ($terms as $term) {
|
||||
$profile = Contact::getDetailsByURL($term['url']);
|
||||
if (!empty($profile['addr']) && ((($profile['contact-type'] ?? '') ?: Contact::TYPE_UNKNOWN) != Contact::TYPE_COMMUNITY) &&
|
||||
|
|
|
@ -35,7 +35,6 @@ use Friendica\Model\Item;
|
|||
use Friendica\Model\ItemURI;
|
||||
use Friendica\Model\Mail;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
|
@ -388,7 +387,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;
|
||||
|
@ -1016,7 +1015,7 @@ class Processor
|
|||
return [];
|
||||
}
|
||||
|
||||
$parent_terms = Term::tagArrayFromItemId($parent['id'], [Term::MENTION, Term::IMPLICIT_MENTION]);
|
||||
$parent_terms = Tag::getByURIId($parent['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
|
||||
|
||||
$parent_author = Contact::getDetailsByURL($parent['author-link'], 0);
|
||||
|
||||
|
@ -1084,8 +1083,8 @@ class Processor
|
|||
foreach ($activity_tags as $index => $tag) {
|
||||
if (in_array($tag['href'], $potential_mentions)) {
|
||||
$activity_tags[$index]['name'] = preg_replace(
|
||||
'/' . preg_quote(Term::TAG_CHARACTER[Term::MENTION], '/') . '/',
|
||||
Term::TAG_CHARACTER[Term::IMPLICIT_MENTION],
|
||||
'/' . preg_quote(Tag::TAG_CHARACTER[Tag::MENTION], '/') . '/',
|
||||
Tag::TAG_CHARACTER[Tag::IMPLICIT_MENTION],
|
||||
$activity_tags[$index]['name'],
|
||||
1
|
||||
);
|
||||
|
|
|
@ -34,9 +34,10 @@ use Friendica\Model\APContact;
|
|||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Conversation;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\ItemURI;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\Photo;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
|
@ -407,7 +408,7 @@ class Transmitter
|
|||
$actor_profile = APContact::getByURL($item['author-link']);
|
||||
}
|
||||
|
||||
$terms = Term::tagArrayFromItemId($item['id'], [Term::MENTION, Term::IMPLICIT_MENTION]);
|
||||
$terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
|
||||
|
||||
if ($item['private'] != Item::PRIVATE) {
|
||||
// Directly mention the original author upon a quoted reshare.
|
||||
|
@ -701,6 +702,8 @@ class Transmitter
|
|||
return [];
|
||||
}
|
||||
|
||||
$mail['uri-id'] = ItemURI::insert(['uri' => $mail['uri'], 'guid' => $mail['guid']]);
|
||||
|
||||
$reply = DBA::selectFirst('mail', ['uri'], ['parent-uri' => $mail['parent-uri'], 'reply' => false]);
|
||||
|
||||
// Making the post more compatible for Mastodon by:
|
||||
|
@ -1009,12 +1012,12 @@ class Transmitter
|
|||
{
|
||||
$tags = [];
|
||||
|
||||
$terms = Term::tagArrayFromItemId($item['id'], [Term::HASHTAG, Term::MENTION, Term::IMPLICIT_MENTION]);
|
||||
$terms = Tag::getByURIId($item['uri-id'], [Tag::HASHTAG, Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
|
||||
foreach ($terms as $term) {
|
||||
if ($term['type'] == Term::HASHTAG) {
|
||||
$url = DI::baseUrl() . '/search?tag=' . urlencode($term['term']);
|
||||
$tags[] = ['type' => 'Hashtag', 'href' => $url, 'name' => '#' . $term['term']];
|
||||
} elseif ($term['type'] == Term::MENTION || $term['type'] == Term::IMPLICIT_MENTION) {
|
||||
if ($term['type'] == Tag::HASHTAG) {
|
||||
$url = DI::baseUrl() . '/search?tag=' . urlencode($term['name']);
|
||||
$tags[] = ['type' => 'Hashtag', 'href' => $url, 'name' => '#' . $term['name']];
|
||||
} else {
|
||||
$contact = Contact::getDetailsByURL($term['url']);
|
||||
if (!empty($contact['addr'])) {
|
||||
$mention = '@' . $contact['addr'];
|
||||
|
@ -1213,15 +1216,14 @@ class Transmitter
|
|||
/**
|
||||
* Returns if the post contains sensitive content ("nsfw")
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @param integer $uri_id
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function isSensitive($item_id)
|
||||
private static function isSensitive($uri_id)
|
||||
{
|
||||
$condition = ['otype' => Term::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => Term::HASHTAG, 'term' => 'nsfw'];
|
||||
return DBA::exists('term', $condition);
|
||||
return DBA::exists('tag-view', ['uri-id' => $uri_id, 'name' => 'nsfw']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1303,7 +1305,7 @@ class Transmitter
|
|||
|
||||
$data['url'] = $item['plink'];
|
||||
$data['attributedTo'] = $item['author-link'];
|
||||
$data['sensitive'] = self::isSensitive($item['id']);
|
||||
$data['sensitive'] = self::isSensitive($item['uri-id']);
|
||||
$data['context'] = self::fetchContextURLForItem($item);
|
||||
|
||||
if (!empty($item['title'])) {
|
||||
|
|
|
@ -1080,21 +1080,15 @@ class DFRN
|
|||
$entry->appendChild($actarg);
|
||||
}
|
||||
|
||||
$tags = Item::getFeedTags($item);
|
||||
$tags = Tag::getByURIId($item['uri-id']);
|
||||
|
||||
/// @TODO Combine this with similar below if() block?
|
||||
if (count($tags)) {
|
||||
foreach ($tags as $t) {
|
||||
if (($type != 'html') || ($t[0] != "@")) {
|
||||
XML::addElement($doc, $entry, "category", "", ["scheme" => "X-DFRN:".$t[0].":".$t[1], "term" => $t[2]]);
|
||||
foreach ($tags as $tag) {
|
||||
if (($type != 'html') || ($tag['type'] == Tag::HASHTAG)) {
|
||||
XML::addElement($doc, $entry, "category", "", ["scheme" => "X-DFRN:" . Tag::TAG_CHARACTER[$tag['type']] . ":" . $tag['url'], "term" => $tag['name']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($tags)) {
|
||||
foreach ($tags as $t) {
|
||||
if ($t[0] == "@") {
|
||||
$mentioned[$t[1]] = $t[1];
|
||||
if ($tag['type'] != Tag::HASHTAG) {
|
||||
$mentioned[$tag['url']] = $tag['url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2238,11 +2232,6 @@ class DFRN
|
|||
// extract tag, if not duplicate, add to parent item
|
||||
if ($xo->content) {
|
||||
Tag::store($item_tag['uri-id'], Tag::HASHTAG, $xo->content);
|
||||
|
||||
if (!stristr($item_tag["tag"], trim($xo->content))) {
|
||||
$tag = $item_tag["tag"] . (strlen($item_tag["tag"]) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
|
||||
Item::update(['tag' => $tag], ['id' => $item_tag["id"]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2440,17 +2429,7 @@ class DFRN
|
|||
if (($term != "") && ($scheme != "")) {
|
||||
$parts = explode(":", $scheme);
|
||||
if ((count($parts) >= 4) && (array_shift($parts) == "X-DFRN")) {
|
||||
$termhash = array_shift($parts);
|
||||
$termurl = implode(":", $parts);
|
||||
|
||||
if (!empty($item["tag"])) {
|
||||
$item["tag"] .= ",";
|
||||
} else {
|
||||
$item["tag"] = "";
|
||||
}
|
||||
|
||||
$item["tag"] .= $termhash . "[url=" . $termurl . "]" . $term . "[/url]";
|
||||
|
||||
Tag::store($item['uri-id'], Tag::IMPLICIT_MENTION, $term, $termurl);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,11 +36,9 @@ 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\Profile;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Util\Crypto;
|
||||
|
@ -114,7 +112,7 @@ class Diaspora
|
|||
|
||||
if (DI::config()->get("system", "relay_directly", false)) {
|
||||
// We distribute our stuff based on the parent to ensure that the thread will be complete
|
||||
$parent = Item::selectFirst(['parent'], ['id' => $item_id]);
|
||||
$parent = Item::selectFirst(['uri-id'], ['id' => $item_id]);
|
||||
if (!DBA::isResult($parent)) {
|
||||
return;
|
||||
}
|
||||
|
@ -127,11 +125,10 @@ class Diaspora
|
|||
DBA::close($servers);
|
||||
|
||||
// All tags of the current post
|
||||
$condition = ['otype' => Term::OBJECT_TYPE_POST, 'type' => Term::HASHTAG, 'oid' => $parent['parent']];
|
||||
$tags = DBA::select('term', ['term'], $condition);
|
||||
$tags = DBA::select('tag-view', ['name'], ['uri-id' => $parent['uri-id'], 'type' => Tag::HASHTAG]);
|
||||
$taglist = [];
|
||||
while ($tag = DBA::fetch($tags)) {
|
||||
$taglist[] = $tag['term'];
|
||||
$taglist[] = $tag['name'];
|
||||
}
|
||||
DBA::close($tags);
|
||||
|
||||
|
@ -255,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 $contacts The previously fetched contacts
|
||||
* @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;
|
||||
|
@ -278,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;
|
||||
}
|
||||
|
@ -2254,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;
|
||||
}
|
||||
|
||||
|
@ -2275,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);
|
||||
|
@ -2585,7 +2614,7 @@ class Diaspora
|
|||
}
|
||||
|
||||
// Do we already have this item?
|
||||
$fields = ['body', 'title', 'attach', 'tag', 'app', 'created', 'object-type', 'uri', 'guid',
|
||||
$fields = ['body', 'title', 'attach', 'app', 'created', 'object-type', 'uri', 'guid',
|
||||
'author-name', 'author-link', 'author-avatar'];
|
||||
$condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]];
|
||||
$item = Item::selectFirst($fields, $condition);
|
||||
|
@ -2629,7 +2658,7 @@ class Diaspora
|
|||
}
|
||||
|
||||
if ($stored) {
|
||||
$fields = ['body', 'title', 'attach', 'tag', 'app', 'created', 'object-type', 'uri', 'guid',
|
||||
$fields = ['body', 'title', 'attach', 'app', 'created', 'object-type', 'uri', 'guid',
|
||||
'author-name', 'author-link', 'author-avatar'];
|
||||
$condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]];
|
||||
$item = Item::selectFirst($fields, $condition);
|
||||
|
@ -2775,7 +2804,6 @@ class Diaspora
|
|||
|
||||
Tag::storeFromBody($datarray['uri-id'], $datarray["body"]);
|
||||
|
||||
$datarray["tag"] = $original_item["tag"];
|
||||
$datarray["attach"] = $original_item["attach"];
|
||||
$datarray["app"] = $original_item["app"];
|
||||
|
||||
|
|
|
@ -385,18 +385,10 @@ class Feed {
|
|||
$item["attach"] .= '[attach]href="' . $href . '" length="' . $length . '" type="' . $type . '"[/attach]';
|
||||
}
|
||||
|
||||
$tags = '';
|
||||
$taglist = [];
|
||||
$categories = $xpath->query("category", $entry);
|
||||
foreach ($categories AS $category) {
|
||||
$hashtag = $category->nodeValue;
|
||||
if ($tags != '') {
|
||||
$tags .= ', ';
|
||||
}
|
||||
|
||||
$taglink = "#[url=" . DI::baseUrl() . "/search?tag=" . $hashtag . "]" . $hashtag . "[/url]";
|
||||
$tags .= $taglink;
|
||||
$taglist[] = $hashtag;
|
||||
$taglist[] = $category->nodeValue;
|
||||
}
|
||||
|
||||
$body = trim(XML::getFirstNodeValue($xpath, 'atom:content/text()', $entry));
|
||||
|
@ -477,7 +469,6 @@ class Feed {
|
|||
// We always strip the title since it will be added in the page information
|
||||
$item["title"] = "";
|
||||
$item["body"] = $item["body"] . add_page_info($item["plink"], false, $preview, ($contact["fetch_further_information"] == 2), $contact["ffi_keyword_blacklist"]);
|
||||
$item["tag"] = add_page_keywords($item["plink"], $preview, ($contact["fetch_further_information"] == 2), $contact["ffi_keyword_blacklist"]);
|
||||
$taglist = get_page_keywords($item["plink"], $preview, ($contact["fetch_further_information"] == 2), $contact["ffi_keyword_blacklist"]);
|
||||
$item["object-type"] = Activity\ObjectType::BOOKMARK;
|
||||
unset($item["attach"]);
|
||||
|
@ -487,14 +478,10 @@ class Feed {
|
|||
}
|
||||
|
||||
if (!empty($contact["fetch_further_information"]) && ($contact["fetch_further_information"] == 3)) {
|
||||
if (!empty($tags)) {
|
||||
$item["tag"] = $tags;
|
||||
} else {
|
||||
// @todo $preview is never set in this case, is it intended? - @MrPetovan 2018-02-13
|
||||
$item["tag"] = add_page_keywords($item["plink"], $preview, true, $contact["ffi_keyword_blacklist"]);
|
||||
if (empty($taglist)) {
|
||||
$taglist = get_page_keywords($item["plink"], $preview, true, $contact["ffi_keyword_blacklist"]);
|
||||
}
|
||||
$item["body"] .= "\n" . $item['tag'];
|
||||
$item["body"] .= "\n" . self::tagToString($taglist);
|
||||
} else {
|
||||
$taglist = [];
|
||||
}
|
||||
|
@ -540,6 +527,27 @@ class Feed {
|
|||
return ["header" => $author, "items" => $items];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a tag array to a tag string
|
||||
*
|
||||
* @param array $tags
|
||||
* @return string tag string
|
||||
*/
|
||||
private static function tagToString(array $tags)
|
||||
{
|
||||
$tagstr = '';
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if ($tagstr != "") {
|
||||
$tagstr .= ", ";
|
||||
}
|
||||
|
||||
$tagstr .= "#[url=" . DI::baseUrl() . "/search?tag=" . urlencode($tag) . "]" . $tag . "[/url]";
|
||||
}
|
||||
|
||||
return $tagstr;
|
||||
}
|
||||
|
||||
private static function titleIsBody($title, $body)
|
||||
{
|
||||
$title = strip_tags($title);
|
||||
|
|
|
@ -2081,14 +2081,9 @@ class OStatus
|
|||
XML::addElement($doc, $entry, "ostatus:conversation", $conversation_uri, $attributes);
|
||||
}
|
||||
|
||||
$tags = item::getFeedTags($item);
|
||||
|
||||
if (count($tags)) {
|
||||
foreach ($tags as $t) {
|
||||
if ($t[0] == "@") {
|
||||
$mentioned[$t[1]] = $t[1];
|
||||
}
|
||||
}
|
||||
// uri-id isn't present for follow entry pseudo-items
|
||||
foreach (Tag::getByURIId($item['uri-id'] ?? 0) as $tag) {
|
||||
$mentioned[$tag['url']] = $tag['url'];
|
||||
}
|
||||
|
||||
// Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS)
|
||||
|
@ -2138,9 +2133,9 @@ class OStatus
|
|||
}
|
||||
|
||||
if (count($tags)) {
|
||||
foreach ($tags as $t) {
|
||||
if ($t[0] != "@") {
|
||||
XML::addElement($doc, $entry, "category", "", ["term" => $t[2]]);
|
||||
foreach ($tags as $tag) {
|
||||
if ($tag['type'] == Tag::HASHTAG) {
|
||||
XML::addElement($doc, $entry, "category", "", ["term" => $tag['name']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -242,16 +242,16 @@ class Delivery
|
|||
/**
|
||||
* Increased the "failed" counter in the item delivery data
|
||||
*
|
||||
* @param string $cmd Command
|
||||
* @param integer $id Item id
|
||||
* @param string $cmd Command
|
||||
* @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]);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ 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\User;
|
||||
use Friendica\Network\Probe;
|
||||
|
@ -441,7 +441,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 +573,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 +595,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
use Friendica\Database\DBA;
|
||||
|
||||
if (!defined('DB_UPDATE_VERSION')) {
|
||||
define('DB_UPDATE_VERSION', 1341);
|
||||
define('DB_UPDATE_VERSION', 1345);
|
||||
}
|
||||
|
||||
return [
|
||||
|
@ -754,6 +754,7 @@ return [
|
|||
"icid" => ["icid"],
|
||||
"iaid" => ["iaid"],
|
||||
"psid_wall" => ["psid", "wall"],
|
||||
"uri-id" => ["uri-id"],
|
||||
]
|
||||
],
|
||||
"item-activity" => [
|
||||
|
@ -803,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" => [
|
||||
|
@ -926,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" => ""],
|
||||
|
@ -944,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"],
|
||||
|
@ -1305,6 +1289,25 @@ return [
|
|||
"url" => ["url"]
|
||||
]
|
||||
],
|
||||
"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" => [
|
||||
|
|
|
@ -186,20 +186,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"],
|
||||
|
@ -220,6 +206,25 @@ return [
|
|||
INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid`
|
||||
INNER JOIN `user` ON `register`.`uid` = `user`.`uid`"
|
||||
],
|
||||
"tag-search-view" => [
|
||||
"fields" => [
|
||||
"uri-id" => ["post-tag", "uri-id"],
|
||||
"iid" => ["item", "id"],
|
||||
"uri" => ["item", "uri"],
|
||||
"guid" => ["item", "guid"],
|
||||
"uid" => ["item", "uid"],
|
||||
"private" => ["item", "private"],
|
||||
"wall" => ["item", "wall"],
|
||||
"origin" => ["item", "origin"],
|
||||
"gravity" => ["item", "gravity"],
|
||||
"received" => ["item", "received"],
|
||||
"name" => ["tag", "name"],
|
||||
],
|
||||
"query" => "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"
|
||||
],
|
||||
"workerqueue-view" => [
|
||||
"fields" => [
|
||||
"pid" => ["process", "pid"],
|
||||
|
|
|
@ -27,7 +27,7 @@ return [
|
|||
'photo',
|
||||
'workerqueue',
|
||||
'mail',
|
||||
'item-delivery-data',
|
||||
'post-delivery-data',
|
||||
// Base test config to avoid notice messages
|
||||
'config' => [
|
||||
[
|
||||
|
@ -103,6 +103,7 @@ return [
|
|||
'item' => [
|
||||
[
|
||||
'id' => 1,
|
||||
'uri-id' => 1,
|
||||
'visible' => 1,
|
||||
'contact-id' => 42,
|
||||
'author-id' => 42,
|
||||
|
@ -123,6 +124,7 @@ return [
|
|||
],
|
||||
[
|
||||
'id' => 2,
|
||||
'uri-id' => 2,
|
||||
'visible' => 1,
|
||||
'contact-id' => 42,
|
||||
'author-id' => 42,
|
||||
|
@ -140,6 +142,7 @@ return [
|
|||
[
|
||||
|
||||
'id' => 3,
|
||||
'uri-id' => 3,
|
||||
'visible' => 1,
|
||||
'contact-id' => 43,
|
||||
'author-id' => 43,
|
||||
|
@ -156,6 +159,7 @@ return [
|
|||
],
|
||||
[
|
||||
'id' => 4,
|
||||
'uri-id' => 4,
|
||||
'visible' => 1,
|
||||
'contact-id' => 44,
|
||||
'author-id' => 44,
|
||||
|
@ -173,6 +177,7 @@ return [
|
|||
[
|
||||
|
||||
'id' => 5,
|
||||
'uri-id' => 5,
|
||||
'visible' => 1,
|
||||
'contact-id' => 42,
|
||||
'author-id' => 42,
|
||||
|
@ -193,6 +198,7 @@ return [
|
|||
],
|
||||
[
|
||||
'id' => 6,
|
||||
'uri-id' => 6,
|
||||
'visible' => 1,
|
||||
'contact-id' => 44,
|
||||
'author-id' => 44,
|
||||
|
|
|
@ -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.
|
||||
|
|
@ -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).
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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; }
|
|
@ -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
|
@ -0,0 +1,3 @@
|
|||
@import 'variables';
|
||||
@import 'mixins';
|
||||
@import 'themes';
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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(' ');
|
||||
}
|
||||
};
|
|
@ -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;
|
|
@ -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;
|
|
@ -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();
|
||||
};
|
||||
})();
|
|
@ -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
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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'
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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)];
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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'));
|
||||
}
|
||||
|
||||
};
|
|
@ -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', '');
|
||||
};
|
|
@ -45,7 +45,7 @@
|
|||
{{if $term.type == 8}}Implicit Mention{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
{{$term.term}}
|
||||
{{$term.name}}
|
||||
</td>
|
||||
<td>
|
||||
{{$term.url}}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue
Block a user