diff --git a/output/main.js b/output/main.js index b6d48d7..748d5d4 100644 --- a/output/main.js +++ b/output/main.js @@ -170,11 +170,10 @@ async function onLoad() { * @param {boolean} [inMainView] Whether to apply styles for tags in outside view. * @returns HTMLElement */ -const tagBody = ({text, type, description}, inMainView = false) => element.span({ - // todo: truncate - textContent: text, +const tagBody = ({text, type, description = ""}, inMainView = false) => element.span({ + textContent: text.slice(0, 16), className: `room-label room-label-view-${inMainView ? 'main' : 'secondary'} room-label-${type} badge`, - title: description + title: description || `Tag: ${text}` }); /** diff --git a/php/servers/servers-rooms.php b/php/servers/servers-rooms.php index b77dc99..f3d0722 100644 --- a/php/servers/servers-rooms.php +++ b/php/servers/servers-rooms.php @@ -111,8 +111,14 @@ // Custom properties /** - * @var string[] $tags - * String tags applied to room by creator or submitter. + * @var string[] $string_tags + * String tags originally applied to room. + */ + private array $string_tags = []; + + /** + * @var CommunityTag[] $tags + * Tag-based information from multiple sources. * * Custom attribute. */ @@ -126,7 +132,11 @@ */ private ?string $language_flag; - private function __construct(CommunityServer $server, array $details) { + private function __construct( + CommunityServer $server, + array $details, + bool $suppress_processing = false + ) { $this->server = $server; $this->active_users = $details['active_users']; $this->active_users_cutoff = $details['active_users_cutoff']; @@ -142,15 +152,25 @@ $this->read = $details['read']; $this->write = $details['write']; $this->upload = $details['upload']; + $this->string_tags = $details['string_tags'] ?? []; + + if ($suppress_processing) return; $this->extract_tags_from_description(); + $this->tags = isset($details['tags']) + ? CommunityTag::from_details_array($details['tags']) + : $this->get_derived_tags(); } /** - * Regular expression matching tags specified in the Community description. + * Create incomplete CommunityRoom instance from intermediate data. */ - private const DESCRIPTION_TAGS_SPECIFICATION = '/(#[^#()@., ]+(?:,?\s*|\s+|$))+\s*.?$/'; - + public static function _from_intermediate_data( + CommunityServer $server, + array $details + ) { + return new CommunityRoom($server, $details, suppress_processing: true); + } /** * Return an optional Unicode emoji of region matching @@ -178,6 +198,11 @@ return ""; } + /** + * Regular expression matching tags specified in the Community description. + */ + private const DESCRIPTION_TAGS_SPECIFICATION = '/(#[^#()@., ]+(?:,?\s*|\s+|$))+\s*.?$/'; + /** * Pre-processes SOGS data by treating description-trailing hashtags as room tags. */ @@ -218,9 +243,6 @@ public function jsonSerialize(): array { $details = get_object_vars($this); unset($details['server']); - $details['tags'] = $this->get_raw_tags(); - $details['tags_custom'] = $this->get_derived_tags(); - $details['language_flag'] = $this->get_language_flag(); return $details; } @@ -232,12 +254,13 @@ unset($details['server']); unset($details['tags']); unset($details['language_flag']); + unset($details['string_tags']); return array( "room" => $details, "room_extra" => array( "join_url" => $this->get_join_url(), - "language_flag" => $this->get_language_flag(), - "tags" => $this->get_raw_tags() + "language_flag" => $this->language_flag, + "tags" => $this->string_tags ) ); } @@ -247,15 +270,7 @@ * @param CommunityServer $server */ public static function from_details($server, array $details) { - $room = new CommunityRoom($server, $details); - $has_tags = isset($details['tags']); - if ($has_tags) { - $room->add_tags($details['tags']); - } - if (isset($details['language_flag'])) { - $room->language_flag = $details['language_flag']; - } - return $room; + return new CommunityRoom($server, $details); } /** @@ -394,11 +409,17 @@ * @param string[] $tags */ public function add_tags(array $tags) { + $new_string_tags = []; foreach ($tags as $tag) { - if (!$this->parse_language_tag($tag)) { - $this->tags[] = $tag; - } + $this->string_tags[] = $tag; + if ($this->parse_language_tag($tag)) continue; + $new_string_tags[] = $tag; } + $new_tags = CommunityTag::from_user_tags($new_string_tags); + $this->tags = CommunityTag::dedupe_tags([ + ...$this->tags, + ...$new_tags + ]); } /** @@ -537,8 +558,8 @@ * Return the string tags associated with this Community * @return string[] Array of unique string tags. */ - private function get_raw_tags(): array { - return array_unique(array_values($this->tags)); + private function get_string_tags(): array { + return $this->string_tags; } /** @@ -553,92 +574,38 @@ */ $derived_tags = []; - $CHECK_MARK = CommunityTag::CHECK_MARK; - $WARNING = CommunityTag::WARNING_ICON; - $USERS_PER_STAFF = CommunityRoom::USERS_PER_STAFF; $USERS_PER_STAFF_WARNING = CommunityRoom::USERS_PER_STAFF_WARNING; if ($this->is_official_room()) { - $derived_tags[] = new CommunityTag( - "official", - TagType::RESERVED_TAG, - "This Community is maintained by the Session team. $CHECK_MARK" - ); + $derived_tags[] = ReservedTags::official(); } if ($this->rated_nsfw()) { - $derived_tags[] = - new CommunityTag( - "nsfw", - TagType::WARNING_TAG, - "This Community may contain adult material. $WARNING" - ); + $derived_tags[] = ReservedTags::nsfw(); } if ($this->write && $this->has_good_staff_rating()) { - $derived_tags[] = - new CommunityTag( - "moderated", - TagType::RESERVED_TAG, - "This Community has at least 1 staff per $USERS_PER_STAFF active users. $CHECK_MARK" - ); - } - - /* - if ($this->write && $this->has_poor_staff_rating()) { - $derived_tags[] = - new CommunityTag( - "not modded", - TagType::WARNING_TAG, - "This Community has less than 1 staff per $USERS_PER_STAFF_WARNING active users. $WARNING" - ); + $derived_tags[] = ReservedTags::moderated(CommunityRoom::USERS_PER_STAFF); } - */ if (!$this->write) { - $derived_tags[] = - new CommunityTag( - "read-only", - TagType::RESERVED_TAG, - "This Community is read-only." - ); + $derived_tags[] = ReservedTags::read_only(); } if ($this->write && !$this->upload) { - $derived_tags[] = - new CommunityTag( - "uploads off", - TagType::RESERVED_TAG, - "This Community does not support uploading files or link previews." - ); + $derived_tags[] = ReservedTags::no_upload_permission(); } if ($this->created && $this->created > strtotime("-4 week")) { - $derived_tags[] = - new CommunityTag( - "new", - TagType::RESERVED_TAG, - "This Community was created recently." - ); + $derived_tags[] = ReservedTags::recently_created(); } if (in_array($this->get_room_identifier(), $ROOMS_USED_BY_PROJECT)) { - $derived_tags[] = - new CommunityTag( - "we're here", - TagType::RESERVED_TAG, - "The sessioncommunities.online maintainer(s) can post updates " - . "or respond to feedback in this Community." - ); + $derived_tags[] = ReservedTags::used_by_project(); } - if (in_array("test", $this->tags) || $this->matched_by_list($TESTING_INCLUDE)) { - $derived_tags[] = - new CommunityTag( - "test", - TagType::RESERVED_TAG, - "This Community is intended for testing only." - ); + if ($this->is_testing_room()) { + $derived_tags[] = ReservedTags::testing(); } return $derived_tags; @@ -649,8 +616,7 @@ * @return CommunityTag[] Array of tags. */ function get_room_tags(): array { - $user_tags = CommunityTag::from_user_tags($this->tags, remove_redundant: true); - return [...$this->get_derived_tags(), ...$user_tags]; + return $this->tags; } } diff --git a/php/servers/tags.php b/php/servers/tags.php index bcfe531..7fda907 100644 --- a/php/servers/tags.php +++ b/php/servers/tags.php @@ -14,15 +14,15 @@ /** * Specifies custom tag added by Community maintainer or Community source. */ - const USER_TAG = 0; + const USER_TAG = 'user'; /** * Specifies basic type of tag reserved for assignment by our aggregator. */ - const RESERVED_TAG = 1; + const RESERVED_TAG = 'reserved'; /** * Specifies warning tag reserved for assignment by our aggregator. */ - const WARNING_TAG = 2; + const WARNING_TAG = 'warning'; } /** @@ -32,25 +32,24 @@ /** * Create a new CommunityTag instance. * @param string $text Text the tag should read. - * @param int $tag_type Numeric {@link TagType} value. + * @param string $tag_type {@link TagType} enumeration value. * @param string|null $description [optional] Brief explanation of tag. */ public function __construct( string $text, - int $tag_type = TagType::USER_TAG, + string $tag_type = TagType::USER_TAG, ?string $description = "" ) { - $this->text = $text; + $this->text = CommunityTag::preprocess_tag($text); $this->type = $tag_type; - $this->description = - empty($description) ? "Tag: $text" : $description; + $this->description = $description; } /** - * @var int $type + * @var string $type * Tag type as given by a {@link TagType} value. */ - public readonly int $type; + public readonly string $type; /** * @var string $text @@ -78,15 +77,30 @@ return strtolower($this->text); } + /** + * Return a lowercase text representation of the tag for use in HTML. + */ + public function get_text_sanitized(): string { + return html_sanitize($this->get_text()); + } + + /** + * Return tag description. + */ + public function get_description_sanitized(): string { + return html_sanitize($this->description ?? "Tag: $this->text"); + } + /** * Produce data used to serialize the tag. */ public function jsonSerialize(): mixed { - // Only used for passing to DOM - $details = get_object_vars($this); - $details['text'] = html_sanitize($this->get_text()); - $details['description'] = html_sanitize($details['description']); + $details = []; + $details['text'] = $this->get_text(); $details['type'] = $this->get_tag_type(); + if (!empty($this->description)) { + $details['description'] = $this->description; + } return $details; } @@ -97,30 +111,28 @@ return $tag; } - $tag = html_sanitize(html_entity_decode($tag)); + $tag = html_entity_decode($tag); if ($tag[0] == '#') { return substr($tag, 1); } - return $tag; + return strtolower($tag); } /** * @param string[] $tag_array * @return CommunityTag[] */ - private static function from_tag_array(array $tag_array) { - $tags = array_map(function(?string $tag) { - return CommunityTag::preprocess_tag($tag); - }, $tag_array); - + private static function from_string_tags(array $tag_array) { $tags = array_filter( - $tags, function(?string $tag) { + $tag_array, function(?string $tag) { return strlen($tag) != 0; } ); + $tags = CommunityTag::dedupe_tags($tags); + return array_map(function(string $tag) { return new CommunityTag($tag); }, $tags); @@ -129,38 +141,54 @@ /** * Constructs the tags given, removing any reserved tags. * @param string[] $tags - * @param bool $remove_redundant Removes duplicate and obvious tags. + * @param bool $remove_redundant Removes meaningless tags. * @return CommunityTag[] */ public static function from_user_tags( - array $tags, bool $remove_redundant = false + array $tags, + bool $remove_redundant = true ): array { - $tags_user = array_filter( + $tags_user = array_values(array_filter( $tags, function($tag) { return !CommunityTag::is_reserved_tag($tag); } - ); + )); - $tags_built = CommunityTag::from_tag_array($tags_user); + $tags_built = CommunityTag::from_string_tags($tags_user); if ($remove_redundant) { - $tags_built = CommunityTag::dedupe_tags($tags_built); - $tags_built = array_filter($tags_built, function(CommunityTag $tag) { - $text = strtolower($tag->text); - return !in_array($text, CommunityTag::REDUNDANT_TAGS); - }); + $tags_built = array_values( + array_filter($tags_built, function(CommunityTag $tag) { + return !in_array($tag->get_text(), CommunityTag::REDUNDANT_TAGS); + }) + ); } return $tags_built; } /** - * @param string[] $details_array Array of string tags. + * @param array $details Deserialized tag info. + * @return CommunityTag + */ + public static function from_details(array $details): CommunityTag { + return new CommunityTag( + $details['text'], + $details['type'], + $details['description'] ?? '' + ); + } + + + /** + * @param array[] $details_array Array of deserialized tag info. * @return CommunityTag[] */ public static function from_details_array(array $details_array): array { - return CommunityTag::from_user_tags($details_array); + return array_map(function($details) { + return CommunityTag::from_details($details); + }, $details_array); } /** @@ -168,7 +196,7 @@ * @return CommunityTag[] */ public static function dedupe_tags(array $tags) { - return array_unique($tags); + return array_values(array_unique($tags)); } /** @@ -223,11 +251,6 @@ private const REDUNDANT_TAGS = ["session"]; public const NSFW_KEYWORDS = ["nsfw", "porn", "erotic", "18+", "sex"]; - - public const CHECK_MARK = "✅"; - - public const WARNING_ICON = "⚠️"; - /** * Check whether the given user tag is reserved by our aggregator. */ @@ -249,4 +272,87 @@ return in_array(strtolower($tag), CommunityTag::HIGHLIGHTED_TAGS); } } + + class ReservedTags { + public static function official() { + $CHECK_MARK = "✅"; + + return new CommunityTag( + "official", + TagType::RESERVED_TAG, + "This Community is maintained by the Session team. $CHECK_MARK" + ); + } + + public static function nsfw() { + $WARNING_ICON = "⚠️"; + + return new CommunityTag( + "nsfw", + TagType::WARNING_TAG, + "This Community may contain adult material. $WARNING_ICON" + ); + } + + public static function moderated(int $users_per_staff) { + $CHECK_MARK = "✅"; + + return new CommunityTag( + "moderated", + TagType::RESERVED_TAG, + "This Community has at least 1 staff per $users_per_staff active users. $CHECK_MARK" + ); + } + + public static function not_modded(int $users_per_staff) { + $WARNING_ICON = "⚠️"; + + return new CommunityTag( + "not modded", + TagType::WARNING_TAG, + "This Community has less than 1 staff per $users_per_staff active users. $WARNING_ICON" + ); + } + + public static function read_only() { + return new CommunityTag( + "read-only", + TagType::RESERVED_TAG, + "This Community is read-only." + ); + } + + public static function no_upload_permission() { + return new CommunityTag( + "uploads off", + TagType::RESERVED_TAG, + "This Community does not support uploading files or link previews." + ); + } + + public static function recently_created() { + return new CommunityTag( + "new", + TagType::RESERVED_TAG, + "This Community was created recently." + ); + } + + public static function used_by_project() { + return new CommunityTag( + "we're here", + TagType::RESERVED_TAG, + "The sessioncommunities.online maintainer(s) can post updates " + . "or respond to feedback in this Community." + ); + } + + public static function testing() { + return new CommunityTag( + "test", + TagType::RESERVED_TAG, + "This Community is intended for testing only." + ); + } + } ?> diff --git a/sites/+components/tbl-communities.php b/sites/+components/tbl-communities.php index 9919f28..6163c4e 100644 --- a/sites/+components/tbl-communities.php +++ b/sites/+components/tbl-communities.php @@ -103,9 +103,9 @@ get_room_tags() as $tag): if (CommunityTag::is_showcased_tag($tag->text)): ?> get_text(), 16) + truncate($tag->get_text_sanitized(), 16) ?>