import discourseComputed from "discourse-common/utils/decorators"; import { get } from "@ember/object"; import { ajax } from "discourse/lib/ajax"; import RestModel from "discourse/models/rest"; import { on } from "discourse-common/utils/decorators"; import PermissionType from "discourse/models/permission-type"; import { NotificationLevels } from "discourse/lib/notification-levels"; import deprecated from "discourse-common/lib/deprecated"; import Site from "discourse/models/site"; const Category = RestModel.extend({ permissions: null, @on("init") setupGroupsAndPermissions() { const availableGroups = this.available_groups; if (!availableGroups) { return; } this.set("availableGroups", availableGroups); const groupPermissions = this.group_permissions; if (groupPermissions) { this.set( "permissions", groupPermissions.map(elem => { availableGroups.removeObject(elem.group_name); return { group_name: elem.group_name, permission: PermissionType.create({ id: elem.permission_type }) }; }) ); } }, @on("init") setupRequiredTagGroups() { if (this.required_tag_group_name) { this.set("required_tag_groups", [this.required_tag_group_name]); } }, @discourseComputed availablePermissions() { return [ PermissionType.create({ id: PermissionType.FULL }), PermissionType.create({ id: PermissionType.CREATE_POST }), PermissionType.create({ id: PermissionType.READONLY }) ]; }, @discourseComputed("id") searchContext(id) { return { type: "category", id, category: this }; }, @discourseComputed("parentCategory.ancestors") ancestors(parentAncestors) { return [...(parentAncestors || []), this]; }, @discourseComputed("parentCategory.level") level(parentLevel) { return (parentLevel || -1) + 1; }, @discourseComputed("subcategories") isParent(subcategories) { return subcategories && subcategories.length > 0; }, @discourseComputed("subcategories") isGrandParent(subcategories) { return ( subcategories && subcategories.some( cat => cat.subcategories && cat.subcategories.length > 0 ) ); }, @discourseComputed("notification_level") isMuted(notificationLevel) { return notificationLevel === NotificationLevels.MUTED; }, @discourseComputed("notification_level") notificationLevelString(notificationLevel) { // Get the key from the value const notificationLevelString = Object.keys(NotificationLevels).find( key => NotificationLevels[key] === notificationLevel ); if (notificationLevelString) return notificationLevelString.toLowerCase(); }, @discourseComputed("name") url() { return Discourse.getURL(`/c/${Category.slugFor(this)}/${this.id}`); }, @discourseComputed fullSlug() { return Category.slugFor(this).replace(/\//g, "-"); }, @discourseComputed("name") nameLower(name) { return name.toLowerCase(); }, @discourseComputed("url") unreadUrl(url) { return `${url}/l/unread`; }, @discourseComputed("url") newUrl(url) { return `${url}/l/new`; }, @discourseComputed("color", "text_color") style(color, textColor) { return `background-color: #${color}; color: #${textColor}`; }, @discourseComputed("topic_count") moreTopics(topicCount) { return topicCount > (this.num_featured_topics || 2); }, @discourseComputed("topic_count", "subcategories.[]") totalTopicCount(topicCount, subcategories) { if (subcategories) { subcategories.forEach(subcategory => { topicCount += subcategory.topic_count; }); } return topicCount; }, save() { const id = this.id; const url = id ? `/categories/${id}` : "/categories"; return ajax(url, { data: { name: this.name, slug: this.slug, color: this.color, text_color: this.text_color, secure: this.secure, permissions: this._permissionsForUpdate(), auto_close_hours: this.auto_close_hours, auto_close_based_on_last_post: this.get( "auto_close_based_on_last_post" ), position: this.position, email_in: this.email_in, email_in_allow_strangers: this.email_in_allow_strangers, mailinglist_mirror: this.mailinglist_mirror, parent_category_id: this.parent_category_id, uploaded_logo_id: this.get("uploaded_logo.id"), uploaded_background_id: this.get("uploaded_background.id"), allow_badges: this.allow_badges, custom_fields: this.custom_fields, topic_template: this.topic_template, all_topics_wiki: this.all_topics_wiki, allowed_tags: this.allowed_tags, allowed_tag_groups: this.allowed_tag_groups, allow_global_tags: this.allow_global_tags, required_tag_group_name: this.required_tag_groups ? this.required_tag_groups[0] : null, min_tags_from_required_group: this.min_tags_from_required_group, sort_order: this.sort_order, sort_ascending: this.sort_ascending, topic_featured_link_allowed: this.topic_featured_link_allowed, show_subcategory_list: this.show_subcategory_list, num_featured_topics: this.num_featured_topics, default_view: this.default_view, subcategory_list_style: this.subcategory_list_style, default_top_period: this.default_top_period, minimum_required_tags: this.minimum_required_tags, navigate_to_first_post_after_read: this.get( "navigate_to_first_post_after_read" ), search_priority: this.search_priority, reviewable_by_group_name: this.reviewable_by_group_name }, type: id ? "PUT" : "POST" }); }, _permissionsForUpdate() { const permissions = this.permissions; let rval = {}; permissions.forEach(p => (rval[p.group_name] = p.permission.id)); return rval; }, destroy() { return ajax(`/categories/${this.id || this.slug}`, { type: "DELETE" }); }, addPermission(permission) { this.permissions.addObject(permission); this.availableGroups.removeObject(permission.group_name); }, removePermission(permission) { this.permissions.removeObject(permission); this.availableGroups.addObject(permission.group_name); }, @discourseComputed("topics") latestTopic(topics) { if (topics && topics.length) { return topics[0]; } }, @discourseComputed("topics") featuredTopics(topics) { if (topics && topics.length) { return topics.slice(0, this.num_featured_topics || 2); } }, @discourseComputed("id", "topicTrackingState.messageCount") unreadTopics(id) { return this.topicTrackingState.countUnread(id); }, @discourseComputed("id", "topicTrackingState.messageCount") newTopics(id) { return this.topicTrackingState.countNew(id); }, setNotification(notification_level) { this.set("notification_level", notification_level); const url = `/category/${this.id}/notifications`; return ajax(url, { data: { notification_level }, type: "POST" }); }, @discourseComputed("id") isUncategorizedCategory(id) { return id === Site.currentProp("uncategorized_category_id"); } }); var _uncategorized; Category.reopenClass({ findUncategorized() { _uncategorized = _uncategorized || Category.list().findBy( "id", Site.currentProp("uncategorized_category_id") ); return _uncategorized; }, slugFor(category, separator = "/") { if (!category) return ""; const parentCategory = get(category, "parentCategory"); let result = ""; if (parentCategory) { result = Category.slugFor(parentCategory) + separator; } const id = get(category, "id"), slug = get(category, "slug"); return !slug || slug.trim().length === 0 ? `${result}${id}-category` : result + slug; }, list() { return Site.currentProp("categoriesList"); }, listByActivity() { return Site.currentProp("sortedCategories"); }, _idMap() { return Site.currentProp("categoriesById"); }, findSingleBySlug(slug) { if (Discourse.SiteSettings.slug_generation_method !== "encoded") { return Category.list().find(c => Category.slugFor(c) === slug); } else { return Category.list().find(c => Category.slugFor(c) === encodeURI(slug)); } }, findById(id) { if (!id) { return; } return Category._idMap()[id]; }, findByIds(ids = []) { const categories = []; ids.forEach(id => { const found = Category.findById(id); if (found) { categories.push(found); } }); return categories; }, findBySlugAndParent(slug, parentCategory) { if (Discourse.SiteSettings.slug_generation_method === "encoded") { slug = encodeURI(slug); } return Category.list().find(category => { return ( category.slug === slug && (category.parentCategory || null) === parentCategory ); }); }, findBySlugPath(slugPath) { let category = null; for (const slug of slugPath) { category = this.findBySlugAndParent(slug, category); if (!category) { return null; } } return category; }, findBySlugPathWithID(slugPathWithID) { let parts = slugPathWithID.split("/").filter(Boolean); // slugs found by star/glob pathing in emeber do not automatically url decode - ensure that these are decoded if (Discourse.SiteSettings.slug_generation_method === "encoded") { parts = parts.map(urlPart => decodeURI(urlPart)); } let category = null; if (parts.length > 0 && parts[parts.length - 1].match(/^\d+$/)) { const id = parseInt(parts.pop(), 10); category = Category.findById(id); } else { category = Category.findBySlugPath(parts); if ( !category && parts.length > 0 && parts[parts.length - 1].match(/^\d+-category/) ) { const id = parseInt(parts.pop(), 10); category = Category.findById(id); } } return category; }, findBySlug(slug, parentSlug) { const categories = Category.list(); let category; if (parentSlug) { const parentCategory = Category.findSingleBySlug(parentSlug); if (parentCategory) { if (slug === "none") { return parentCategory; } category = categories.find(item => { return ( item && item.get("parentCategory") === parentCategory && ((Discourse.SiteSettings.slug_generation_method !== "encoded" && Category.slugFor(item) === parentSlug + "/" + slug) || (Discourse.SiteSettings.slug_generation_method === "encoded" && Category.slugFor(item) === encodeURI(parentSlug) + "/" + encodeURI(slug))) ); }); } } else { category = Category.findSingleBySlug(slug); // If we have a parent category, we need to enforce it if (category && category.get("parentCategory")) return; } // In case the slug didn't work, try to find it by id instead. if (!category) { category = categories.findBy("id", parseInt(slug, 10)); } return category; }, reloadById(id) { return ajax(`/c/${id}/show.json`); }, reloadBySlug(slug, parentSlug) { return parentSlug ? ajax(`/c/${parentSlug}/${slug}/find_by_slug.json`) : ajax(`/c/${slug}/find_by_slug.json`); }, reloadBySlugPath(slugPath) { return ajax(`/c/${slugPath}/find_by_slug.json`); }, search(term, opts) { var limit = 5; if (opts) { if (opts.limit === 0) { return []; } else if (opts.limit) { limit = opts.limit; } } const emptyTerm = term === ""; let slugTerm = term; if (!emptyTerm) { term = term.toLowerCase(); slugTerm = term; term = term.replace(/-/g, " "); } const categories = Category.listByActivity(); const length = categories.length; var i; var data = []; const done = () => { return data.length === limit; }; for (i = 0; i < length && !done(); i++) { const category = categories[i]; if ( (emptyTerm && !category.get("parent_category_id")) || (!emptyTerm && (category .get("name") .toLowerCase() .indexOf(term) === 0 || category .get("slug") .toLowerCase() .indexOf(slugTerm) === 0)) ) { data.push(category); } } if (!done()) { for (i = 0; i < length && !done(); i++) { const category = categories[i]; if ( !emptyTerm && (category .get("name") .toLowerCase() .indexOf(term) > 0 || category .get("slug") .toLowerCase() .indexOf(slugTerm) > 0) ) { if (data.indexOf(category) === -1) data.push(category); } } } return _.sortBy(data, category => { return category.get("read_restricted"); }); } }); Object.defineProperty(Discourse, "Category", { get() { deprecated( "Import the Category class instead of using Discourse.Category", { since: "2.4.0", dropFrom: "2.5.0" } ); return Category; } }); export default Category;