mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 21:43:38 +08:00
DEV: Clean up hashtag code (#25397)
* Delete dead code * Split up hashtag-autocomplete into more logical modules
This commit is contained in:
parent
ef87629526
commit
c7860173c1
|
@ -10,7 +10,7 @@ import { ajax } from "discourse/lib/ajax";
|
||||||
import {
|
import {
|
||||||
fetchUnseenHashtagsInContext,
|
fetchUnseenHashtagsInContext,
|
||||||
linkSeenHashtagsInContext,
|
linkSeenHashtagsInContext,
|
||||||
} from "discourse/lib/hashtag-autocomplete";
|
} from "discourse/lib/hashtag-decorator";
|
||||||
import {
|
import {
|
||||||
fetchUnseenMentions,
|
fetchUnseenMentions,
|
||||||
linkSeenMentions,
|
linkSeenMentions,
|
||||||
|
|
|
@ -11,10 +11,8 @@ import { Promise } from "rsvp";
|
||||||
import InsertHyperlink from "discourse/components/modal/insert-hyperlink";
|
import InsertHyperlink from "discourse/components/modal/insert-hyperlink";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import { SKIP } from "discourse/lib/autocomplete";
|
import { SKIP } from "discourse/lib/autocomplete";
|
||||||
import {
|
import { setupHashtagAutocomplete } from "discourse/lib/hashtag-autocomplete";
|
||||||
linkSeenHashtagsInContext,
|
import { linkSeenHashtagsInContext } from "discourse/lib/hashtag-decorator";
|
||||||
setupHashtagAutocomplete,
|
|
||||||
} from "discourse/lib/hashtag-autocomplete";
|
|
||||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||||
import { PLATFORM_KEY_MODIFIER } from "discourse/lib/keyboard-shortcuts";
|
import { PLATFORM_KEY_MODIFIER } from "discourse/lib/keyboard-shortcuts";
|
||||||
import { linkSeenMentions } from "discourse/lib/link-mentions";
|
import { linkSeenMentions } from "discourse/lib/link-mentions";
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { getHashtagTypeClasses } from "discourse/lib/hashtag-autocomplete";
|
import { getHashtagTypeClasses } from "discourse/lib/hashtag-type-registry";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
after: "category-color-css-generator",
|
after: "category-color-css-generator",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { decorateHashtags } from "discourse/lib/hashtag-autocomplete";
|
import { decorateHashtags } from "discourse/lib/hashtag-decorator";
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -2,6 +2,17 @@ import { cancel } from "@ember/runloop";
|
||||||
import { htmlSafe } from "@ember/template";
|
import { htmlSafe } from "@ember/template";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import { CANCELLED_STATUS } from "discourse/lib/autocomplete";
|
import { CANCELLED_STATUS } from "discourse/lib/autocomplete";
|
||||||
|
import {
|
||||||
|
decorateHashtags as decorateHashtagsNew,
|
||||||
|
fetchUnseenHashtagsInContext as fetchUnseenHashtagsInContextNew,
|
||||||
|
generatePlaceholderHashtagHTML as generatePlaceholderHashtagHTMLNew,
|
||||||
|
linkSeenHashtagsInContext as linkSeenHashtagsInContextNew,
|
||||||
|
} from "discourse/lib/hashtag-decorator";
|
||||||
|
import {
|
||||||
|
cleanUpHashtagTypeClasses as cleanUpHashtagTypeClassesNew,
|
||||||
|
getHashtagTypeClasses as getHashtagTypeClassesNew,
|
||||||
|
registerHashtagType as registerHashtagTypeNew,
|
||||||
|
} from "discourse/lib/hashtag-type-registry";
|
||||||
import { emojiUnescape } from "discourse/lib/text";
|
import { emojiUnescape } from "discourse/lib/text";
|
||||||
import {
|
import {
|
||||||
caretPosition,
|
caretPosition,
|
||||||
|
@ -10,58 +21,88 @@ import {
|
||||||
} from "discourse/lib/utilities";
|
} from "discourse/lib/utilities";
|
||||||
import { INPUT_DELAY, isTesting } from "discourse-common/config/environment";
|
import { INPUT_DELAY, isTesting } from "discourse-common/config/environment";
|
||||||
import discourseDebounce from "discourse-common/lib/debounce";
|
import discourseDebounce from "discourse-common/lib/debounce";
|
||||||
import domFromString from "discourse-common/lib/dom-from-string";
|
import deprecated from "discourse-common/lib/deprecated";
|
||||||
import discourseLater from "discourse-common/lib/later";
|
import discourseLater from "discourse-common/lib/later";
|
||||||
import { findRawTemplate } from "discourse-common/lib/raw-templates";
|
import { findRawTemplate } from "discourse-common/lib/raw-templates";
|
||||||
|
|
||||||
let hashtagTypeClasses = {};
|
// TODO (martin) Remove this once plugins have changed to use hashtag-decorator and
|
||||||
export function registerHashtagType(type, typeClassInstance) {
|
// hashtag-type-registry imports
|
||||||
hashtagTypeClasses[type] = typeClassInstance;
|
export function fetchUnseenHashtagsInContext() {
|
||||||
|
deprecated(
|
||||||
|
`fetchUnseenHashtagsInContext is has been moved to the module 'discourse/lib/hashtag-decorator'`,
|
||||||
|
{
|
||||||
|
id: "discourse.hashtag.fetchUnseenHashtagsInContext",
|
||||||
|
since: "3.2.0.beta5-dev",
|
||||||
|
dropFrom: "3.2.1",
|
||||||
}
|
}
|
||||||
export function cleanUpHashtagTypeClasses() {
|
);
|
||||||
hashtagTypeClasses = {};
|
return fetchUnseenHashtagsInContextNew(...arguments);
|
||||||
|
}
|
||||||
|
export function linkSeenHashtagsInContext() {
|
||||||
|
deprecated(
|
||||||
|
`linkSeenHashtagsInContext is has been moved to the module 'discourse/lib/hashtag-decorator'`,
|
||||||
|
{
|
||||||
|
id: "discourse.hashtag.linkSeenHashtagsInContext",
|
||||||
|
since: "3.2.0.beta5-dev",
|
||||||
|
dropFrom: "3.2.1",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return linkSeenHashtagsInContextNew(...arguments);
|
||||||
|
}
|
||||||
|
export function generatePlaceholderHashtagHTML() {
|
||||||
|
deprecated(
|
||||||
|
`generatePlaceholderHashtagHTML is has been moved to the module 'discourse/lib/hashtag-decorator'`,
|
||||||
|
{
|
||||||
|
id: "discourse.hashtag.generatePlaceholderHashtagHTML",
|
||||||
|
since: "3.2.0.beta5-dev",
|
||||||
|
dropFrom: "3.2.1",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return generatePlaceholderHashtagHTMLNew(...arguments);
|
||||||
|
}
|
||||||
|
export function decorateHashtags() {
|
||||||
|
deprecated(
|
||||||
|
`decorateHashtags is has been moved to the module 'discourse/lib/hashtag-decorator'`,
|
||||||
|
{
|
||||||
|
id: "discourse.hashtag.decorateHashtags",
|
||||||
|
since: "3.2.0.beta5-dev",
|
||||||
|
dropFrom: "3.2.1",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return decorateHashtagsNew(...arguments);
|
||||||
}
|
}
|
||||||
export function getHashtagTypeClasses() {
|
export function getHashtagTypeClasses() {
|
||||||
return hashtagTypeClasses;
|
deprecated(
|
||||||
|
`getHashtagTypeClasses is has been moved to the module 'discourse/lib/hashtag-type-registry'`,
|
||||||
|
{
|
||||||
|
id: "discourse.hashtag.getHashtagTypeClasses",
|
||||||
|
since: "3.2.0.beta5-dev",
|
||||||
|
dropFrom: "3.2.1",
|
||||||
}
|
}
|
||||||
export function decorateHashtags(element, site) {
|
|
||||||
element.querySelectorAll(".hashtag-cooked").forEach((hashtagEl) => {
|
|
||||||
// Replace the empty icon placeholder span with actual icon HTML.
|
|
||||||
const iconPlaceholderEl = hashtagEl.querySelector(
|
|
||||||
".hashtag-icon-placeholder"
|
|
||||||
);
|
);
|
||||||
const hashtagType = hashtagEl.dataset.type;
|
return getHashtagTypeClassesNew(...arguments);
|
||||||
const hashtagTypeClass = getHashtagTypeClasses()[hashtagType];
|
|
||||||
if (iconPlaceholderEl && hashtagTypeClass) {
|
|
||||||
const hashtagIconHTML = hashtagTypeClass
|
|
||||||
.generateIconHTML({
|
|
||||||
icon: site.hashtag_icons[hashtagType],
|
|
||||||
id: hashtagEl.dataset.id,
|
|
||||||
})
|
|
||||||
.trim();
|
|
||||||
iconPlaceholderEl.replaceWith(domFromString(hashtagIconHTML)[0]);
|
|
||||||
}
|
}
|
||||||
|
export function registerHashtagType() {
|
||||||
// Add an aria-label to the hashtag element so that screen readers
|
deprecated(
|
||||||
// can read the hashtag text.
|
`registerHashtagType is has been moved to the module 'discourse/lib/hashtag-type-registry'`,
|
||||||
hashtagEl.setAttribute("aria-label", `${hashtagEl.innerText.trim()}`);
|
{
|
||||||
});
|
id: "discourse.hashtag.registerHashtagType",
|
||||||
|
since: "3.2.0.beta5-dev",
|
||||||
|
dropFrom: "3.2.1",
|
||||||
}
|
}
|
||||||
|
);
|
||||||
export function generatePlaceholderHashtagHTML(type, spanEl, data) {
|
return registerHashtagTypeNew(...arguments);
|
||||||
// NOTE: When changing the HTML structure here, you must also change
|
}
|
||||||
// it in the hashtag-autocomplete markdown rule, and vice-versa.
|
export function cleanUpHashtagTypeClasses() {
|
||||||
const link = document.createElement("a");
|
deprecated(
|
||||||
link.classList.add("hashtag-cooked");
|
`cleanUpHashtagTypeClasses is has been moved to the module 'discourse/lib/hashtag-type-registry'`,
|
||||||
link.href = data.relative_url;
|
{
|
||||||
link.dataset.type = type;
|
id: "discourse.hashtag.cleanUpHashtagTypeClasses",
|
||||||
link.dataset.id = data.id;
|
since: "3.2.0.beta5-dev",
|
||||||
link.dataset.slug = data.slug;
|
dropFrom: "3.2.1",
|
||||||
const hashtagTypeClass = new getHashtagTypeClasses()[type];
|
}
|
||||||
link.innerHTML = `${hashtagTypeClass.generateIconHTML(
|
);
|
||||||
data
|
return cleanUpHashtagTypeClassesNew(...arguments);
|
||||||
)}<span>${emojiUnescape(data.text)}</span>`;
|
|
||||||
spanEl.replaceWith(link);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,57 +148,6 @@ export function hashtagTriggerRule(textarea) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkedHashtags = new Set();
|
|
||||||
let seenHashtags = {};
|
|
||||||
|
|
||||||
// NOTE: For future maintainers, the hashtag lookup here does not take
|
|
||||||
// into account mixed contexts -- for instance, a chat quote inside a post
|
|
||||||
// or a post quote inside a chat message, so this may
|
|
||||||
// not provide an accurate priority lookup for hashtags without a ::type suffix in those
|
|
||||||
// cases.
|
|
||||||
export function fetchUnseenHashtagsInContext(
|
|
||||||
contextualHashtagConfiguration,
|
|
||||||
slugs
|
|
||||||
) {
|
|
||||||
return ajax("/hashtags", {
|
|
||||||
data: { slugs, order: contextualHashtagConfiguration },
|
|
||||||
}).then((response) => {
|
|
||||||
Object.keys(response).forEach((type) => {
|
|
||||||
seenHashtags[type] = seenHashtags[type] || {};
|
|
||||||
response[type].forEach((item) => {
|
|
||||||
seenHashtags[type][item.ref] = seenHashtags[type][item.ref] || item;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
slugs.forEach(checkedHashtags.add, checkedHashtags);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function linkSeenHashtagsInContext(
|
|
||||||
contextualHashtagConfiguration,
|
|
||||||
elem
|
|
||||||
) {
|
|
||||||
const hashtagSpans = [...(elem?.querySelectorAll("span.hashtag-raw") || [])];
|
|
||||||
if (hashtagSpans.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const slugs = [
|
|
||||||
...hashtagSpans.map((span) => span.innerText.replace("#", "")),
|
|
||||||
];
|
|
||||||
|
|
||||||
hashtagSpans.forEach((hashtagSpan, index) => {
|
|
||||||
_findAndReplaceSeenHashtagPlaceholder(
|
|
||||||
slugs[index],
|
|
||||||
contextualHashtagConfiguration,
|
|
||||||
hashtagSpan
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return slugs
|
|
||||||
.map((slug) => slug.toLowerCase())
|
|
||||||
.uniq()
|
|
||||||
.filter((slug) => !checkedHashtags.has(slug));
|
|
||||||
}
|
|
||||||
|
|
||||||
function _setup(
|
function _setup(
|
||||||
contextualHashtagConfiguration,
|
contextualHashtagConfiguration,
|
||||||
$textArea,
|
$textArea,
|
||||||
|
@ -236,7 +226,7 @@ function _searchRequest(term, contextualHashtagConfiguration, resultFunc) {
|
||||||
// Convert :emoji: in the result text to HTML safely.
|
// Convert :emoji: in the result text to HTML safely.
|
||||||
result.text = htmlSafe(emojiUnescape(escapeExpression(result.text)));
|
result.text = htmlSafe(emojiUnescape(escapeExpression(result.text)));
|
||||||
|
|
||||||
const hashtagType = getHashtagTypeClasses()[result.type];
|
const hashtagType = getHashtagTypeClassesNew()[result.type];
|
||||||
result.icon = hashtagType.generateIconHTML({
|
result.icon = hashtagType.generateIconHTML({
|
||||||
icon: result.icon,
|
icon: result.icon,
|
||||||
id: result.id,
|
id: result.id,
|
||||||
|
@ -249,17 +239,3 @@ function _searchRequest(term, contextualHashtagConfiguration, resultFunc) {
|
||||||
});
|
});
|
||||||
return currentSearch;
|
return currentSearch;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _findAndReplaceSeenHashtagPlaceholder(
|
|
||||||
slugRef,
|
|
||||||
contextualHashtagConfiguration,
|
|
||||||
hashtagSpan
|
|
||||||
) {
|
|
||||||
contextualHashtagConfiguration.forEach((type) => {
|
|
||||||
// Replace raw span for the hashtag with a cooked one
|
|
||||||
const matchingSeenHashtag = seenHashtags[type]?.[slugRef];
|
|
||||||
if (matchingSeenHashtag) {
|
|
||||||
generatePlaceholderHashtagHTML(type, hashtagSpan, matchingSeenHashtag);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
109
app/assets/javascripts/discourse/app/lib/hashtag-decorator.js
Normal file
109
app/assets/javascripts/discourse/app/lib/hashtag-decorator.js
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import { getHashtagTypeClasses } from "discourse/lib/hashtag-type-registry";
|
||||||
|
import { emojiUnescape } from "discourse/lib/text";
|
||||||
|
import domFromString from "discourse-common/lib/dom-from-string";
|
||||||
|
|
||||||
|
const checkedHashtags = new Set();
|
||||||
|
let seenHashtags = {};
|
||||||
|
|
||||||
|
// NOTE: For future maintainers, the hashtag lookup here does not take
|
||||||
|
// into account mixed contexts -- for instance, a chat quote inside a post
|
||||||
|
// or a post quote inside a chat message, so this may
|
||||||
|
// not provide an accurate priority lookup for hashtags without a ::type suffix in those
|
||||||
|
// cases.
|
||||||
|
export function fetchUnseenHashtagsInContext(
|
||||||
|
contextualHashtagConfiguration,
|
||||||
|
slugs
|
||||||
|
) {
|
||||||
|
return ajax("/hashtags", {
|
||||||
|
data: { slugs, order: contextualHashtagConfiguration },
|
||||||
|
}).then((response) => {
|
||||||
|
Object.keys(response).forEach((type) => {
|
||||||
|
seenHashtags[type] = seenHashtags[type] || {};
|
||||||
|
response[type].forEach((item) => {
|
||||||
|
seenHashtags[type][item.ref] = seenHashtags[type][item.ref] || item;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
slugs.forEach(checkedHashtags.add, checkedHashtags);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function linkSeenHashtagsInContext(
|
||||||
|
contextualHashtagConfiguration,
|
||||||
|
elem
|
||||||
|
) {
|
||||||
|
const hashtagSpans = [...(elem?.querySelectorAll("span.hashtag-raw") || [])];
|
||||||
|
if (hashtagSpans.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const slugs = [
|
||||||
|
...hashtagSpans.map((span) => span.innerText.replace("#", "")),
|
||||||
|
];
|
||||||
|
|
||||||
|
hashtagSpans.forEach((hashtagSpan, index) => {
|
||||||
|
_findAndReplaceSeenHashtagPlaceholder(
|
||||||
|
slugs[index],
|
||||||
|
contextualHashtagConfiguration,
|
||||||
|
hashtagSpan
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return slugs
|
||||||
|
.map((slug) => slug.toLowerCase())
|
||||||
|
.uniq()
|
||||||
|
.filter((slug) => !checkedHashtags.has(slug));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _findAndReplaceSeenHashtagPlaceholder(
|
||||||
|
slugRef,
|
||||||
|
contextualHashtagConfiguration,
|
||||||
|
hashtagSpan
|
||||||
|
) {
|
||||||
|
contextualHashtagConfiguration.forEach((type) => {
|
||||||
|
// Replace raw span for the hashtag with a cooked one
|
||||||
|
const matchingSeenHashtag = seenHashtags[type]?.[slugRef];
|
||||||
|
if (matchingSeenHashtag) {
|
||||||
|
generatePlaceholderHashtagHTML(type, hashtagSpan, matchingSeenHashtag);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generatePlaceholderHashtagHTML(type, spanEl, data) {
|
||||||
|
// NOTE: When changing the HTML structure here, you must also change
|
||||||
|
// it in the hashtag-autocomplete markdown rule, and vice-versa.
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.classList.add("hashtag-cooked");
|
||||||
|
link.href = data.relative_url;
|
||||||
|
link.dataset.type = type;
|
||||||
|
link.dataset.id = data.id;
|
||||||
|
link.dataset.slug = data.slug;
|
||||||
|
const hashtagTypeClass = new getHashtagTypeClasses()[type];
|
||||||
|
link.innerHTML = `${hashtagTypeClass.generateIconHTML(
|
||||||
|
data
|
||||||
|
)}<span>${emojiUnescape(data.text)}</span>`;
|
||||||
|
spanEl.replaceWith(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decorateHashtags(element, site) {
|
||||||
|
element.querySelectorAll(".hashtag-cooked").forEach((hashtagEl) => {
|
||||||
|
// Replace the empty icon placeholder span with actual icon HTML.
|
||||||
|
const iconPlaceholderEl = hashtagEl.querySelector(
|
||||||
|
".hashtag-icon-placeholder"
|
||||||
|
);
|
||||||
|
const hashtagType = hashtagEl.dataset.type;
|
||||||
|
const hashtagTypeClass = getHashtagTypeClasses()[hashtagType];
|
||||||
|
if (iconPlaceholderEl && hashtagTypeClass) {
|
||||||
|
const hashtagIconHTML = hashtagTypeClass
|
||||||
|
.generateIconHTML({
|
||||||
|
icon: site.hashtag_icons[hashtagType],
|
||||||
|
id: hashtagEl.dataset.id,
|
||||||
|
})
|
||||||
|
.trim();
|
||||||
|
iconPlaceholderEl.replaceWith(domFromString(hashtagIconHTML)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an aria-label to the hashtag element so that screen readers
|
||||||
|
// can read the hashtag text.
|
||||||
|
hashtagEl.setAttribute("aria-label", `${hashtagEl.innerText.trim()}`);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
let hashtagTypeClasses = {};
|
||||||
|
export function registerHashtagType(type, typeClassInstance) {
|
||||||
|
hashtagTypeClasses[type] = typeClassInstance;
|
||||||
|
}
|
||||||
|
export function cleanUpHashtagTypeClasses() {
|
||||||
|
hashtagTypeClasses = {};
|
||||||
|
}
|
||||||
|
export function getHashtagTypeClasses() {
|
||||||
|
return hashtagTypeClasses;
|
||||||
|
}
|
|
@ -1,66 +0,0 @@
|
||||||
// TODO (martin) Delete this after core PR and any other PRs that depend
|
|
||||||
// on this file (e.g. discourse-encrypt) are merged.
|
|
||||||
|
|
||||||
import $ from "jquery";
|
|
||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
import { replaceSpan } from "discourse/lib/category-hashtags";
|
|
||||||
import { TAG_HASHTAG_POSTFIX } from "discourse/lib/tag-hashtags";
|
|
||||||
import deprecated from "discourse-common/lib/deprecated";
|
|
||||||
|
|
||||||
const categoryHashtags = {};
|
|
||||||
const tagHashtags = {};
|
|
||||||
const checkedHashtags = new Set();
|
|
||||||
|
|
||||||
export function linkSeenHashtags(elem) {
|
|
||||||
if (elem instanceof $) {
|
|
||||||
elem = elem[0];
|
|
||||||
|
|
||||||
deprecated("linkSeenHashtags now expects a DOM node as first parameter", {
|
|
||||||
since: "2.8.0.beta7",
|
|
||||||
dropFrom: "2.9.0.beta1",
|
|
||||||
id: "discourse.link-hashtags.dom-node",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const hashtags = [...(elem?.querySelectorAll("span.hashtag") || [])];
|
|
||||||
if (hashtags.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const slugs = [...hashtags.map((hashtag) => hashtag.innerText.slice(1))];
|
|
||||||
|
|
||||||
hashtags.forEach((hashtag, index) => {
|
|
||||||
let slug = slugs[index];
|
|
||||||
const hasTagSuffix = slug.endsWith(TAG_HASHTAG_POSTFIX);
|
|
||||||
if (hasTagSuffix) {
|
|
||||||
slug = slug.slice(0, slug.length - TAG_HASHTAG_POSTFIX.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
const lowerSlug = slug.toLowerCase();
|
|
||||||
if (categoryHashtags[lowerSlug] && !hasTagSuffix) {
|
|
||||||
replaceSpan($(hashtag), slug, categoryHashtags[lowerSlug]);
|
|
||||||
} else if (tagHashtags[lowerSlug]) {
|
|
||||||
replaceSpan($(hashtag), slug, tagHashtags[lowerSlug]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return slugs
|
|
||||||
.map((slug) => slug.toLowerCase())
|
|
||||||
.uniq()
|
|
||||||
.filter((slug) => !checkedHashtags.has(slug));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchUnseenHashtags(slugs) {
|
|
||||||
return ajax("/hashtags", {
|
|
||||||
data: { slugs },
|
|
||||||
}).then((response) => {
|
|
||||||
Object.keys(response.categories).forEach((slug) => {
|
|
||||||
categoryHashtags[slug] = response.categories[slug];
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(response.tags).forEach((slug) => {
|
|
||||||
tagHashtags[slug] = response.tags[slug];
|
|
||||||
});
|
|
||||||
|
|
||||||
slugs.forEach(checkedHashtags.add, checkedHashtags);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -46,7 +46,7 @@ import { addBeforeAuthCompleteCallback } from "discourse/instance-initializers/a
|
||||||
import { addPopupMenuOption } from "discourse/lib/composer/custom-popup-menu-options";
|
import { addPopupMenuOption } from "discourse/lib/composer/custom-popup-menu-options";
|
||||||
import { registerDesktopNotificationHandler } from "discourse/lib/desktop-notifications";
|
import { registerDesktopNotificationHandler } from "discourse/lib/desktop-notifications";
|
||||||
import { downloadCalendar } from "discourse/lib/download-calendar";
|
import { downloadCalendar } from "discourse/lib/download-calendar";
|
||||||
import { registerHashtagType } from "discourse/lib/hashtag-autocomplete";
|
import { registerHashtagType } from "discourse/lib/hashtag-type-registry";
|
||||||
import {
|
import {
|
||||||
registerHighlightJSLanguage,
|
registerHighlightJSLanguage,
|
||||||
registerHighlightJSPlugin,
|
registerHighlightJSPlugin,
|
||||||
|
|
|
@ -31,7 +31,7 @@ import { resetUsernameDecorators } from "discourse/helpers/decorate-username-sel
|
||||||
import { resetBeforeAuthCompleteCallbacks } from "discourse/instance-initializers/auth-complete";
|
import { resetBeforeAuthCompleteCallbacks } from "discourse/instance-initializers/auth-complete";
|
||||||
import { clearPopupMenuOptions } from "discourse/lib/composer/custom-popup-menu-options";
|
import { clearPopupMenuOptions } from "discourse/lib/composer/custom-popup-menu-options";
|
||||||
import { clearDesktopNotificationHandlers } from "discourse/lib/desktop-notifications";
|
import { clearDesktopNotificationHandlers } from "discourse/lib/desktop-notifications";
|
||||||
import { cleanUpHashtagTypeClasses } from "discourse/lib/hashtag-autocomplete";
|
import { cleanUpHashtagTypeClasses } from "discourse/lib/hashtag-type-registry";
|
||||||
import {
|
import {
|
||||||
clearExtraKeyboardShortcutHelp,
|
clearExtraKeyboardShortcutHelp,
|
||||||
PLATFORM_KEY_MODIFIER,
|
PLATFORM_KEY_MODIFIER,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
import { spinnerHTML } from "discourse/helpers/loading-spinner";
|
import { spinnerHTML } from "discourse/helpers/loading-spinner";
|
||||||
import { decorateGithubOneboxBody } from "discourse/instance-initializers/onebox-decorators";
|
import { decorateGithubOneboxBody } from "discourse/instance-initializers/onebox-decorators";
|
||||||
import { decorateHashtags } from "discourse/lib/hashtag-autocomplete";
|
import { decorateHashtags } from "discourse/lib/hashtag-decorator";
|
||||||
import highlightSyntax from "discourse/lib/highlight-syntax";
|
import highlightSyntax from "discourse/lib/highlight-syntax";
|
||||||
import loadScript from "discourse/lib/load-script";
|
import loadScript from "discourse/lib/load-script";
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { generatePlaceholderHashtagHTML } from "discourse/lib/hashtag-autocomplete";
|
import { generatePlaceholderHashtagHTML } from "discourse/lib/hashtag-decorator";
|
||||||
import getURL from "discourse-common/lib/get-url";
|
import getURL from "discourse-common/lib/get-url";
|
||||||
|
|
||||||
const domParser = new DOMParser();
|
const domParser = new DOMParser();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user