DEV: Remove enable_experimental_hashtag_autocomplete logic (#22820)
This commit removes any logic in the app and in specs around enable_experimental_hashtag_autocomplete and deletes some old category hashtag code that is no longer necessary. It also adds a `slug_ref` category instance method, which will generate a reference like `parent:child` for a category, with an optional depth, which hashtags use. Also refactors PostRevisor which was using CategoryHashtagDataSource directly which is a no-no. Deletes the old hashtag markdown rule as well.
This commit is contained in:
parent
0c88bf341a
commit
09223e5ae7
|
@ -14,10 +14,6 @@ import discourseComputed, {
|
|||
observes,
|
||||
on,
|
||||
} from "discourse-common/utils/decorators";
|
||||
import {
|
||||
fetchUnseenHashtags,
|
||||
linkSeenHashtags,
|
||||
} from "discourse/lib/link-hashtags";
|
||||
import {
|
||||
fetchUnseenHashtagsInContext,
|
||||
linkSeenHashtagsInContext,
|
||||
|
@ -500,24 +496,12 @@ export default Component.extend(
|
|||
},
|
||||
|
||||
_renderUnseenHashtags(preview) {
|
||||
let unseen;
|
||||
const hashtagContext = this.site.hashtag_configurations["topic-composer"];
|
||||
if (this.siteSettings.enable_experimental_hashtag_autocomplete) {
|
||||
unseen = linkSeenHashtagsInContext(hashtagContext, preview);
|
||||
} else {
|
||||
unseen = linkSeenHashtags(preview);
|
||||
}
|
||||
|
||||
const unseen = linkSeenHashtagsInContext(hashtagContext, preview);
|
||||
if (unseen.length > 0) {
|
||||
if (this.siteSettings.enable_experimental_hashtag_autocomplete) {
|
||||
fetchUnseenHashtagsInContext(hashtagContext, unseen).then(() => {
|
||||
linkSeenHashtagsInContext(hashtagContext, preview);
|
||||
});
|
||||
} else {
|
||||
fetchUnseenHashtags(unseen).then(() => {
|
||||
linkSeenHashtags(preview);
|
||||
});
|
||||
}
|
||||
fetchUnseenHashtagsInContext(hashtagContext, unseen).then(() => {
|
||||
linkSeenHashtagsInContext(hashtagContext, preview);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -946,15 +930,9 @@ export default Component.extend(
|
|||
this._warnCannotSeeMention(preview);
|
||||
|
||||
// Paint category, tag, and other data source hashtags
|
||||
let unseenHashtags;
|
||||
const hashtagContext =
|
||||
this.site.hashtag_configurations["topic-composer"];
|
||||
if (this.siteSettings.enable_experimental_hashtag_autocomplete) {
|
||||
unseenHashtags = linkSeenHashtagsInContext(hashtagContext, preview);
|
||||
} else {
|
||||
unseenHashtags = linkSeenHashtags(preview);
|
||||
}
|
||||
if (unseenHashtags.length > 0) {
|
||||
if (linkSeenHashtagsInContext(hashtagContext, preview).length > 0) {
|
||||
discourseDebounce(this, this._renderUnseenHashtags, preview, 450);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,13 +18,15 @@ import I18n from "I18n";
|
|||
import ItsATrap from "@discourse/itsatrap";
|
||||
import { Promise } from "rsvp";
|
||||
import { SKIP } from "discourse/lib/autocomplete";
|
||||
import { setupHashtagAutocomplete } from "discourse/lib/hashtag-autocomplete";
|
||||
import {
|
||||
linkSeenHashtagsInContext,
|
||||
setupHashtagAutocomplete,
|
||||
} from "discourse/lib/hashtag-autocomplete";
|
||||
import deprecated from "discourse-common/lib/deprecated";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { findRawTemplate } from "discourse-common/lib/raw-templates";
|
||||
import { getRegister } from "discourse-common/lib/get-owner";
|
||||
import { isTesting } from "discourse-common/config/environment";
|
||||
import { linkSeenHashtags } from "discourse/lib/link-hashtags";
|
||||
import { linkSeenMentions } from "discourse/lib/link-mentions";
|
||||
import { loadOneboxes } from "discourse/lib/load-oneboxes";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
|
@ -438,8 +440,10 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
if (this.siteSettings.enable_diffhtml_preview) {
|
||||
const cookedElement = document.createElement("div");
|
||||
cookedElement.innerHTML = cooked;
|
||||
|
||||
linkSeenHashtags(cookedElement);
|
||||
linkSeenHashtagsInContext(
|
||||
this.site.hashtag_configurations["topic-composer"],
|
||||
cookedElement
|
||||
);
|
||||
linkSeenMentions(cookedElement, this.siteSettings);
|
||||
resolveCachedShortUrls(this.siteSettings, cookedElement);
|
||||
loadOneboxes(
|
||||
|
|
|
@ -5,16 +5,13 @@ export default {
|
|||
after: "hashtag-css-generator",
|
||||
|
||||
initialize(owner) {
|
||||
const siteSettings = owner.lookup("service:site-settings");
|
||||
const site = owner.lookup("service:site");
|
||||
|
||||
withPluginApi("0.8.7", (api) => {
|
||||
if (siteSettings.enable_experimental_hashtag_autocomplete) {
|
||||
api.decorateCookedElement((post) => decorateHashtags(post, site), {
|
||||
onlyStream: true,
|
||||
id: "hashtag-icons",
|
||||
});
|
||||
}
|
||||
api.decorateCookedElement((post) => decorateHashtags(post, site), {
|
||||
onlyStream: true,
|
||||
id: "hashtag-icons",
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
escapeExpression,
|
||||
inCodeBlock,
|
||||
} from "discourse/lib/utilities";
|
||||
import { search as searchCategoryTag } from "discourse/lib/category-tag-search";
|
||||
import { emojiUnescape } from "discourse/lib/text";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
|
||||
|
@ -76,16 +75,12 @@ export function setupHashtagAutocomplete(
|
|||
siteSettings,
|
||||
autocompleteOptions = {}
|
||||
) {
|
||||
if (siteSettings.enable_experimental_hashtag_autocomplete) {
|
||||
_setupExperimental(
|
||||
contextualHashtagConfiguration,
|
||||
$textArea,
|
||||
siteSettings,
|
||||
autocompleteOptions
|
||||
);
|
||||
} else {
|
||||
_setup($textArea, siteSettings, autocompleteOptions.afterComplete);
|
||||
}
|
||||
_setup(
|
||||
contextualHashtagConfiguration,
|
||||
$textArea,
|
||||
siteSettings,
|
||||
autocompleteOptions
|
||||
);
|
||||
}
|
||||
|
||||
export function hashtagTriggerRule(textarea) {
|
||||
|
@ -147,7 +142,7 @@ export function linkSeenHashtagsInContext(
|
|||
.filter((slug) => !checkedHashtags.has(slug));
|
||||
}
|
||||
|
||||
function _setupExperimental(
|
||||
function _setup(
|
||||
contextualHashtagConfiguration,
|
||||
$textArea,
|
||||
siteSettings,
|
||||
|
@ -171,22 +166,6 @@ function _setupExperimental(
|
|||
});
|
||||
}
|
||||
|
||||
function _setup($textArea, siteSettings, afterComplete) {
|
||||
$textArea.autocomplete({
|
||||
template: findRawTemplate("category-tag-autocomplete"),
|
||||
key: "#",
|
||||
afterComplete,
|
||||
transformComplete: (obj) => obj.text,
|
||||
dataSource: (term) => {
|
||||
if (term.match(/\s/)) {
|
||||
return null;
|
||||
}
|
||||
return searchCategoryTag(term, siteSettings);
|
||||
},
|
||||
triggerRule: (textarea, opts) => hashtagTriggerRule(textarea, opts),
|
||||
});
|
||||
}
|
||||
|
||||
let searchCache = {};
|
||||
let searchCacheTime;
|
||||
let currentSearch;
|
||||
|
@ -220,10 +199,6 @@ function _searchGeneric(term, siteSettings, contextualHashtagConfiguration) {
|
|||
resolve(CANCELLED_STATUS);
|
||||
}, 5000);
|
||||
|
||||
if (!siteSettings.enable_experimental_hashtag_autocomplete && term === "") {
|
||||
return resolve(CANCELLED_STATUS);
|
||||
}
|
||||
|
||||
const debouncedSearch = (q, ctx, resultFunc) => {
|
||||
discourseDebounce(this, _searchRequest, q, ctx, resultFunc, INPUT_DELAY);
|
||||
};
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
// TODO (martin) Delete this after core PR and any other PRs that depend
|
||||
// on this file (e.g. discourse-encrypt) are merged.
|
||||
|
||||
import deprecated from "discourse-common/lib/deprecated";
|
||||
import { TAG_HASHTAG_POSTFIX } from "discourse/lib/tag-hashtags";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
|
|
|
@ -9,7 +9,6 @@ acceptance("#hashtag autocompletion in composer", function (needs) {
|
|||
needs.user();
|
||||
needs.settings({
|
||||
tagging_enabled: true,
|
||||
enable_experimental_hashtag_autocomplete: true,
|
||||
});
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/hashtags", () => {
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
import {
|
||||
acceptance,
|
||||
query,
|
||||
visible,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import { click, fillIn, visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
|
||||
acceptance("Category and Tag Hashtags", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
tagging_enabled: true,
|
||||
enable_experimental_hashtag_autocomplete: false,
|
||||
});
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/hashtags", () => {
|
||||
return helper.response({
|
||||
categories: { bug: "/c/bugs" },
|
||||
tags: {
|
||||
monkey: "/tag/monkey",
|
||||
bug: "/tag/bug",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("hashtags are cooked properly", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await click("#topic-footer-buttons .btn.create");
|
||||
|
||||
await fillIn(
|
||||
".d-editor-input",
|
||||
`this is a category hashtag #bug
|
||||
|
||||
this is a tag hashtag #monkey
|
||||
|
||||
category vs tag: #bug vs #bug::tag
|
||||
|
||||
uppercase hashtag works too #BUG, #BUG::tag`
|
||||
);
|
||||
|
||||
assert.ok(visible(".d-editor-preview"));
|
||||
assert.strictEqual(
|
||||
query(".d-editor-preview").innerHTML.trim(),
|
||||
`<p>this is a category hashtag <a href="/c/bugs" class="hashtag" tabindex=\"-1\">#<span>bug</span></a></p>
|
||||
<p>this is a tag hashtag <a href="/tag/monkey" class="hashtag" tabindex=\"-1\">#<span>monkey</span></a></p>
|
||||
<p>category vs tag: <a href="/c/bugs" class="hashtag" tabindex=\"-1\">#<span>bug</span></a> vs <a href="/tag/bug" class="hashtag" tabindex=\"-1\">#<span>bug</span></a></p>
|
||||
<p>uppercase hashtag works too <a href="/c/bugs" class="hashtag" tabindex=\"-1\">#<span>BUG</span></a>, <a href="/tag/bug" class="hashtag" tabindex=\"-1\">#<span>BUG</span></a></p>`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -643,73 +643,6 @@ eviltrout</p>
|
|||
);
|
||||
});
|
||||
|
||||
test("Category hashtags", function (assert) {
|
||||
const alwaysTrue = {
|
||||
categoryHashtagLookup: function () {
|
||||
return [
|
||||
"http://test.discourse.org/category-hashtag",
|
||||
"category-hashtag",
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
assert.cookedOptions(
|
||||
"Check out #category-hashtag",
|
||||
alwaysTrue,
|
||||
'<p>Check out <a class="hashtag" href="http://test.discourse.org/category-hashtag">#<span>category-hashtag</span></a></p>',
|
||||
"it translates category hashtag into links"
|
||||
);
|
||||
|
||||
assert.cooked(
|
||||
"Check out #category-hashtag",
|
||||
'<p>Check out <span class="hashtag">#category-hashtag</span></p>',
|
||||
"it does not translate category hashtag into links if it is not a valid category hashtag"
|
||||
);
|
||||
|
||||
assert.cookedOptions(
|
||||
"[#category-hashtag](http://www.test.com)",
|
||||
alwaysTrue,
|
||||
'<p><a href="http://www.test.com">#category-hashtag</a></p>',
|
||||
"it does not translate category hashtag within links"
|
||||
);
|
||||
|
||||
assert.cooked(
|
||||
"```\n# #category-hashtag\n```",
|
||||
'<pre><code class="lang-auto"># #category-hashtag\n</code></pre>',
|
||||
"it does not translate category hashtags to links in code blocks"
|
||||
);
|
||||
|
||||
assert.cooked(
|
||||
"># #category-hashtag\n",
|
||||
'<blockquote>\n<h1><span class="hashtag">#category-hashtag</span></h1>\n</blockquote>',
|
||||
"it handles category hashtags in simple quotes"
|
||||
);
|
||||
|
||||
assert.cooked(
|
||||
"# #category-hashtag",
|
||||
'<h1><a name="category-hashtag-1" class="anchor" href="#category-hashtag-1"></a><span class="hashtag">#category-hashtag</span></h1>',
|
||||
"it works within ATX-style headers"
|
||||
);
|
||||
|
||||
assert.cooked(
|
||||
"don't `#category-hashtag`",
|
||||
"<p>don't <code>#category-hashtag</code></p>",
|
||||
"it does not mention in an inline code block"
|
||||
);
|
||||
|
||||
assert.cooked(
|
||||
"<small>#category-hashtag</small>",
|
||||
'<p><small><span class="hashtag">#category-hashtag</span></small></p>',
|
||||
"it works between HTML tags"
|
||||
);
|
||||
|
||||
assert.cooked(
|
||||
"Checkout #ụdị",
|
||||
'<p>Checkout <span class="hashtag">#ụdị</span></p>',
|
||||
"it works for non-english characters"
|
||||
);
|
||||
});
|
||||
|
||||
test("Heading", function (assert) {
|
||||
assert.cooked(
|
||||
"**Bold**\n----------",
|
||||
|
|
|
@ -547,8 +547,6 @@ export function setup(opts, siteSettings, state) {
|
|||
markdownTypographerQuotationMarks:
|
||||
siteSettings.markdown_typographer_quotation_marks,
|
||||
markdownLinkifyTlds: siteSettings.markdown_linkify_tlds,
|
||||
enableExperimentalHashtagAutocomplete:
|
||||
siteSettings.enable_experimental_hashtag_autocomplete,
|
||||
};
|
||||
|
||||
const markdownitOpts = {
|
||||
|
|
|
@ -28,7 +28,6 @@ export function buildOptions(state) {
|
|||
getTopicInfo,
|
||||
topicId,
|
||||
forceQuoteLink,
|
||||
categoryHashtagLookup,
|
||||
userId,
|
||||
getCurrentUser,
|
||||
currentUser,
|
||||
|
@ -67,7 +66,6 @@ export function buildOptions(state) {
|
|||
getTopicInfo,
|
||||
topicId,
|
||||
forceQuoteLink,
|
||||
categoryHashtagLookup,
|
||||
userId,
|
||||
getCurrentUser,
|
||||
currentUser,
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
// TODO (martin) Remove this once enable_experimental_hashtag_autocomplete
|
||||
// (and by extension enableExperimentalHashtagAutocomplete) is not required
|
||||
// anymore, the new hashtag-autocomplete rule replaces it.
|
||||
|
||||
function addHashtag(buffer, matches, state) {
|
||||
const options = state.md.options.discourse;
|
||||
const slug = matches[1];
|
||||
const categoryHashtagLookup = options.categoryHashtagLookup;
|
||||
const result = categoryHashtagLookup && categoryHashtagLookup(slug);
|
||||
|
||||
let token;
|
||||
|
||||
if (result) {
|
||||
token = new state.Token("link_open", "a", 1);
|
||||
token.attrs = [
|
||||
["class", "hashtag"],
|
||||
["href", result[0]],
|
||||
];
|
||||
token.block = false;
|
||||
buffer.push(token);
|
||||
|
||||
token = new state.Token("text", "", 0);
|
||||
token.content = "#";
|
||||
buffer.push(token);
|
||||
|
||||
token = new state.Token("span_open", "span", 1);
|
||||
token.block = false;
|
||||
buffer.push(token);
|
||||
|
||||
token = new state.Token("text", "", 0);
|
||||
token.content = result[1];
|
||||
buffer.push(token);
|
||||
|
||||
buffer.push(new state.Token("span_close", "span", -1));
|
||||
|
||||
buffer.push(new state.Token("link_close", "a", -1));
|
||||
} else {
|
||||
token = new state.Token("span_open", "span", 1);
|
||||
token.attrs = [["class", "hashtag"]];
|
||||
buffer.push(token);
|
||||
|
||||
token = new state.Token("text", "", 0);
|
||||
token.content = matches[0];
|
||||
buffer.push(token);
|
||||
|
||||
token = new state.Token("span_close", "span", -1);
|
||||
buffer.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
export function setup(helper) {
|
||||
helper.registerPlugin((md) => {
|
||||
if (
|
||||
!md.options.discourse.limitedSiteSettings
|
||||
.enableExperimentalHashtagAutocomplete
|
||||
) {
|
||||
const rule = {
|
||||
matcher: /#([\u00C0-\u1FFF\u2C00-\uD7FF\w:-]{1,101})/,
|
||||
onMatch: addHashtag,
|
||||
};
|
||||
|
||||
md.core.textPostProcess.ruler.push("category-hashtag", rule);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -87,17 +87,12 @@ function addIconPlaceholder(buffer, state) {
|
|||
|
||||
export function setup(helper) {
|
||||
helper.registerPlugin((md) => {
|
||||
if (
|
||||
md.options.discourse.limitedSiteSettings
|
||||
.enableExperimentalHashtagAutocomplete
|
||||
) {
|
||||
const rule = {
|
||||
matcher: /#([\u00C0-\u1FFF\u2C00-\uD7FF\w:-]{1,101})/,
|
||||
onMatch: addHashtag,
|
||||
};
|
||||
const rule = {
|
||||
matcher: /#([\u00C0-\u1FFF\u2C00-\uD7FF\w:-]{1,101})/,
|
||||
onMatch: addHashtag,
|
||||
};
|
||||
|
||||
md.core.textPostProcess.ruler.push("hashtag-autocomplete", rule);
|
||||
}
|
||||
md.core.textPostProcess.ruler.push("hashtag-autocomplete", rule);
|
||||
});
|
||||
|
||||
helper.allowList([
|
||||
|
|
|
@ -4,11 +4,7 @@ class HashtagsController < ApplicationController
|
|||
requires_login
|
||||
|
||||
def lookup
|
||||
if SiteSetting.enable_experimental_hashtag_autocomplete
|
||||
render json: HashtagAutocompleteService.new(guardian).lookup(params[:slugs], params[:order])
|
||||
else
|
||||
render json: HashtagAutocompleteService.new(guardian).lookup_old(params[:slugs])
|
||||
end
|
||||
render json: HashtagAutocompleteService.new(guardian).lookup(params[:slugs], params[:order])
|
||||
end
|
||||
|
||||
def search
|
||||
|
|
|
@ -18,6 +18,8 @@ class Category < ActiveRecord::Base
|
|||
|
||||
REQUIRE_TOPIC_APPROVAL = "require_topic_approval"
|
||||
REQUIRE_REPLY_APPROVAL = "require_reply_approval"
|
||||
NUM_AUTO_BUMP_DAILY = "num_auto_bump_daily"
|
||||
SLUG_REF_SEPARATOR = ":"
|
||||
|
||||
register_custom_field_type(REQUIRE_TOPIC_APPROVAL, :boolean)
|
||||
register_custom_field_type(REQUIRE_REPLY_APPROVAL, :boolean)
|
||||
|
@ -1037,6 +1039,20 @@ class Category < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def slug_ref(depth: 1)
|
||||
if self.parent_category_id.present?
|
||||
built_ref = [self.slug]
|
||||
parent = self.parent_category
|
||||
while parent.present? && (built_ref.length < depth + 1)
|
||||
built_ref << parent.slug
|
||||
parent = parent.parent_category
|
||||
end
|
||||
built_ref.reverse.join(Category::SLUG_REF_SEPARATOR)
|
||||
else
|
||||
self.slug
|
||||
end
|
||||
end
|
||||
|
||||
def cannot_delete_reason
|
||||
return I18n.t("category.cannot_delete.uncategorized") if self.uncategorized?
|
||||
return I18n.t("category.cannot_delete.has_subcategories") if self.has_children?
|
||||
|
|
|
@ -3,26 +3,7 @@
|
|||
module CategoryHashtag
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
SEPARATOR = ":"
|
||||
|
||||
class_methods do
|
||||
# TODO (martin) Remove this when enable_experimental_hashtag_autocomplete
|
||||
# becomes the norm, it is reimplemented below for CategoryHashtagDataSourcee
|
||||
def query_from_hashtag_slug(category_slug)
|
||||
slug_path = split_slug_path(category_slug)
|
||||
return if slug_path.blank?
|
||||
|
||||
slug_path.map! { |slug| CGI.escape(slug) } if SiteSetting.slug_generation_method == "encoded"
|
||||
|
||||
parent_slug, child_slug = slug_path.last(2)
|
||||
categories = Category.where(slug: parent_slug)
|
||||
if child_slug
|
||||
Category.where(slug: child_slug, parent_category_id: categories.select(:id)).first
|
||||
else
|
||||
categories.where(parent_category_id: nil).first
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Finds any categories that match the provided slugs, supporting
|
||||
# the parent:child format for category slugs (only one level of
|
||||
|
@ -66,7 +47,7 @@ module CategoryHashtag
|
|||
end
|
||||
|
||||
def split_slug_path(slug)
|
||||
slug_path = slug.split(SEPARATOR)
|
||||
slug_path = slug.split(Category::SLUG_REF_SEPARATOR)
|
||||
return if slug_path.empty? || slug_path.size > 2
|
||||
slug_path
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# categories via the # autocomplete character.
|
||||
class CategoryHashtagDataSource
|
||||
def self.enabled?
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete
|
||||
true
|
||||
end
|
||||
|
||||
def self.icon
|
||||
|
@ -27,12 +27,7 @@ class CategoryHashtagDataSource
|
|||
|
||||
# Single-level category heirarchy should be enough to distinguish between
|
||||
# categories here.
|
||||
item.ref =
|
||||
if category.parent_category_id
|
||||
"#{category.parent_category.slug}:#{category.slug}"
|
||||
else
|
||||
category.slug
|
||||
end
|
||||
item.ref = category.slug_ref
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -299,51 +299,6 @@ class HashtagAutocompleteService
|
|||
append_types_to_conflicts(limited_results, top_ranked_type, types_in_priority_order, limit)
|
||||
end
|
||||
|
||||
# TODO (martin) Remove this once plugins are not relying on the old lookup
|
||||
# behavior via HashtagsController when enable_experimental_hashtag_autocomplete is removed
|
||||
def lookup_old(slugs)
|
||||
raise Discourse::InvalidParameters.new(:slugs) if !slugs.is_a?(Array)
|
||||
|
||||
all_slugs = []
|
||||
tag_slugs = []
|
||||
|
||||
slugs[0..HashtagAutocompleteService::HASHTAGS_PER_REQUEST].each do |slug|
|
||||
if slug.end_with?(PrettyText::Helpers::TAG_HASHTAG_POSTFIX)
|
||||
tag_slugs << slug.chomp(PrettyText::Helpers::TAG_HASHTAG_POSTFIX)
|
||||
else
|
||||
all_slugs << slug
|
||||
end
|
||||
end
|
||||
|
||||
# Try to resolve hashtags as categories first
|
||||
category_slugs_and_ids =
|
||||
all_slugs.map { |slug| [slug, Category.query_from_hashtag_slug(slug)&.id] }.to_h
|
||||
category_ids_and_urls =
|
||||
Category
|
||||
.secured(guardian)
|
||||
.select(:id, :slug, :parent_category_id) # fields required for generating category URL
|
||||
.where(id: category_slugs_and_ids.values)
|
||||
.map { |c| [c.id, c.url] }
|
||||
.to_h
|
||||
categories_hashtags = {}
|
||||
category_slugs_and_ids.each do |slug, id|
|
||||
if category_url = category_ids_and_urls[id]
|
||||
categories_hashtags[slug] = category_url
|
||||
end
|
||||
end
|
||||
|
||||
# Resolve remaining hashtags as tags
|
||||
tag_hashtags = {}
|
||||
if SiteSetting.tagging_enabled
|
||||
tag_slugs += (all_slugs - categories_hashtags.keys)
|
||||
DiscourseTagging
|
||||
.filter_visible(Tag.where_name(tag_slugs), guardian)
|
||||
.each { |tag| tag_hashtags[tag.name] = tag.full_url }
|
||||
end
|
||||
|
||||
{ categories: categories_hashtags, tags: tag_hashtags }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search_using_condition(limited_results, term, type, limit, condition)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# tags via the # autocomplete character.
|
||||
class TagHashtagDataSource
|
||||
def self.enabled?
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete && SiteSetting.tagging_enabled
|
||||
SiteSetting.tagging_enabled
|
||||
end
|
||||
|
||||
def self.icon
|
||||
|
|
|
@ -160,18 +160,14 @@ class PostRevisor
|
|||
user,
|
||||
I18n.t(
|
||||
"topic_category_changed",
|
||||
from: category_name_raw(old_category),
|
||||
to: category_name_raw(new_category),
|
||||
from: "##{old_category.slug_ref}",
|
||||
to: "##{new_category.slug_ref}",
|
||||
),
|
||||
post_type: Post.types[:small_action],
|
||||
action_code: "category_changed",
|
||||
)
|
||||
end
|
||||
|
||||
def self.category_name_raw(category)
|
||||
"##{CategoryHashtagDataSource.category_to_hashtag_item(category).ref}"
|
||||
end
|
||||
|
||||
def self.create_small_action_for_tag_changes(topic:, user:, added_tags:, removed_tags:)
|
||||
return if !SiteSetting.create_post_for_category_and_tag_changes
|
||||
|
||||
|
|
|
@ -197,7 +197,6 @@ module PrettyText
|
|||
__optInput.lookupPrimaryUserGroup = __lookupPrimaryUserGroup;
|
||||
__optInput.formatUsername = __formatUsername;
|
||||
__optInput.getTopicInfo = __getTopicInfo;
|
||||
__optInput.categoryHashtagLookup = __categoryLookup;
|
||||
__optInput.hashtagLookup = __hashtagLookup;
|
||||
__optInput.customEmoji = #{custom_emoji.to_json};
|
||||
__optInput.customEmojiTranslation = #{Plugin::CustomEmoji.translations.to_json};
|
||||
|
@ -471,10 +470,7 @@ module PrettyText
|
|||
DiscourseEvent.trigger(:reduce_excerpt, doc, options)
|
||||
strip_image_wrapping(doc)
|
||||
strip_oneboxed_media(doc)
|
||||
|
||||
if SiteSetting.enable_experimental_hashtag_autocomplete && options[:plain_hashtags]
|
||||
convert_hashtag_links_to_plaintext(doc)
|
||||
end
|
||||
convert_hashtag_links_to_plaintext(doc) if options[:plain_hashtags]
|
||||
|
||||
html = doc.to_html
|
||||
ExcerptParser.get_excerpt(html, max_length, options)
|
||||
|
|
|
@ -96,21 +96,6 @@ module PrettyText
|
|||
end
|
||||
end
|
||||
|
||||
# TODO (martin) Remove this when everything is using hashtag_lookup
|
||||
# after enable_experimental_hashtag_autocomplete is default.
|
||||
def category_tag_hashtag_lookup(text)
|
||||
is_tag = text =~ /#{TAG_HASHTAG_POSTFIX}\z/
|
||||
|
||||
if !is_tag && category = Category.query_from_hashtag_slug(text)
|
||||
[category.url, text]
|
||||
elsif (!is_tag && tag = Tag.find_by(name: text)) ||
|
||||
(is_tag && tag = Tag.find_by(name: text.gsub!(TAG_HASHTAG_POSTFIX, "")))
|
||||
[tag.url, text]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def hashtag_lookup(slug, cooking_user_id, types_in_priority_order)
|
||||
# NOTE: This is _somewhat_ expected since we need to be able to cook posts
|
||||
# etc. without a user sometimes, but it is still an edge case.
|
||||
|
|
|
@ -106,12 +106,6 @@ function __getTopicInfo(i) {
|
|||
return __helpers.get_topic_info(i);
|
||||
}
|
||||
|
||||
// TODO (martin) Remove this when everything is using hashtag_lookup
|
||||
// after enable_experimental_hashtag_autocomplete is default.
|
||||
function __categoryLookup(c) {
|
||||
return __helpers.category_tag_hashtag_lookup(c);
|
||||
}
|
||||
|
||||
function __hashtagLookup(slug, cookingUserId, typesInPriorityOrder) {
|
||||
return __helpers.hashtag_lookup(slug, cookingUserId, typesInPriorityOrder);
|
||||
}
|
||||
|
|
|
@ -301,9 +301,7 @@ module Chat
|
|||
end
|
||||
|
||||
def channel_hashtag_or_name
|
||||
if chat_channel.slug.present? && SiteSetting.enable_experimental_hashtag_autocomplete
|
||||
return "##{chat_channel.slug}::channel"
|
||||
end
|
||||
return "##{chat_channel.slug}::channel" if chat_channel.slug.present?
|
||||
chat_channel_title
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Chat
|
||||
class ChannelHashtagDataSource
|
||||
def self.enabled?
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete && SiteSetting.enable_public_channels
|
||||
SiteSetting.enable_public_channels
|
||||
end
|
||||
|
||||
def self.icon
|
||||
|
|
|
@ -186,28 +186,24 @@ describe Chat::ChannelArchiveService do
|
|||
expect(pm_topic.first_post.raw).to include("Title can't have more than 1 emoji")
|
||||
end
|
||||
|
||||
context "when enable_experimental_hashtag_autocomplete" do
|
||||
before { SiteSetting.enable_experimental_hashtag_autocomplete = true }
|
||||
|
||||
it "uses the channel slug to autolink a hashtag for the channel in the PM" do
|
||||
create_messages(3) && start_archive
|
||||
described_class.new(@channel_archive).execute
|
||||
expect(@channel_archive.reload.complete?).to eq(true)
|
||||
pm_topic = Topic.private_messages.last
|
||||
expect(pm_topic.first_post.cooked).to have_tag(
|
||||
"a",
|
||||
with: {
|
||||
class: "hashtag-cooked",
|
||||
href: channel.relative_url,
|
||||
"data-type": "channel",
|
||||
"data-slug": channel.slug,
|
||||
"data-id": channel.id,
|
||||
"data-ref": "#{channel.slug}::channel",
|
||||
},
|
||||
) do
|
||||
with_tag("span", with: { class: "hashtag-icon-placeholder" })
|
||||
with_tag("span", text: channel.title(user))
|
||||
end
|
||||
it "uses the channel slug to autolink a hashtag for the channel in the PM" do
|
||||
create_messages(3) && start_archive
|
||||
described_class.new(@channel_archive).execute
|
||||
expect(@channel_archive.reload.complete?).to eq(true)
|
||||
pm_topic = Topic.private_messages.last
|
||||
expect(pm_topic.first_post.cooked).to have_tag(
|
||||
"a",
|
||||
with: {
|
||||
class: "hashtag-cooked",
|
||||
href: channel.relative_url,
|
||||
"data-type": "channel",
|
||||
"data-slug": channel.slug,
|
||||
"data-id": channel.id,
|
||||
"data-ref": "#{channel.slug}::channel",
|
||||
},
|
||||
) do
|
||||
with_tag("span", with: { class: "hashtag-icon-placeholder" })
|
||||
with_tag("span", text: channel.title(user))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ RSpec.describe Chat::ChannelHashtagDataSource do
|
|||
let!(:guardian) { Guardian.new(user) }
|
||||
|
||||
before do
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = true
|
||||
SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:trust_level_1]
|
||||
Group.refresh_automatic_groups!
|
||||
end
|
||||
|
|
|
@ -228,22 +228,8 @@ describe Chat::Message do
|
|||
expect(cooked).to eq("<p><span class=\"mention\">@mention</span></p>")
|
||||
end
|
||||
|
||||
it "supports category-hashtag plugin" do
|
||||
# TODO (martin) Remove when enable_experimental_hashtag_autocomplete is default for all sites
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = false
|
||||
|
||||
category = Fabricate(:category)
|
||||
|
||||
cooked = described_class.cook("##{category.slug}")
|
||||
|
||||
expect(cooked).to eq(
|
||||
"<p><a class=\"hashtag\" href=\"#{category.url}\">#<span>#{category.slug}</span></a></p>",
|
||||
)
|
||||
end
|
||||
|
||||
it "supports hashtag autocomplete" do
|
||||
SiteSetting.chat_enabled = true
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = true
|
||||
|
||||
category = Fabricate(:category)
|
||||
user = Fabricate(:user)
|
||||
|
@ -504,7 +490,6 @@ describe Chat::Message do
|
|||
|
||||
before do
|
||||
SiteSetting.chat_enabled = true
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = true
|
||||
SiteSetting.suppress_secured_categories_from_admin = true
|
||||
end
|
||||
|
||||
|
|
|
@ -15,8 +15,6 @@ describe "Using #hashtag autocompletion to search for and lookup channels", type
|
|||
let(:topic_page) { PageObjects::Pages::Topic.new }
|
||||
|
||||
before do
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = true
|
||||
|
||||
chat_system_bootstrap(user, [channel1, channel2])
|
||||
sign_in(user)
|
||||
end
|
||||
|
|
|
@ -400,18 +400,8 @@ en:
|
|||
instructions: |-
|
||||
Did you know that you can refer to categories and tags in your post? For example, have you seen the %{category} category?
|
||||
|
||||
Type `#` in the middle of a sentence and select any category or tag.
|
||||
instructions_experimental: |-
|
||||
Did you know that you can refer to categories and tags in your post? For example, have you seen the %{category} category?
|
||||
|
||||
Type `#` anywhere in a sentence and select any category or tag.
|
||||
not_found: |-
|
||||
Hmm, I don’t see a category in there anywhere. Note that `#` can't be the first character. Can you copy this in your next reply?
|
||||
|
||||
```text
|
||||
I can create a category link via #
|
||||
```
|
||||
not_found_experimental: |-
|
||||
Hmm, I don’t see a category in there anywhere. Can you copy this in your next reply?
|
||||
|
||||
```text
|
||||
|
|
|
@ -39,27 +39,11 @@ module DiscourseNarrativeBot
|
|||
next_state: :tutorial_category_hashtag,
|
||||
next_instructions:
|
||||
Proc.new do
|
||||
category = Category.secured(Guardian.new(@user)).last
|
||||
slug = category.slug
|
||||
|
||||
if parent_category = category.parent_category
|
||||
slug = "#{parent_category.slug}#{CategoryHashtag::SEPARATOR}#{slug}"
|
||||
end
|
||||
|
||||
# TODO (martin) When enable_experimental_hashtag_autocomplete is the only option
|
||||
# update the instructions and remove instructions_experimental, as well as the
|
||||
# not_found translation
|
||||
if SiteSetting.enable_experimental_hashtag_autocomplete
|
||||
I18n.t(
|
||||
"#{I18N_KEY}.category_hashtag.instructions_experimental",
|
||||
i18n_post_args(category: "##{slug}"),
|
||||
)
|
||||
else
|
||||
I18n.t(
|
||||
"#{I18N_KEY}.category_hashtag.instructions",
|
||||
i18n_post_args(category: "##{slug}"),
|
||||
)
|
||||
end
|
||||
category = Category.secured(@user.guardian).last
|
||||
I18n.t(
|
||||
"#{I18N_KEY}.category_hashtag.instructions",
|
||||
i18n_post_args(category: "##{category.slug_ref}"),
|
||||
)
|
||||
end,
|
||||
recover: {
|
||||
action: :reply_to_recover,
|
||||
|
@ -298,9 +282,7 @@ module DiscourseNarrativeBot
|
|||
topic_id = @post.topic_id
|
||||
return unless valid_topic?(topic_id)
|
||||
|
||||
hashtag_css_class =
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete ? ".hashtag-cooked" : ".hashtag"
|
||||
if Nokogiri::HTML5.fragment(@post.cooked).css(hashtag_css_class).size > 0
|
||||
if Nokogiri::HTML5.fragment(@post.cooked).css(".hashtag-cooked").size > 0
|
||||
raw = <<~MD
|
||||
#{I18n.t("#{I18N_KEY}.category_hashtag.reply", i18n_post_args)}
|
||||
|
||||
|
@ -312,14 +294,7 @@ module DiscourseNarrativeBot
|
|||
else
|
||||
fake_delay
|
||||
unless @data[:attempted]
|
||||
if SiteSetting.enable_experimental_hashtag_autocomplete
|
||||
reply_to(
|
||||
@post,
|
||||
I18n.t("#{I18N_KEY}.category_hashtag.not_found_experimental", i18n_post_args),
|
||||
)
|
||||
else
|
||||
reply_to(@post, I18n.t("#{I18N_KEY}.category_hashtag.not_found", i18n_post_args))
|
||||
end
|
||||
reply_to(@post, I18n.t("#{I18N_KEY}.category_hashtag.not_found", i18n_post_args))
|
||||
end
|
||||
enqueue_timeout_job(@user)
|
||||
false
|
||||
|
|
|
@ -377,9 +377,6 @@ RSpec.describe DiscourseNarrativeBot::AdvancedUserNarrative do
|
|||
|
||||
context "when reply contains the skip trigger" do
|
||||
it "should create the right reply" do
|
||||
# TODO (martin) Remove when enable_experimental_hashtag_autocomplete is default for all sites
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = false
|
||||
|
||||
parent_category = Fabricate(:category, name: "a")
|
||||
_category = Fabricate(:category, parent_category: parent_category, name: "b")
|
||||
|
||||
|
@ -417,9 +414,6 @@ RSpec.describe DiscourseNarrativeBot::AdvancedUserNarrative do
|
|||
|
||||
context "when user recovers a post in the right topic" do
|
||||
it "should create the right reply" do
|
||||
# TODO (martin) Remove when enable_experimental_hashtag_autocomplete is default for all sites
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = false
|
||||
|
||||
parent_category = Fabricate(:category, name: "a")
|
||||
_category = Fabricate(:category, parent_category: parent_category, name: "b")
|
||||
post
|
||||
|
@ -448,9 +442,6 @@ RSpec.describe DiscourseNarrativeBot::AdvancedUserNarrative do
|
|||
topic_id: topic.id,
|
||||
track: described_class.to_s,
|
||||
)
|
||||
|
||||
# TODO (martin) Remove when enable_experimental_hashtag_autocomplete is default for all sites
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = false
|
||||
end
|
||||
|
||||
context "when post is not in the right topic" do
|
||||
|
@ -509,37 +500,15 @@ RSpec.describe DiscourseNarrativeBot::AdvancedUserNarrative do
|
|||
narrative.input(:reply, user, post: post)
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t("discourse_narrative_bot.advanced_user_narrative.category_hashtag.reply", base_uri: "")}
|
||||
|
||||
#{I18n.t("discourse_narrative_bot.advanced_user_narrative.change_topic_notification_level.instructions", base_uri: "")}
|
||||
RAW
|
||||
|
||||
expect(Post.last.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(
|
||||
:tutorial_change_topic_notification_level,
|
||||
)
|
||||
end
|
||||
|
||||
context "when enable_experimental_hashtag_autocomplete is true" do
|
||||
before { SiteSetting.enable_experimental_hashtag_autocomplete = true }
|
||||
|
||||
it "should create the right reply" do
|
||||
category = Fabricate(:category)
|
||||
|
||||
post.update!(raw: "Check out this ##{category.slug}")
|
||||
narrative.input(:reply, user, post: post)
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t("discourse_narrative_bot.advanced_user_narrative.category_hashtag.reply", base_uri: "")}
|
||||
|
||||
#{I18n.t("discourse_narrative_bot.advanced_user_narrative.change_topic_notification_level.instructions", base_uri: "")}
|
||||
RAW
|
||||
|
||||
expect(Post.last.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(
|
||||
:tutorial_change_topic_notification_level,
|
||||
)
|
||||
end
|
||||
expect(Post.last.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(
|
||||
:tutorial_change_topic_notification_level,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -5,9 +5,6 @@ RSpec.describe "CommonMark" do
|
|||
SiteSetting.enable_markdown_typographer = false
|
||||
SiteSetting.highlighted_languages = "ruby|aa"
|
||||
|
||||
# TODO (martin) Remove when enable_experimental_hashtag_autocomplete is default for all sites
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = false
|
||||
|
||||
html, state, md = nil
|
||||
failed = 0
|
||||
|
||||
|
@ -34,7 +31,7 @@ RSpec.describe "CommonMark" do
|
|||
cooked = PrettyText.markdown(md, sanitize: false)
|
||||
cooked.strip!
|
||||
cooked.gsub!(" class=\"lang-auto\"", "")
|
||||
cooked.gsub!(%r{<span class="hashtag">(.*)</span>}, "\\1")
|
||||
cooked.gsub!(%r{<span class="hashtag-raw">(.*)</span>}, "\\1")
|
||||
cooked.gsub!(%r{<a name="(.*)" class="anchor" href="#\1*"></a>}, "")
|
||||
# we support data-attributes which is not in the spec
|
||||
cooked.gsub!("<pre data-code-startline=\"3\">", "<pre>")
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe CategoryHashtag do
|
||||
describe "#query_from_hashtag_slug" do
|
||||
fab!(:parent_category) { Fabricate(:category) }
|
||||
fab!(:child_category) { Fabricate(:category, parent_category: parent_category) }
|
||||
|
||||
it "should return the right result for a parent category slug" do
|
||||
expect(Category.query_from_hashtag_slug(parent_category.slug)).to eq(parent_category)
|
||||
end
|
||||
|
||||
it "should return the right result for a parent and child category slug" do
|
||||
expect(
|
||||
Category.query_from_hashtag_slug(
|
||||
"#{parent_category.slug}#{CategoryHashtag::SEPARATOR}#{child_category.slug}",
|
||||
),
|
||||
).to eq(child_category)
|
||||
end
|
||||
|
||||
it "should return nil for incorrect parent category slug" do
|
||||
expect(Category.query_from_hashtag_slug("random-slug")).to eq(nil)
|
||||
end
|
||||
|
||||
it "should return nil for incorrect parent and child category slug" do
|
||||
expect(
|
||||
Category.query_from_hashtag_slug("random-slug#{CategoryHashtag::SEPARATOR}random-slug"),
|
||||
).to eq(nil)
|
||||
end
|
||||
|
||||
it "should return nil for a non-existent root and a parent subcategory" do
|
||||
expect(
|
||||
Category.query_from_hashtag_slug(
|
||||
"non-existent#{CategoryHashtag::SEPARATOR}#{parent_category.slug}",
|
||||
),
|
||||
).to eq(nil)
|
||||
end
|
||||
|
||||
context "with multi-level categories" do
|
||||
before { SiteSetting.max_category_nesting = 3 }
|
||||
|
||||
it "should return the right result for a grand child category slug" do
|
||||
category = Fabricate(:category, parent_category: child_category)
|
||||
expect(
|
||||
Category.query_from_hashtag_slug(
|
||||
"#{child_category.slug}#{CategoryHashtag::SEPARATOR}#{category.slug}",
|
||||
),
|
||||
).to eq(category)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -566,7 +566,6 @@ RSpec.describe Email::Sender do
|
|||
end
|
||||
|
||||
it "changes the hashtags to the slug with a # symbol beforehand rather than the full name of the resource" do
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = true
|
||||
category = Fabricate(:category, slug: "dev")
|
||||
reply.update!(raw: reply.raw + "\n wow this is #dev")
|
||||
reply.rebake!
|
||||
|
|
|
@ -179,7 +179,6 @@ RSpec.describe Oneboxer do
|
|||
end
|
||||
|
||||
it "includes hashtag HTML" do
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = true
|
||||
category = Fabricate(:category, slug: "random")
|
||||
tag = Fabricate(:tag, name: "bug")
|
||||
public_post = Fabricate(:post, raw: "This post has some hashtags, #random and #bug")
|
||||
|
|
|
@ -16,37 +16,6 @@ RSpec.describe PrettyText::Helpers do
|
|||
end
|
||||
end
|
||||
|
||||
describe ".category_tag_hashtag_lookup" do
|
||||
fab!(:tag) { Fabricate(:tag, name: "somecooltag") }
|
||||
fab!(:category) do
|
||||
Fabricate(:category, name: "Some Awesome Category", slug: "someawesomecategory")
|
||||
end
|
||||
|
||||
it "handles tags based on slug with TAG_HASHTAG_POSTFIX" do
|
||||
expect(
|
||||
PrettyText::Helpers.category_tag_hashtag_lookup(
|
||||
+"somecooltag#{PrettyText::Helpers::TAG_HASHTAG_POSTFIX}",
|
||||
),
|
||||
).to eq([tag.url, "somecooltag"])
|
||||
end
|
||||
|
||||
it "handles categories based on slug" do
|
||||
expect(PrettyText::Helpers.category_tag_hashtag_lookup("someawesomecategory")).to eq(
|
||||
[category.url, "someawesomecategory"],
|
||||
)
|
||||
end
|
||||
|
||||
it "handles tags based on slug without TAG_HASHTAG_POSTFIX" do
|
||||
expect(PrettyText::Helpers.category_tag_hashtag_lookup(+"somecooltag")).to eq(
|
||||
[tag.url, "somecooltag"],
|
||||
)
|
||||
end
|
||||
|
||||
it "returns nil when no tag or category that matches exists" do
|
||||
expect(PrettyText::Helpers.category_tag_hashtag_lookup("blah")).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".hashtag_lookup" do
|
||||
fab!(:tag) { Fabricate(:tag, name: "somecooltag", description: "Coolest things ever") }
|
||||
fab!(:category) do
|
||||
|
|
|
@ -1722,50 +1722,6 @@ RSpec.describe PrettyText do
|
|||
end
|
||||
|
||||
it "produces hashtag links" do
|
||||
# TODO (martin) Remove when enable_experimental_hashtag_autocomplete is default for all sites
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = false
|
||||
|
||||
category = Fabricate(:category, name: "testing")
|
||||
category2 = Fabricate(:category, name: "known")
|
||||
Fabricate(:topic, tags: [Fabricate(:tag, name: "known")])
|
||||
|
||||
cooked = PrettyText.cook(" #unknown::tag #known #known::tag #testing")
|
||||
|
||||
[
|
||||
"<span class=\"hashtag\">#unknown::tag</span>",
|
||||
"<a class=\"hashtag\" href=\"#{category2.url}\">#<span>known</span></a>",
|
||||
"<a class=\"hashtag\" href=\"/tag/known\">#<span>known</span></a>",
|
||||
"<a class=\"hashtag\" href=\"#{category.url}\">#<span>testing</span></a>",
|
||||
].each { |element| expect(cooked).to include(element) }
|
||||
|
||||
cooked = PrettyText.cook("[`a` #known::tag here](http://example.com)")
|
||||
|
||||
html = <<~HTML
|
||||
<p><a href="http://example.com" rel="noopener nofollow ugc"><code>a</code> #known::tag here</a></p>
|
||||
HTML
|
||||
|
||||
expect(cooked).to eq(html.strip)
|
||||
|
||||
cooked = PrettyText.cook("<a href='http://example.com'>`a` #known::tag here</a>")
|
||||
|
||||
expect(cooked).to eq(html.strip)
|
||||
|
||||
cooked = PrettyText.cook("<A href='/a'>test</A> #known::tag")
|
||||
html = <<~HTML
|
||||
<p><a href="/a">test</a> <a class="hashtag" href="/tag/known">#<span>known</span></a></p>
|
||||
HTML
|
||||
|
||||
expect(cooked).to eq(html.strip)
|
||||
|
||||
# ensure it does not fight with the autolinker
|
||||
expect(PrettyText.cook(" http://somewhere.com/#known")).not_to include("hashtag")
|
||||
expect(PrettyText.cook(" http://somewhere.com/?#known")).not_to include("hashtag")
|
||||
expect(PrettyText.cook(" http://somewhere.com/?abc#known")).not_to include("hashtag")
|
||||
end
|
||||
|
||||
it "produces hashtag links when enable_experimental_hashtag_autocomplete is enabled" do
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = true
|
||||
|
||||
user = Fabricate(:user)
|
||||
category = Fabricate(:category, name: "testing", slug: "testing")
|
||||
category2 = Fabricate(:category, name: "known", slug: "known")
|
||||
|
@ -2059,11 +2015,8 @@ HTML
|
|||
end
|
||||
|
||||
it "does not replace hashtags and mentions" do
|
||||
# TODO (martin) Remove when enable_experimental_hashtag_autocomplete is default for all sites
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = false
|
||||
|
||||
Fabricate(:user, username: "test")
|
||||
category = Fabricate(:category, slug: "test")
|
||||
category = Fabricate(:category, slug: "test", name: "test")
|
||||
Fabricate(
|
||||
:watched_word,
|
||||
action: WatchedWord.actions[:replace],
|
||||
|
@ -2071,19 +2024,25 @@ HTML
|
|||
replacement: "discourse",
|
||||
)
|
||||
|
||||
expect(PrettyText.cook("@test #test test")).to match_html(<<~HTML)
|
||||
<p>
|
||||
<a class="mention" href="/u/test">@test</a>
|
||||
<a class="hashtag" href="/c/test/#{category.id}">#<span>test</span></a>
|
||||
discourse
|
||||
</p>
|
||||
HTML
|
||||
cooked = PrettyText.cook("@test #test test")
|
||||
expect(cooked).to have_tag("a", text: "@test", with: { class: "mention", href: "/u/test" })
|
||||
expect(cooked).to have_tag(
|
||||
"a",
|
||||
text: "test",
|
||||
with: {
|
||||
class: "hashtag-cooked",
|
||||
href: "/c/test/#{category.id}",
|
||||
"data-type": "category",
|
||||
"data-slug": category.slug,
|
||||
"data-id": category.id,
|
||||
},
|
||||
) do
|
||||
with_tag("span", with: { class: "hashtag-icon-placeholder" })
|
||||
end
|
||||
expect(cooked).to include("discourse")
|
||||
end
|
||||
|
||||
it "does not replace hashtags and mentions when watched words are regular expressions" do
|
||||
# TODO (martin) Remove when enable_experimental_hashtag_autocomplete is default for all sites
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = false
|
||||
|
||||
SiteSetting.watched_words_regular_expressions = true
|
||||
|
||||
Fabricate(:user, username: "test")
|
||||
|
@ -2095,19 +2054,6 @@ HTML
|
|||
replacement: "discourse",
|
||||
)
|
||||
|
||||
cooked = PrettyText.cook("@test #test test")
|
||||
expect(cooked).to have_tag("a", text: "@test", with: { class: "mention", href: "/u/test" })
|
||||
expect(cooked).to have_tag(
|
||||
"a",
|
||||
text: "#test",
|
||||
with: {
|
||||
class: "hashtag",
|
||||
href: "/c/test/#{category.id}",
|
||||
},
|
||||
)
|
||||
expect(cooked).to include("tdiscourset")
|
||||
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = true
|
||||
cooked = PrettyText.cook("@test #test test")
|
||||
expect(cooked).to have_tag("a", text: "@test", with: { class: "mention", href: "/u/test" })
|
||||
expect(cooked).to have_tag(
|
||||
|
|
|
@ -1451,4 +1451,37 @@ RSpec.describe Category do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#slug_ref" do
|
||||
fab!(:category) { Fabricate(:category, slug: "foo") }
|
||||
|
||||
it "returns the slug for categories without parents" do
|
||||
expect(category.slug_ref).to eq("foo")
|
||||
end
|
||||
|
||||
context "for category with parent" do
|
||||
fab!(:subcategory) { Fabricate(:category, parent_category: category, slug: "bar") }
|
||||
|
||||
it "returns the parent and child slug ref with separator" do
|
||||
expect(subcategory.slug_ref).to eq("foo#{Category::SLUG_REF_SEPARATOR}bar")
|
||||
end
|
||||
end
|
||||
|
||||
context "for category with multiple parents" do
|
||||
let(:subcategory_1) { Fabricate(:category, parent_category: category, slug: "bar") }
|
||||
let(:subcategory_2) { Fabricate(:category, parent_category: subcategory_1, slug: "boo") }
|
||||
|
||||
before { SiteSetting.max_category_nesting = 3 }
|
||||
|
||||
it "returns the parent and child slug ref with separator" do
|
||||
expect(subcategory_2.slug_ref(depth: 2)).to eq(
|
||||
"foo#{Category::SLUG_REF_SEPARATOR}bar#{Category::SLUG_REF_SEPARATOR}boo",
|
||||
)
|
||||
end
|
||||
|
||||
it "allows limiting depth" do
|
||||
expect(subcategory_2.slug_ref(depth: 1)).to eq("bar#{Category::SLUG_REF_SEPARATOR}boo")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,305 +20,184 @@ RSpec.describe HashtagsController do
|
|||
end
|
||||
|
||||
describe "#lookup" do
|
||||
context "when enable_experimental_hashtag_autocomplete disabled" do
|
||||
# TODO (martin) Remove when enable_experimental_hashtag_autocomplete is default for all sites
|
||||
before { SiteSetting.enable_experimental_hashtag_autocomplete = false }
|
||||
context "when logged in" do
|
||||
context "as regular user" do
|
||||
before { sign_in(Fabricate(:user)) }
|
||||
context "when logged in" do
|
||||
context "as regular user" do
|
||||
before { sign_in(Fabricate(:user)) }
|
||||
|
||||
it "returns only valid categories and tags" do
|
||||
get "/hashtags.json",
|
||||
params: {
|
||||
slugs: [category.slug, private_category.slug, "none", tag.name, hidden_tag.name],
|
||||
}
|
||||
it "returns only valid categories and tags" do
|
||||
get "/hashtags.json",
|
||||
params: {
|
||||
slugs: [category.slug, private_category.slug, "none", tag.name, hidden_tag.name],
|
||||
order: %w[category tag],
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body).to eq(
|
||||
"categories" => {
|
||||
category.slug => category.url,
|
||||
},
|
||||
"tags" => {
|
||||
tag.name => tag.full_url,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "handles tags with the TAG_HASHTAG_POSTFIX" do
|
||||
get "/hashtags.json",
|
||||
params: {
|
||||
slugs: ["#{tag.name}#{PrettyText::Helpers::TAG_HASHTAG_POSTFIX}"],
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body).to eq(
|
||||
"categories" => {
|
||||
},
|
||||
"tags" => {
|
||||
tag.name => tag.full_url,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does not return restricted categories or hidden tags" do
|
||||
get "/hashtags.json", params: { slugs: [private_category.slug, hidden_tag.name] }
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body).to eq("categories" => {}, "tags" => {})
|
||||
end
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body).to eq(
|
||||
{
|
||||
"category" => [
|
||||
{
|
||||
"relative_url" => category.url,
|
||||
"text" => category.name,
|
||||
"description" => nil,
|
||||
"icon" => "folder",
|
||||
"type" => "category",
|
||||
"ref" => category.slug,
|
||||
"slug" => category.slug,
|
||||
"id" => category.id,
|
||||
},
|
||||
],
|
||||
"tag" => [
|
||||
{
|
||||
"relative_url" => tag.url,
|
||||
"text" => tag.name,
|
||||
"description" => nil,
|
||||
"icon" => "tag",
|
||||
"type" => "tag",
|
||||
"ref" => tag.name,
|
||||
"slug" => tag.name,
|
||||
"secondary_text" => "x0",
|
||||
"id" => tag.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
context "as admin" do
|
||||
fab!(:admin) { Fabricate(:admin) }
|
||||
it "handles tags with the ::tag type suffix" do
|
||||
get "/hashtags.json", params: { slugs: ["#{tag.name}::tag"], order: %w[category tag] }
|
||||
|
||||
before { sign_in(admin) }
|
||||
|
||||
it "returns restricted categories and hidden tags" do
|
||||
group.add(admin)
|
||||
|
||||
get "/hashtags.json", params: { slugs: [private_category.slug, hidden_tag.name] }
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body).to eq(
|
||||
"categories" => {
|
||||
private_category.slug => private_category.url,
|
||||
},
|
||||
"tags" => {
|
||||
hidden_tag.name => hidden_tag.full_url,
|
||||
},
|
||||
)
|
||||
end
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body).to eq(
|
||||
{
|
||||
"category" => [],
|
||||
"tag" => [
|
||||
{
|
||||
"relative_url" => tag.url,
|
||||
"text" => tag.name,
|
||||
"description" => nil,
|
||||
"icon" => "tag",
|
||||
"type" => "tag",
|
||||
"ref" => "#{tag.name}::tag",
|
||||
"slug" => tag.name,
|
||||
"secondary_text" => "x0",
|
||||
"id" => tag.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
context "with sub-sub-categories" do
|
||||
before do
|
||||
SiteSetting.max_category_nesting = 3
|
||||
sign_in(Fabricate(:user))
|
||||
end
|
||||
it "does not return restricted categories or hidden tags" do
|
||||
get "/hashtags.json",
|
||||
params: {
|
||||
slugs: [private_category.slug, hidden_tag.name],
|
||||
order: %w[category tag],
|
||||
}
|
||||
|
||||
it "works" do
|
||||
foo = Fabricate(:category_with_definition, slug: "foo")
|
||||
foobar = Fabricate(:category_with_definition, slug: "bar", parent_category_id: foo.id)
|
||||
foobarbaz =
|
||||
Fabricate(:category_with_definition, slug: "baz", parent_category_id: foobar.id)
|
||||
|
||||
qux = Fabricate(:category_with_definition, slug: "qux")
|
||||
quxbar = Fabricate(:category_with_definition, slug: "bar", parent_category_id: qux.id)
|
||||
quxbarbaz =
|
||||
Fabricate(:category_with_definition, slug: "baz", parent_category_id: quxbar.id)
|
||||
|
||||
invalid_slugs = [":"]
|
||||
child_slugs = %w[bar baz]
|
||||
deeply_nested_slugs = %w[foo:bar:baz qux:bar:baz]
|
||||
get "/hashtags.json",
|
||||
params: {
|
||||
slugs:
|
||||
invalid_slugs + child_slugs + deeply_nested_slugs +
|
||||
%w[foo foo:bar bar:baz qux qux:bar],
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body["categories"]).to eq(
|
||||
"foo" => foo.url,
|
||||
"foo:bar" => foobar.url,
|
||||
"bar:baz" => foobarbaz.id < quxbarbaz.id ? foobarbaz.url : quxbarbaz.url,
|
||||
"qux" => qux.url,
|
||||
"qux:bar" => quxbar.url,
|
||||
)
|
||||
end
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body).to eq({ "category" => [], "tag" => [] })
|
||||
end
|
||||
end
|
||||
|
||||
context "when not logged in" do
|
||||
it "returns invalid access" do
|
||||
get "/hashtags.json", params: { slugs: [] }
|
||||
expect(response.status).to eq(403)
|
||||
context "as admin" do
|
||||
fab!(:admin) { Fabricate(:admin) }
|
||||
|
||||
before { sign_in(admin) }
|
||||
|
||||
it "returns restricted categories and hidden tags" do
|
||||
group.add(admin)
|
||||
|
||||
get "/hashtags.json",
|
||||
params: {
|
||||
slugs: [private_category.slug, hidden_tag.name],
|
||||
order: %w[category tag],
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body).to eq(
|
||||
{
|
||||
"category" => [
|
||||
{
|
||||
"relative_url" => private_category.url,
|
||||
"text" => private_category.name,
|
||||
"description" => nil,
|
||||
"icon" => "folder",
|
||||
"type" => "category",
|
||||
"ref" => private_category.slug,
|
||||
"slug" => private_category.slug,
|
||||
"id" => private_category.id,
|
||||
},
|
||||
],
|
||||
"tag" => [
|
||||
{
|
||||
"relative_url" => hidden_tag.url,
|
||||
"text" => hidden_tag.name,
|
||||
"description" => nil,
|
||||
"icon" => "tag",
|
||||
"type" => "tag",
|
||||
"ref" => hidden_tag.name,
|
||||
"slug" => hidden_tag.name,
|
||||
"secondary_text" => "x0",
|
||||
"id" => hidden_tag.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "with sub-sub-categories" do
|
||||
before do
|
||||
SiteSetting.max_category_nesting = 3
|
||||
sign_in(Fabricate(:user))
|
||||
end
|
||||
|
||||
it "works" do
|
||||
foo = Fabricate(:category_with_definition, slug: "foo")
|
||||
foobar = Fabricate(:category_with_definition, slug: "bar", parent_category_id: foo.id)
|
||||
foobarbaz =
|
||||
Fabricate(:category_with_definition, slug: "baz", parent_category_id: foobar.id)
|
||||
|
||||
qux = Fabricate(:category_with_definition, slug: "qux")
|
||||
quxbar = Fabricate(:category_with_definition, slug: "bar", parent_category_id: qux.id)
|
||||
quxbarbaz =
|
||||
Fabricate(:category_with_definition, slug: "baz", parent_category_id: quxbar.id)
|
||||
|
||||
invalid_slugs = [":"]
|
||||
child_slugs = %w[bar baz]
|
||||
deeply_nested_slugs = %w[foo:bar:baz qux:bar:baz]
|
||||
get "/hashtags.json",
|
||||
params: {
|
||||
slugs:
|
||||
invalid_slugs + child_slugs + deeply_nested_slugs +
|
||||
%w[foo foo:bar bar:baz qux qux:bar],
|
||||
order: %w[category tag],
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
found_categories = response.parsed_body["category"]
|
||||
expect(found_categories.map { |c| c["ref"] }).to match_array(
|
||||
%w[foo foo:bar bar:baz qux qux:bar],
|
||||
)
|
||||
expect(found_categories.find { |c| c["ref"] == "foo" }["relative_url"]).to eq(foo.url)
|
||||
expect(found_categories.find { |c| c["ref"] == "foo:bar" }["relative_url"]).to eq(
|
||||
foobar.url,
|
||||
)
|
||||
expect(found_categories.find { |c| c["ref"] == "bar:baz" }["relative_url"]).to eq(
|
||||
foobarbaz.url,
|
||||
)
|
||||
expect(found_categories.find { |c| c["ref"] == "qux" }["relative_url"]).to eq(qux.url)
|
||||
expect(found_categories.find { |c| c["ref"] == "qux:bar" }["relative_url"]).to eq(
|
||||
quxbar.url,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when enable_experimental_hashtag_autocomplete enabled" do
|
||||
before { SiteSetting.enable_experimental_hashtag_autocomplete = true }
|
||||
|
||||
context "when logged in" do
|
||||
context "as regular user" do
|
||||
before { sign_in(Fabricate(:user)) }
|
||||
|
||||
it "returns only valid categories and tags" do
|
||||
get "/hashtags.json",
|
||||
params: {
|
||||
slugs: [category.slug, private_category.slug, "none", tag.name, hidden_tag.name],
|
||||
order: %w[category tag],
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body).to eq(
|
||||
{
|
||||
"category" => [
|
||||
{
|
||||
"relative_url" => category.url,
|
||||
"text" => category.name,
|
||||
"description" => nil,
|
||||
"icon" => "folder",
|
||||
"type" => "category",
|
||||
"ref" => category.slug,
|
||||
"slug" => category.slug,
|
||||
"id" => category.id,
|
||||
},
|
||||
],
|
||||
"tag" => [
|
||||
{
|
||||
"relative_url" => tag.url,
|
||||
"text" => tag.name,
|
||||
"description" => nil,
|
||||
"icon" => "tag",
|
||||
"type" => "tag",
|
||||
"ref" => tag.name,
|
||||
"slug" => tag.name,
|
||||
"secondary_text" => "x0",
|
||||
"id" => tag.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "handles tags with the ::tag type suffix" do
|
||||
get "/hashtags.json", params: { slugs: ["#{tag.name}::tag"], order: %w[category tag] }
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body).to eq(
|
||||
{
|
||||
"category" => [],
|
||||
"tag" => [
|
||||
{
|
||||
"relative_url" => tag.url,
|
||||
"text" => tag.name,
|
||||
"description" => nil,
|
||||
"icon" => "tag",
|
||||
"type" => "tag",
|
||||
"ref" => "#{tag.name}::tag",
|
||||
"slug" => tag.name,
|
||||
"secondary_text" => "x0",
|
||||
"id" => tag.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does not return restricted categories or hidden tags" do
|
||||
get "/hashtags.json",
|
||||
params: {
|
||||
slugs: [private_category.slug, hidden_tag.name],
|
||||
order: %w[category tag],
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body).to eq({ "category" => [], "tag" => [] })
|
||||
end
|
||||
end
|
||||
|
||||
context "as admin" do
|
||||
fab!(:admin) { Fabricate(:admin) }
|
||||
|
||||
before { sign_in(admin) }
|
||||
|
||||
it "returns restricted categories and hidden tags" do
|
||||
group.add(admin)
|
||||
|
||||
get "/hashtags.json",
|
||||
params: {
|
||||
slugs: [private_category.slug, hidden_tag.name],
|
||||
order: %w[category tag],
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body).to eq(
|
||||
{
|
||||
"category" => [
|
||||
{
|
||||
"relative_url" => private_category.url,
|
||||
"text" => private_category.name,
|
||||
"description" => nil,
|
||||
"icon" => "folder",
|
||||
"type" => "category",
|
||||
"ref" => private_category.slug,
|
||||
"slug" => private_category.slug,
|
||||
"id" => private_category.id,
|
||||
},
|
||||
],
|
||||
"tag" => [
|
||||
{
|
||||
"relative_url" => hidden_tag.url,
|
||||
"text" => hidden_tag.name,
|
||||
"description" => nil,
|
||||
"icon" => "tag",
|
||||
"type" => "tag",
|
||||
"ref" => hidden_tag.name,
|
||||
"slug" => hidden_tag.name,
|
||||
"secondary_text" => "x0",
|
||||
"id" => hidden_tag.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "with sub-sub-categories" do
|
||||
before do
|
||||
SiteSetting.max_category_nesting = 3
|
||||
sign_in(Fabricate(:user))
|
||||
end
|
||||
|
||||
it "works" do
|
||||
foo = Fabricate(:category_with_definition, slug: "foo")
|
||||
foobar = Fabricate(:category_with_definition, slug: "bar", parent_category_id: foo.id)
|
||||
foobarbaz =
|
||||
Fabricate(:category_with_definition, slug: "baz", parent_category_id: foobar.id)
|
||||
|
||||
qux = Fabricate(:category_with_definition, slug: "qux")
|
||||
quxbar = Fabricate(:category_with_definition, slug: "bar", parent_category_id: qux.id)
|
||||
quxbarbaz =
|
||||
Fabricate(:category_with_definition, slug: "baz", parent_category_id: quxbar.id)
|
||||
|
||||
invalid_slugs = [":"]
|
||||
child_slugs = %w[bar baz]
|
||||
deeply_nested_slugs = %w[foo:bar:baz qux:bar:baz]
|
||||
get "/hashtags.json",
|
||||
params: {
|
||||
slugs:
|
||||
invalid_slugs + child_slugs + deeply_nested_slugs +
|
||||
%w[foo foo:bar bar:baz qux qux:bar],
|
||||
order: %w[category tag],
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
found_categories = response.parsed_body["category"]
|
||||
expect(found_categories.map { |c| c["ref"] }).to match_array(
|
||||
%w[foo foo:bar bar:baz qux qux:bar],
|
||||
)
|
||||
expect(found_categories.find { |c| c["ref"] == "foo" }["relative_url"]).to eq(foo.url)
|
||||
expect(found_categories.find { |c| c["ref"] == "foo:bar" }["relative_url"]).to eq(
|
||||
foobar.url,
|
||||
)
|
||||
expect(found_categories.find { |c| c["ref"] == "bar:baz" }["relative_url"]).to eq(
|
||||
foobarbaz.url,
|
||||
)
|
||||
expect(found_categories.find { |c| c["ref"] == "qux" }["relative_url"]).to eq(qux.url)
|
||||
expect(found_categories.find { |c| c["ref"] == "qux:bar" }["relative_url"]).to eq(
|
||||
quxbar.url,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when not logged in" do
|
||||
it "returns invalid access" do
|
||||
get "/hashtags.json", params: { slugs: [], order: %w[category tag] }
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
context "when not logged in" do
|
||||
it "returns invalid access" do
|
||||
get "/hashtags.json", params: { slugs: [], order: %w[category tag] }
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -313,53 +313,6 @@ RSpec.describe HashtagAutocompleteService do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#lookup_old" do
|
||||
fab!(:tag2) { Fabricate(:tag, name: "fiction-books") }
|
||||
|
||||
it "returns categories and tags in a hash format with the slug and url" do
|
||||
result = service.lookup_old(%w[the-book-club great-books fiction-books])
|
||||
expect(result[:categories]).to eq({ "the-book-club" => "/c/the-book-club/#{category1.id}" })
|
||||
expect(result[:tags]).to eq(
|
||||
{
|
||||
"fiction-books" => "http://test.localhost/tag/fiction-books",
|
||||
"great-books" => "http://test.localhost/tag/great-books",
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does not include categories the user cannot access" do
|
||||
category1.update!(read_restricted: true)
|
||||
result = service.lookup_old(%w[the-book-club great-books fiction-books])
|
||||
expect(result[:categories]).to eq({})
|
||||
end
|
||||
|
||||
it "does not include tags the user cannot access" do
|
||||
Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: ["great-books"])
|
||||
result = service.lookup_old(%w[the-book-club great-books fiction-books])
|
||||
expect(result[:tags]).to eq({ "fiction-books" => "http://test.localhost/tag/fiction-books" })
|
||||
end
|
||||
|
||||
it "handles tags which have the ::tag suffix" do
|
||||
result = service.lookup_old(%w[the-book-club great-books::tag fiction-books])
|
||||
expect(result[:tags]).to eq(
|
||||
{
|
||||
"fiction-books" => "http://test.localhost/tag/fiction-books",
|
||||
"great-books" => "http://test.localhost/tag/great-books",
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
context "when not tagging_enabled" do
|
||||
before { SiteSetting.tagging_enabled = false }
|
||||
|
||||
it "does not return tags" do
|
||||
result = service.lookup_old(%w[the-book-club great-books fiction-books])
|
||||
expect(result[:categories]).to eq({ "the-book-club" => "/c/the-book-club/#{category1.id}" })
|
||||
expect(result[:tags]).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#lookup" do
|
||||
fab!(:tag2) { Fabricate(:tag, name: "fiction-books") }
|
||||
|
||||
|
|
|
@ -1332,8 +1332,7 @@ RSpec.describe PostAlerter do
|
|||
expect(JSON.parse(body)).to eq(payload)
|
||||
end
|
||||
|
||||
it "does not have invalid HTML in the excerpt when enable_experimental_hashtag_autocomplete is enabled" do
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = true
|
||||
it "does not have invalid HTML in the excerpt" do
|
||||
Fabricate(:category, slug: "random")
|
||||
Jobs.run_immediately!
|
||||
body = nil
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
describe "Using #hashtag autocompletion to search for and lookup categories and tags",
|
||||
type: :system do
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
fab!(:current_user) { Fabricate(:user) }
|
||||
fab!(:category) do
|
||||
Fabricate(:category, name: "Cool Category", slug: "cool-cat", topic_count: 3234)
|
||||
end
|
||||
|
@ -16,10 +16,7 @@ describe "Using #hashtag autocompletion to search for and lookup categories and
|
|||
let(:uncategorized_category) { Category.find(SiteSetting.uncategorized_category_id) }
|
||||
let(:topic_page) { PageObjects::Pages::Topic.new }
|
||||
|
||||
before do
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = true
|
||||
sign_in user
|
||||
end
|
||||
before { sign_in(current_user) }
|
||||
|
||||
def visit_topic_and_initiate_autocomplete(initiation_text: "something #co", expected_count: 2)
|
||||
topic_page.visit_topic_and_open_composer(topic)
|
||||
|
|
|
@ -9,9 +9,8 @@ RSpec.describe "tasks/hashtags" do
|
|||
describe "hashtag:mark_old_format_for_rebake" do
|
||||
fab!(:category) { Fabricate(:category, slug: "support") }
|
||||
|
||||
before { SiteSetting.enable_experimental_hashtag_autocomplete = false }
|
||||
|
||||
it "sets the baked_version to 0 for matching posts" do
|
||||
hashtag_html = PrettyText.cook("#support").gsub("<p>", "").gsub("</p>", "")
|
||||
post_1 = Fabricate(:post, raw: "This is a cool #support hashtag")
|
||||
post_2 =
|
||||
Fabricate(
|
||||
|
@ -19,10 +18,12 @@ RSpec.describe "tasks/hashtags" do
|
|||
raw:
|
||||
"Some other thing which will not match <a class=\"hashtag-wow\">some weird custom thing</a>",
|
||||
)
|
||||
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = true
|
||||
post_3 = Fabricate(:post, raw: "This is a cool #support hashtag")
|
||||
SiteSetting.enable_experimental_hashtag_autocomplete = false
|
||||
|
||||
# Update to use the old hashtag format.
|
||||
post_1.update!(
|
||||
cooked: post_1.cooked.gsub(hashtag_html, "<span class=\"hashtag\"'>#support</span>"),
|
||||
)
|
||||
|
||||
capture_stdout { Rake::Task["hashtags:mark_old_format_for_rebake"].invoke }
|
||||
|
||||
|
|
Loading…
Reference in New Issue