From 77ca946745959f44f907d5b312b55344ca396042 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Fri, 5 Feb 2021 18:29:30 +0530 Subject: [PATCH] completed categories and date after filters --- .../realtime-validation-settings.js.es6 | 18 ++ .../realtime-validation-settings.hbs | 19 +- assets/javascripts/wizard-custom.js | 26 ++ .../components/suggested-validator.js.es6 | 47 ++- .../wizard/components/validator.js.es6 | 3 +- .../initializers/register-widgets.js.es6 | 276 ++++++++++++++++++ .../components/suggested-validator.hbs | 10 + .../templates/components/wizard-field.hbs | 2 +- config/locales/client.en.yml | 4 +- config/routes.rb | 2 +- ..._validation.rb => realtime_validations.rb} | 2 +- lib/custom_wizard/realtime_validation.rb | 8 +- plugin.rb | 2 +- 13 files changed, 402 insertions(+), 17 deletions(-) create mode 100644 assets/javascripts/wizard/initializers/register-widgets.js.es6 rename controllers/custom_wizard/{realtime_validation.rb => realtime_validations.rb} (78%) diff --git a/assets/javascripts/discourse/components/realtime-validation-settings.js.es6 b/assets/javascripts/discourse/components/realtime-validation-settings.js.es6 index 33796cf3..2fd306e6 100644 --- a/assets/javascripts/discourse/components/realtime-validation-settings.js.es6 +++ b/assets/javascripts/discourse/components/realtime-validation-settings.js.es6 @@ -1,4 +1,7 @@ import Component from "@ember/component"; +import EmberObject from "@ember/object"; +import { cloneJSON } from "discourse-common/lib/object"; +import Category from "discourse/models/category"; export default Component.extend({ init(){ @@ -13,5 +16,20 @@ export default Component.extend({ this.set('field.validations', EmberObject.create(validations)); } + + const validationBuffer = cloneJSON(this.get('field.validations')); + let bufferCategories; + if( validationBuffer.similar_topics && (bufferCategories = validationBuffer.similar_topics.categories)) { + const categories = Category.findByIds(bufferCategories); + validationBuffer.similar_topics.categories = categories; + } + this.set('validationBuffer', validationBuffer); + }, + + actions: { + updateValidationCategories(name, validation, categories) { + this.set(`validationBuffer.${name}.categories`, categories); + this.set(`field.validations.${name}.categories`, categories.map(category => category.id)); + } } }); diff --git a/assets/javascripts/discourse/templates/components/realtime-validation-settings.hbs b/assets/javascripts/discourse/templates/components/realtime-validation-settings.hbs index 487c4de6..c8d35aa4 100644 --- a/assets/javascripts/discourse/templates/components/realtime-validation-settings.hbs +++ b/assets/javascripts/discourse/templates/components/realtime-validation-settings.hbs @@ -4,8 +4,25 @@ {{#each-in field.validations as |name props|}} {{input type="checkbox" checked=props.status }} - {{i18n (concat 'admin.wizard.field.validations.' name)}} + {{i18n (concat 'admin.wizard.field.validations.' name) }} +
+ {{i18n 'admin.wizard.field.validations.categories'}} +
+ {{category-selector + categories=(get this (concat 'validationBuffer.' name '.categories')) + onChange=(action 'updateValidationCategories' name props) }} +
+
+
+ {{i18n 'admin.wizard.field.validations.date_after'}} +
+ {{date-picker-past + value=(readonly props.date_after) + containerId="date-container" + onSelect=(action (mut props.date_after))}} +
+
{{radio-button name=(concat name field.id) value="above" selection=props.position}} {{i18n 'admin.wizard.field.validations.above'}}
diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index 5d18328f..dd6bbf0c 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -144,3 +144,29 @@ //= require_tree ./wizard/models //= require_tree ./wizard/routes //= require_tree ./wizard/templates + +//= require discourse/app/components/mount-widget +//= require discourse/app/widgets/widget +//= require discourse/app/widgets/hooks +//= require discourse/app/widgets/decorator-helper +//= require discourse/app/widgets/connector +//= require discourse/app/widgets/post-cooked +//= require discourse/app/lib/highlight-html +//= require discourse/app/lib/highlight-search +//= require discourse/app/lib/constants +//= require discourse/app/lib/click-track +//= require discourse/app/helpers/loading-spinner +//= require discourse/app/widgets/raw-html +//= require discourse/app/lib/dirty-keys + +//= require discourse/app/widgets/search-menu +//= require discourse/app/widgets/search-menu-results +//= require discourse/app/widgets/post +//= require discourse/app/helpers/node +//= require discourse/app/widgets/post-stream + +//= require discourse/app/lib/posts-with-placeholders +//= require discourse/app/lib/transform-post +//= require discourse/app/helpers/category-link +//= require discourse/app/lib/render-tags +//= require discourse/app/helpers/topic-status-icons \ No newline at end of file diff --git a/assets/javascripts/wizard/components/suggested-validator.js.es6 b/assets/javascripts/wizard/components/suggested-validator.js.es6 index 953acfa1..599723e3 100644 --- a/assets/javascripts/wizard/components/suggested-validator.js.es6 +++ b/assets/javascripts/wizard/components/suggested-validator.js.es6 @@ -1,20 +1,53 @@ import WizardFieldValidator from "../../wizard/components/validator"; -import { ajax } from "discourse/lib/ajax"; -import { getToken } from "wizard/lib/ajax"; -import { getOwner } from "discourse-common/lib/get-owner"; -import discourseComputed from "discourse-common/utils/decorators"; +import { deepMerge } from "discourse-common/lib/object"; +import { observes } from "discourse-common/utils/decorators"; +import { cancel, later } from "@ember/runloop"; +import { A } from '@ember/array'; +import EmberObject from "@ember/object"; export default WizardFieldValidator.extend({ validMessageKey: 'hello', invalidMessageKey: 'world', + similarTopics: [], + validate() { - this.backendValidate({title: this.get("field.value")}).then(response => { - console.log(response) - }) + }, + @observes("field.value") + customValidate(){ + const lastKeyUp = new Date(); + this._lastKeyUp = lastKeyUp; + + // One second from now, check to see if the last key was hit when + // we recorded it. If it was, the user paused typing. + cancel(this._lastKeyTimeout); + this._lastKeyTimeout = later(() => { + if (lastKeyUp !== this._lastKeyUp) { + return; + } + + this.updateSimilarTopics(); + }, 1000); }, + updateSimilarTopics(){ + this.backendValidate({ + title: this.get("field.value"), + categories: this.get('validation.categories'), + date_after: this.get('validation.date_after') + }).then((result) => { + const similarTopics = A(deepMerge(result['topics'], result['similar_topics'])); + similarTopics.forEach(function(topic, index) { + similarTopics[index] = EmberObject.create(topic); + }); + + this.set('similarTopics', similarTopics); + }); + }, init() { this._super(...arguments); + }, + actions: { + closeMessage(){} } }); \ No newline at end of file diff --git a/assets/javascripts/wizard/components/validator.js.es6 b/assets/javascripts/wizard/components/validator.js.es6 index f46b8319..2e5c47e7 100644 --- a/assets/javascripts/wizard/components/validator.js.es6 +++ b/assets/javascripts/wizard/components/validator.js.es6 @@ -17,8 +17,7 @@ export default Component.extend({ // set a function that can be called as often as it need to // from the derived component this.backendValidate = (params) => { - return ajax('/w/realtime_validation', { - type: 'put', + return ajax('/realtime_validations', { data: { validation: this.get('name'), authenticity_token: getToken(), diff --git a/assets/javascripts/wizard/initializers/register-widgets.js.es6 b/assets/javascripts/wizard/initializers/register-widgets.js.es6 new file mode 100644 index 00000000..7040d150 --- /dev/null +++ b/assets/javascripts/wizard/initializers/register-widgets.js.es6 @@ -0,0 +1,276 @@ +import { escapeExpression, formatUsername } from "discourse/lib/utilities"; +import I18n from "I18n"; +import RawHtml from "discourse/widgets/raw-html"; +import { avatarImg } from "discourse/widgets/post"; +import { createWidget } from "discourse/widgets/widget"; +import { dateNode } from "discourse/helpers/node"; +import { emojiUnescape } from "discourse/lib/text"; +import { h } from "virtual-dom"; +import highlightSearch from "discourse/lib/highlight-search"; +import { iconNode } from "discourse-common/lib/icon-library"; +import renderTag from "discourse/lib/render-tag"; +import DiscourseURL from "discourse/lib/url"; +import getURL from "discourse-common/lib/get-url"; +import { wantsNewWindow } from "discourse/lib/intercept-click"; +import { htmlSafe } from "@ember/template"; +import { registerUnbound } from "discourse-common/lib/helpers"; +import renderTags from "discourse/lib/render-tags"; +import TopicStatusIcons from "discourse/helpers/topic-status-icons"; + +class Highlighted extends RawHtml { + constructor(html, term) { + super({ html: `${html}` }); + this.term = term; + } + + decorate($html) { + highlightSearch($html[0], this.term); + } +} + + + +export default { + name: "wizard-register-widgets", + after: "custom-routes", + initialize(app) { + if (window.location.pathname.indexOf("/w/") < 0) return; + + createWidget("link", { + tagName: "a", + + href(attrs) { + const route = attrs.route; + if (route) { + const router = this.register.lookup("router:main"); + if (router && router._routerMicrolib) { + const params = [route]; + if (attrs.model) { + params.push(attrs.model); + } + return getURL( + router._routerMicrolib.generate.apply(router._routerMicrolib, params) + ); + } + } else { + return getURL(attrs.href); + } + }, + + buildClasses(attrs) { + const result = []; + result.push("widget-link"); + if (attrs.className) { + result.push(attrs.className); + } + return result; + }, + + buildAttributes(attrs) { + const ret = { + href: this.href(attrs), + title: attrs.title + ? I18n.t(attrs.title, attrs.titleOptions) + : this.label(attrs), + }; + if (attrs.attributes) { + Object.keys(attrs.attributes).forEach( + (k) => (ret[k] = attrs.attributes[k]) + ); + } + return ret; + }, + + label(attrs) { + if (attrs.labelCount && attrs.count) { + return I18n.t(attrs.labelCount, { count: attrs.count }); + } + return attrs.rawLabel || (attrs.label ? I18n.t(attrs.label) : ""); + }, + + html(attrs) { + if (attrs.contents) { + return attrs.contents(); + } + + const result = []; + if (attrs.icon) { + if (attrs.alt) { + let icon = iconNode(attrs.icon); + icon.properties.attributes["alt"] = I18n.t(attrs.alt); + icon.properties.attributes["aria-hidden"] = false; + result.push(icon); + } else { + result.push(iconNode(attrs.icon)); + } + result.push(" "); + } + + if (!attrs.hideLabel) { + let label = this.label(attrs); + + if (attrs.omitSpan) { + result.push(label); + } else { + result.push(h("span.d-label", label)); + } + } + + const currentUser = this.currentUser; + if (currentUser && attrs.badgeCount) { + const val = parseInt(currentUser.get(attrs.badgeCount), 10); + if (val > 0) { + const title = attrs.badgeTitle ? I18n.t(attrs.badgeTitle) : ""; + result.push(" "); + result.push( + h( + "span.badge-notification", + { + className: attrs.badgeClass, + attributes: { title }, + }, + val + ) + ); + } + } + return result; + }, + + click(e) { + if (this.attrs.attributes && this.attrs.attributes.target === "_blank") { + return; + } + + if (wantsNewWindow(e)) { + return; + } + + e.preventDefault(); + + if (this.attrs.action) { + e.preventDefault(); + return this.sendWidgetAction(this.attrs.action, this.attrs.actionParam); + } else { + this.sendWidgetEvent("linkClicked", this.attrs); + } + + return DiscourseURL.routeToTag($(e.target).closest("a")[0]); + }, + }); + createWidget("topic-status", { + tagName: "div.topic-statuses", + + html(attrs) { + const topic = attrs.topic; + const canAct = this.currentUser && !attrs.disableActions; + + const result = []; + TopicStatusIcons.render(topic, function (name, key) { + const iconArgs = key === "unpinned" ? { class: "unpinned" } : null; + const icon = iconNode(name, iconArgs); + + const attributes = { + title: escapeExpression(I18n.t(`topic_statuses.${key}.help`)), + }; + result.push(h(`${canAct ? "a" : "span"}.topic-status`, attributes, icon)); + }); + + return result; + }, + }); + + createSearchResult({ + type: "topic", + linkField: "url", + builder(result, term) { + const topic = result; + + const firstLine = [ + this.attach("topic-status", { topic, disableActions: true }), + h( + "span.topic-title", + { attributes: { "data-topic-id": topic.id } }, + this.siteSettings.use_pg_headlines_for_excerpt && + result.topic_title_headline + ? new RawHtml({ + html: `${emojiUnescape( + result.topic_title_headline + )}`, + }) + : new Highlighted(topic.fancy_title, term) + ), + ]; + + const secondLine = [ + // this.attach("category-link", { + // category: topic.category, + // link: false, + // }), + ]; + // if (this.siteSettings.tagging_enabled) { + // secondLine.push( + // this.attach("discourse-tags", { topic, tagName: "span" }) + // ); + // } + + const link = h("span.topic", [ + h("div.first-line", firstLine), + h("div.second-line", secondLine), + ]); + + return postResult.call(this, result, link, term); + }, + }); + + } +} + +function createSearchResult({ type, linkField, builder }) { + return createWidget(`search-result-${type}`, { + tagName: "ul.list", + + html(attrs) { + return attrs.results.map((r) => { + let searchResultId; + + if (type === "topic") { + searchResultId = r.topic_id; + } else { + searchResultId = r.id; + } + + return h( + "li.item", + this.attach("link", { + href: r[linkField], + contents: () => builder.call(this, r, attrs.term), + className: "search-link", + searchResultId, + searchResultType: type, + searchContextEnabled: attrs.searchContextEnabled, + searchLogId: attrs.searchLogId, + }) + ); + }); + }, + }); +} + +function postResult(result, link, term) { + const html = [link]; + + if (!this.site.mobileView) { + html.push( + h("span.blurb", [ + dateNode(result.created_at), + h("span", " - "), + this.siteSettings.use_pg_headlines_for_excerpt + ? new RawHtml({ html: `${result.blurb}` }) + : new Highlighted(result.blurb, term), + ]) + ); + } + + return html; +} \ No newline at end of file diff --git a/assets/javascripts/wizard/templates/components/suggested-validator.hbs b/assets/javascripts/wizard/templates/components/suggested-validator.hbs index e69de29b..87ddd03f 100644 --- a/assets/javascripts/wizard/templates/components/suggested-validator.hbs +++ b/assets/javascripts/wizard/templates/components/suggested-validator.hbs @@ -0,0 +1,10 @@ +{{#if similarTopics}} + {{d-icon "times"}} +

{{i18n "composer.similar_topics"}}

+ + + +{{/if}} + diff --git a/assets/javascripts/wizard/templates/components/wizard-field.hbs b/assets/javascripts/wizard/templates/components/wizard-field.hbs index 8f342e54..b0563384 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field.hbs @@ -13,7 +13,7 @@ {{#field-validators field=field as |validators|}} {{#if inputComponentName}}
- {{component inputComponentName field=field step=step fieldClass=fieldClass wizard=wizard focusOut=validators.perform}} + {{component inputComponentName field=field step=step fieldClass=fieldClass wizard=wizard }}
{{/if}} {{/field-validators}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index bb1d03c0..555f8a94 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -178,9 +178,11 @@ en: instructions: "Moment.js format" validations: header: "Realtime Validation Settings" - suggested_topics: "Suggested Topics" + similar_topics: "Similar Topics" above: "Above" below: "Below" + categories: "Categories" + date_after: "Date After" type: text: "Text" diff --git a/config/routes.rb b/config/routes.rb index 7f30e311..3421d55a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,12 +4,12 @@ CustomWizard::Engine.routes.draw do get ':wizard_id/steps' => 'wizard#index' get ':wizard_id/steps/:step_id' => 'wizard#index' put ':wizard_id/steps/:step_id' => 'steps#update' - put 'realtime_validation' => 'realtime_validation#validate' end Discourse::Application.routes.append do mount ::CustomWizard::Engine, at: 'w' post 'wizard/authorization/callback' => "custom_wizard/authorization#callback" + get 'realtime_validations' => 'custom_wizard/realtime_validations#validate' scope module: 'custom_wizard', constraints: AdminConstraint.new do get 'admin/wizards' => 'admin#index' diff --git a/controllers/custom_wizard/realtime_validation.rb b/controllers/custom_wizard/realtime_validations.rb similarity index 78% rename from controllers/custom_wizard/realtime_validation.rb rename to controllers/custom_wizard/realtime_validations.rb index a2e0dac1..38ae1a0d 100644 --- a/controllers/custom_wizard/realtime_validation.rb +++ b/controllers/custom_wizard/realtime_validations.rb @@ -1,4 +1,4 @@ -class CustomWizard::RealtimeValidationController < ::ApplicationController +class CustomWizard::RealtimeValidationsController < ::ApplicationController def validate params.require(:validation) params.require(::CustomWizard::RealtimeValidation.types[params[:validation].to_sym][:required_params]) diff --git a/lib/custom_wizard/realtime_validation.rb b/lib/custom_wizard/realtime_validation.rb index f015d3fb..b5df6a9e 100644 --- a/lib/custom_wizard/realtime_validation.rb +++ b/lib/custom_wizard/realtime_validation.rb @@ -1,7 +1,7 @@ class CustomWizard::RealtimeValidation cattr_accessor :types @@types ||= { - suggested_topics: { types: [:text], component: "suggested-validator", backend: true, required_params: [] } + similar_topics: { types: [:text], component: "suggested-validator", backend: true, required_params: [] } } class SimilarTopic @@ -16,15 +16,19 @@ class CustomWizard::RealtimeValidation end end - def self.suggested_topics(params, current_user) + def self.similar_topics(params, current_user) title = params[:title] raw = params[:raw] + categories = params[:categories] + date_after = params[:date_after] if title.length < SiteSetting.min_title_similar_length || !Topic.count_exceeds_minimum? return [] end topics = Topic.similar_to(title, raw, current_user).to_a + topics.select! { |t| categories.include?(t.category.id.to_s) } if categories.present? + topics.select! { |t| t.created_at > DateTime.parse(date_after) } if date_after.present? topics.map! { |t| SimilarTopic.new(t) } ::ActiveModel::ArraySerializer.new(topics, each_serializer: SimilarTopicSerializer, root: :similar_topics, rest_serializer: true, scope: ::Guardian.new(current_user)) end diff --git a/plugin.rb b/plugin.rb index 83699751..46cd4e78 100644 --- a/plugin.rb +++ b/plugin.rb @@ -47,7 +47,7 @@ after_initialize do ../controllers/custom_wizard/admin/custom_fields.rb ../controllers/custom_wizard/wizard.rb ../controllers/custom_wizard/steps.rb - ../controllers/custom_wizard/realtime_validation.rb + ../controllers/custom_wizard/realtime_validations.rb ../jobs/clear_after_time_wizard.rb ../jobs/refresh_api_access_token.rb ../jobs/set_after_time_wizard.rb