FEATURE: Merge discourse-automation (#26432)
Automation (previously known as discourse-automation) is now a core plugin.
This commit is contained in:
parent
2190c9b957
commit
3d4faf3272
|
@ -40,6 +40,8 @@
|
|||
!/plugins/discourse-narrative-bot
|
||||
!/plugins/discourse-presence
|
||||
!/plugins/discourse-lazy-videos/
|
||||
!/plugins/automation/
|
||||
/plugins/automation/gems
|
||||
!/plugins/chat/
|
||||
!/plugins/poll/
|
||||
!/plugins/styleguide
|
||||
|
|
|
@ -19,9 +19,11 @@ end
|
|||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_post_custom_fields_on_name_and_value (name, "left"(value, 200))
|
||||
# index_post_custom_fields_on_notice (post_id) UNIQUE WHERE ((name)::text = 'notice'::text)
|
||||
# index_post_custom_fields_on_post_id (post_id) UNIQUE WHERE ((name)::text = 'missing uploads'::text)
|
||||
# index_post_custom_fields_on_post_id_and_name (post_id,name)
|
||||
# index_post_id_where_missing_uploads_ignored (post_id) UNIQUE WHERE ((name)::text = 'missing uploads ignored'::text)
|
||||
# idx_post_custom_fields_discourse_automation_unique_id_partial (post_id,value) UNIQUE WHERE ((name)::text = 'discourse_automation_ids'::text)
|
||||
# index_post_custom_fields_on_name_and_value (name, "left"(value, 200))
|
||||
# index_post_custom_fields_on_notice (post_id) UNIQUE WHERE ((name)::text = 'notice'::text)
|
||||
# index_post_custom_fields_on_post_id (post_id) UNIQUE WHERE ((name)::text = 'missing uploads'::text)
|
||||
# index_post_custom_fields_on_post_id_and_name (post_id,name)
|
||||
# index_post_custom_fields_on_stalled_wiki_triggered_at (post_id) UNIQUE WHERE ((name)::text = 'stalled_wiki_triggered_at'::text)
|
||||
# index_post_id_where_missing_uploads_ignored (post_id) UNIQUE WHERE ((name)::text = 'missing uploads ignored'::text)
|
||||
#
|
||||
|
|
|
@ -19,6 +19,8 @@ end
|
|||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_topic_custom_fields_on_topic_id_and_name (topic_id,name)
|
||||
# topic_custom_fields_value_key_idx (value,name) WHERE ((value IS NOT NULL) AND (char_length(value) < 400))
|
||||
# idx_topic_custom_fields_auto_responder_triggered_ids_partial (topic_id,value) UNIQUE WHERE ((name)::text = 'auto_responder_triggered_ids'::text)
|
||||
# idx_topic_custom_fields_discourse_automation_unique_id_partial (topic_id,value) UNIQUE WHERE ((name)::text = 'discourse_automation_ids'::text)
|
||||
# index_topic_custom_fields_on_topic_id_and_name (topic_id,name)
|
||||
# topic_custom_fields_value_key_idx (value,name) WHERE ((value IS NOT NULL) AND (char_length(value) < 400))
|
||||
#
|
||||
|
|
|
@ -26,5 +26,6 @@ end
|
|||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_user_custom_fields_on_user_id_and_name (user_id,name)
|
||||
# idx_user_custom_fields_discourse_automation_unique_id_partial (user_id,value) UNIQUE WHERE ((name)::text = 'discourse_automation_ids'::text)
|
||||
# index_user_custom_fields_on_user_id_and_name (user_id,name)
|
||||
#
|
||||
|
|
|
@ -16,7 +16,6 @@ class Plugin::Metadata
|
|||
discourse-apple-auth
|
||||
discourse-assign
|
||||
discourse-auto-deactivate
|
||||
discourse-automation
|
||||
discourse-bbcode
|
||||
discourse-bbcode-color
|
||||
discourse-bcc
|
||||
|
@ -91,6 +90,7 @@ class Plugin::Metadata
|
|||
discourse-yearly-review
|
||||
discourse-zendesk-plugin
|
||||
discourse-zoom
|
||||
automation
|
||||
docker_manager
|
||||
chat
|
||||
poll
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class AdminAutomationsController < ::Admin::AdminController
|
||||
requires_plugin DiscourseAutomation::PLUGIN_NAME
|
||||
|
||||
def index
|
||||
automations = DiscourseAutomation::Automation.order(:name).all
|
||||
serializer =
|
||||
ActiveModel::ArraySerializer.new(
|
||||
automations,
|
||||
each_serializer: DiscourseAutomation::AutomationSerializer,
|
||||
root: "automations",
|
||||
).as_json
|
||||
render_json_dump(serializer)
|
||||
end
|
||||
|
||||
def show
|
||||
automation = DiscourseAutomation::Automation.find(params[:id])
|
||||
render_serialized_automation(automation)
|
||||
end
|
||||
|
||||
def create
|
||||
automation_params = params.require(:automation).permit(:name, :script, :trigger)
|
||||
|
||||
automation =
|
||||
DiscourseAutomation::Automation.new(
|
||||
automation_params.merge(last_updated_by_id: current_user.id),
|
||||
)
|
||||
if automation.scriptable.forced_triggerable
|
||||
automation.trigger = scriptable.forced_triggerable[:triggerable].to_s
|
||||
end
|
||||
|
||||
automation.save!
|
||||
|
||||
render_serialized_automation(automation)
|
||||
end
|
||||
|
||||
def update
|
||||
params.require(:automation)
|
||||
|
||||
automation = DiscourseAutomation::Automation.find(params[:id])
|
||||
if automation.scriptable.forced_triggerable
|
||||
params[:trigger] = automation.scriptable.forced_triggerable[:triggerable].to_s
|
||||
end
|
||||
|
||||
attributes =
|
||||
request.parameters[:automation].slice(:name, :id, :script, :trigger, :enabled).merge(
|
||||
last_updated_by_id: current_user.id,
|
||||
)
|
||||
|
||||
if automation.trigger != params[:automation][:trigger]
|
||||
params[:automation][:fields] = []
|
||||
attributes[:enabled] = false
|
||||
automation.fields.destroy_all
|
||||
end
|
||||
|
||||
if automation.script != params[:automation][:script]
|
||||
attributes[:trigger] = nil
|
||||
params[:automation][:fields] = []
|
||||
attributes[:enabled] = false
|
||||
automation.fields.destroy_all
|
||||
automation.tap { |r| r.assign_attributes(attributes) }.save!(validate: false)
|
||||
else
|
||||
Array(params[:automation][:fields])
|
||||
.reject(&:empty?)
|
||||
.each do |field|
|
||||
automation.upsert_field!(
|
||||
field[:name],
|
||||
field[:component],
|
||||
field[:metadata],
|
||||
target: field[:target],
|
||||
)
|
||||
end
|
||||
|
||||
automation.tap { |r| r.assign_attributes(attributes) }.save!
|
||||
end
|
||||
|
||||
render_serialized_automation(automation)
|
||||
end
|
||||
|
||||
def destroy
|
||||
automation = DiscourseAutomation::Automation.find(params[:id])
|
||||
automation.destroy!
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_serialized_automation(automation)
|
||||
serializer =
|
||||
DiscourseAutomation::AutomationSerializer.new(automation, root: "automation").as_json
|
||||
render_json_dump(serializer)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class AdminController < ::Admin::AdminController
|
||||
requires_plugin DiscourseAutomation::PLUGIN_NAME
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class AdminScriptablesController < ::Admin::AdminController
|
||||
requires_plugin DiscourseAutomation::PLUGIN_NAME
|
||||
|
||||
def index
|
||||
scriptables =
|
||||
DiscourseAutomation::Scriptable.all.map do |s|
|
||||
id = s.to_s.gsub(/^__scriptable_/, "")
|
||||
{
|
||||
id: id,
|
||||
name: I18n.t("discourse_automation.scriptables.#{id}.title"),
|
||||
description: I18n.t("discourse_automation.scriptables.#{id}.description", default: ""),
|
||||
doc: I18n.t("discourse_automation.scriptables.#{id}.doc", default: ""),
|
||||
}
|
||||
end
|
||||
|
||||
scriptables.sort_by! { |s| s[:name] }
|
||||
render_json_dump(scriptables: scriptables)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class AdminTriggerablesController < ::Admin::AdminController
|
||||
requires_plugin DiscourseAutomation::PLUGIN_NAME
|
||||
|
||||
def index
|
||||
if params[:automation_id].present?
|
||||
automation = DiscourseAutomation::Automation.find(params[:automation_id])
|
||||
scriptable = automation.scriptable
|
||||
triggerables = scriptable.triggerables
|
||||
else
|
||||
triggerables = DiscourseAutomation::Triggerable.all
|
||||
end
|
||||
|
||||
triggerables =
|
||||
triggerables.map do |s|
|
||||
id = s.to_s.gsub(/^__triggerable_/, "")
|
||||
{
|
||||
id: id,
|
||||
name: I18n.t("discourse_automation.triggerables.#{id}.title"),
|
||||
description: I18n.t("discourse_automation.triggerables.#{id}.description", default: ""),
|
||||
doc: I18n.t("discourse_automation.triggerables.#{id}.doc", default: ""),
|
||||
}
|
||||
end
|
||||
|
||||
render_json_dump(triggerables: triggerables)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class AppendLastCheckedByController < ApplicationController
|
||||
requires_plugin DiscourseAutomation::PLUGIN_NAME
|
||||
requires_login
|
||||
|
||||
def post_checked
|
||||
post = Post.find(params[:post_id])
|
||||
guardian.ensure_can_edit!(post)
|
||||
|
||||
topic = post.topic
|
||||
raise Discourse::NotFound if topic.blank?
|
||||
|
||||
topic.custom_fields[DiscourseAutomation::TOPIC_LAST_CHECKED_BY] = current_user.username
|
||||
topic.custom_fields[DiscourseAutomation::TOPIC_LAST_CHECKED_AT] = Time.zone.now.to_s
|
||||
topic.save_custom_fields
|
||||
|
||||
post.rebake!
|
||||
|
||||
render json: success_json
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class AutomationsController < ApplicationController
|
||||
requires_plugin DiscourseAutomation::PLUGIN_NAME
|
||||
before_action :ensure_admin
|
||||
|
||||
def trigger
|
||||
automation = DiscourseAutomation::Automation.find(params[:id])
|
||||
automation.trigger_in_background!(params.merge(kind: DiscourseAutomation::Triggers::API_CALL))
|
||||
render json: success_json
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class UserGlobalNoticesController < ApplicationController
|
||||
requires_plugin DiscourseAutomation::PLUGIN_NAME
|
||||
requires_login
|
||||
|
||||
def destroy
|
||||
notice =
|
||||
DiscourseAutomation::UserGlobalNotice.find_by(user_id: current_user.id, id: params[:id])
|
||||
|
||||
raise Discourse::NotFound unless notice
|
||||
|
||||
notice.destroy!
|
||||
|
||||
head :no_content
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class DiscourseAutomationCallZapierWebhook < ::Jobs::Base
|
||||
def execute(args)
|
||||
RateLimiter.new(nil, "discourse_automation_call_zapier", 5, 30).performed!
|
||||
|
||||
result =
|
||||
Excon.post(
|
||||
args["webhook_url"],
|
||||
body: args["context"].to_json,
|
||||
headers: {
|
||||
"Content-Type" => "application/json",
|
||||
"Accept" => "application/json",
|
||||
},
|
||||
)
|
||||
|
||||
if result.status != 200
|
||||
Rails.logger.warn(
|
||||
"Failed to call Zapier webhook at #{args["webhook_url"]} Status: #{result.status}: #{result.status_line}",
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class DiscourseAutomationTrigger < ::Jobs::Base
|
||||
RETRY_TIMES = [5.minute, 15.minute, 120.minute]
|
||||
|
||||
sidekiq_options retry: RETRY_TIMES.size
|
||||
|
||||
sidekiq_retry_in do |count, exception|
|
||||
# returning nil/0 will trigger the default sidekiq
|
||||
# retry formula
|
||||
#
|
||||
# See https://github.com/mperham/sidekiq/blob/3330df0ee37cfd3e0cd3ef01e3e66b584b99d488/lib/sidekiq/job_retry.rb#L216-L234
|
||||
case exception.wrapped
|
||||
when SocketError
|
||||
return RETRY_TIMES[count]
|
||||
end
|
||||
end
|
||||
|
||||
def execute(args)
|
||||
automation = DiscourseAutomation::Automation.find_by(id: args[:automation_id], enabled: true)
|
||||
|
||||
return if !automation
|
||||
|
||||
context = DiscourseAutomation::Automation.deserialize_context(args[:context])
|
||||
|
||||
automation.running_in_background!
|
||||
automation.trigger!(context)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class DiscourseAutomationTracker < ::Jobs::Scheduled
|
||||
every 1.minute
|
||||
|
||||
BATCH_LIMIT ||= 300
|
||||
|
||||
def execute(_args = nil)
|
||||
return unless SiteSetting.discourse_automation_enabled
|
||||
|
||||
DiscourseAutomation::PendingAutomation
|
||||
.includes(:automation)
|
||||
.limit(BATCH_LIMIT)
|
||||
.where("execute_at < ?", Time.now)
|
||||
.find_each { |pending_automation| run_pending_automation(pending_automation) }
|
||||
|
||||
DiscourseAutomation::PendingPm
|
||||
.includes(:automation)
|
||||
.limit(BATCH_LIMIT)
|
||||
.where("execute_at < ?", Time.now)
|
||||
.find_each { |pending_pm| send_pending_pm(pending_pm) }
|
||||
end
|
||||
|
||||
def send_pending_pm(pending_pm)
|
||||
DiscourseAutomation::Scriptable::Utils.send_pm(
|
||||
pending_pm.attributes.slice("target_usernames", "title", "raw"),
|
||||
sender: pending_pm.sender,
|
||||
prefers_encrypt: pending_pm.prefers_encrypt,
|
||||
)
|
||||
|
||||
pending_pm.destroy!
|
||||
end
|
||||
|
||||
def run_pending_automation(pending_automation)
|
||||
pending_automation.automation.trigger!(
|
||||
"kind" => pending_automation.automation.trigger,
|
||||
"execute_at" => pending_automation.execute_at,
|
||||
)
|
||||
|
||||
pending_automation.destroy!
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class StalledTopicTracker < ::Jobs::Scheduled
|
||||
every 1.hour
|
||||
|
||||
def execute(_args = nil)
|
||||
name = DiscourseAutomation::Triggers::STALLED_TOPIC
|
||||
|
||||
DiscourseAutomation::Automation
|
||||
.where(trigger: name, enabled: true)
|
||||
.find_each do |automation|
|
||||
fields = automation.serialized_fields
|
||||
stalled_after = fields.dig("stalled_after", "value")
|
||||
stalled_duration = ISO8601::Duration.new(stalled_after).to_seconds
|
||||
stalled_date = stalled_duration.seconds.ago
|
||||
categories = fields.dig("categories", "value")
|
||||
tags = fields.dig("tags", "value")
|
||||
|
||||
StalledTopicFinder
|
||||
.call(stalled_date, categories: categories, tags: tags)
|
||||
.each do |result|
|
||||
topic = Topic.find_by(id: result.id)
|
||||
next unless topic
|
||||
|
||||
run_trigger(automation, topic)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run_trigger(automation, topic)
|
||||
automation.trigger!(
|
||||
"kind" => DiscourseAutomation::Triggers::STALLED_TOPIC,
|
||||
"topic" => topic,
|
||||
"placeholders" => {
|
||||
"topic_url" => topic.url,
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class StalledWikiTracker < ::Jobs::Scheduled
|
||||
every 10.minutes
|
||||
|
||||
def execute(_args = nil)
|
||||
name = DiscourseAutomation::Triggers::STALLED_WIKI
|
||||
|
||||
DiscourseAutomation::Automation
|
||||
.where(trigger: name, enabled: true)
|
||||
.find_each do |automation|
|
||||
stalled_after = automation.trigger_field("stalled_after")
|
||||
stalled_duration = ISO8601::Duration.new(stalled_after["value"]).to_seconds
|
||||
finder = Post.where("wiki = TRUE AND last_version_at <= ?", stalled_duration.seconds.ago)
|
||||
|
||||
restricted_category = automation.trigger_field("restricted_category")
|
||||
if restricted_category["value"]
|
||||
finder =
|
||||
finder.joins(:topic).where("topics.category_id = ?", restricted_category["value"])
|
||||
end
|
||||
|
||||
finder.each do |post|
|
||||
last_trigger_date = post.custom_fields["stalled_wiki_triggered_at"]
|
||||
if last_trigger_date
|
||||
retriggered_after = automation.trigger_field("retriggered_after")
|
||||
retrigger_duration = ISO8601::Duration.new(retriggered_after["value"]).to_seconds
|
||||
|
||||
next if Time.parse(last_trigger_date) + retrigger_duration >= Time.zone.now
|
||||
end
|
||||
|
||||
post.upsert_custom_fields(stalled_wiki_triggered_at: Time.zone.now)
|
||||
run_trigger(automation, post)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run_trigger(automation, post)
|
||||
user_ids =
|
||||
(
|
||||
post.post_revisions.order("post_revisions.created_at DESC").limit(5).pluck(:user_id) +
|
||||
[post.user_id]
|
||||
).compact.uniq
|
||||
|
||||
automation.trigger!(
|
||||
"kind" => DiscourseAutomation::Triggers::STALLED_WIKI,
|
||||
"post" => post,
|
||||
"topic" => post.topic,
|
||||
"usernames" => User.where(id: user_ids).pluck(:username),
|
||||
"placeholders" => {
|
||||
"wiki_url" => Discourse.base_url + post.url,
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,172 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class Automation < ActiveRecord::Base
|
||||
self.table_name = "discourse_automation_automations"
|
||||
|
||||
has_many :fields,
|
||||
class_name: "DiscourseAutomation::Field",
|
||||
dependent: :delete_all,
|
||||
foreign_key: "automation_id"
|
||||
has_many :pending_automations,
|
||||
class_name: "DiscourseAutomation::PendingAutomation",
|
||||
dependent: :delete_all,
|
||||
foreign_key: "automation_id"
|
||||
has_many :pending_pms,
|
||||
class_name: "DiscourseAutomation::PendingPm",
|
||||
dependent: :delete_all,
|
||||
foreign_key: "automation_id"
|
||||
|
||||
validates :script, presence: true
|
||||
validate :validate_trigger_fields
|
||||
|
||||
attr_accessor :running_in_background
|
||||
|
||||
def running_in_background!
|
||||
@running_in_background = true
|
||||
end
|
||||
|
||||
MIN_NAME_LENGTH = 5
|
||||
MAX_NAME_LENGTH = 30
|
||||
validates :name, length: { in: MIN_NAME_LENGTH..MAX_NAME_LENGTH }
|
||||
|
||||
def attach_custom_field(target)
|
||||
if ![Topic, Post, User].any? { |m| target.is_a?(m) }
|
||||
raise "Expected an instance of Topic/Post/User."
|
||||
end
|
||||
|
||||
now = Time.now
|
||||
fk = target.custom_fields_fk
|
||||
row = {
|
||||
fk => target.id,
|
||||
:name => DiscourseAutomation::CUSTOM_FIELD,
|
||||
:value => id,
|
||||
:created_at => now,
|
||||
:updated_at => now,
|
||||
}
|
||||
|
||||
relation = "#{target.class.name}CustomField".constantize
|
||||
relation.upsert(
|
||||
row,
|
||||
unique_by:
|
||||
"idx_#{target.class.name.downcase}_custom_fields_discourse_automation_unique_id_partial",
|
||||
)
|
||||
end
|
||||
|
||||
def detach_custom_field(target)
|
||||
if ![Topic, Post, User].any? { |m| target.is_a?(m) }
|
||||
raise "Expected an instance of Topic/Post/User."
|
||||
end
|
||||
|
||||
fk = target.custom_fields_fk
|
||||
relation = "#{target.class.name}CustomField".constantize
|
||||
relation.where(
|
||||
fk => target.id,
|
||||
:name => DiscourseAutomation::CUSTOM_FIELD,
|
||||
:value => id,
|
||||
).delete_all
|
||||
end
|
||||
|
||||
def trigger_field(name)
|
||||
field = fields.find_by(target: "trigger", name: name)
|
||||
field ? field.metadata : {}
|
||||
end
|
||||
|
||||
def has_trigger_field?(name)
|
||||
!!fields.find_by(target: "trigger", name: name)
|
||||
end
|
||||
|
||||
def script_field(name)
|
||||
field = fields.find_by(target: "script", name: name)
|
||||
field ? field.metadata : {}
|
||||
end
|
||||
|
||||
def upsert_field!(name, component, metadata, target: "script")
|
||||
field = fields.find_or_initialize_by(name: name, component: component, target: target)
|
||||
field.update!(metadata: metadata)
|
||||
end
|
||||
|
||||
def self.deserialize_context(context)
|
||||
new_context = ActiveSupport::HashWithIndifferentAccess.new
|
||||
|
||||
context.each do |key, value|
|
||||
if key.start_with?("_serialized_")
|
||||
new_key = key[12..-1]
|
||||
found = nil
|
||||
if value["class"] == "Symbol"
|
||||
found = value["value"].to_sym
|
||||
else
|
||||
found = value["class"].constantize.find_by(id: value["id"])
|
||||
end
|
||||
new_context[new_key] = found
|
||||
else
|
||||
new_context[key] = value
|
||||
end
|
||||
end
|
||||
new_context
|
||||
end
|
||||
|
||||
def self.serialize_context(context)
|
||||
new_context = {}
|
||||
context.each do |k, v|
|
||||
if v.is_a?(Symbol)
|
||||
new_context["_serialized_#{k}"] = { "class" => "Symbol", "value" => v.to_s }
|
||||
elsif v.is_a?(ActiveRecord::Base)
|
||||
new_context["_serialized_#{k}"] = { "class" => v.class.name, "id" => v.id }
|
||||
else
|
||||
new_context[k] = v
|
||||
end
|
||||
end
|
||||
new_context
|
||||
end
|
||||
|
||||
def trigger_in_background!(context = {})
|
||||
Jobs.enqueue(
|
||||
:discourse_automation_trigger,
|
||||
automation_id: id,
|
||||
context: self.class.serialize_context(context),
|
||||
)
|
||||
end
|
||||
|
||||
def trigger!(context = {})
|
||||
if enabled
|
||||
if scriptable.background && !running_in_background
|
||||
trigger_in_background!(context)
|
||||
else
|
||||
triggerable&.on_call&.call(self, serialized_fields)
|
||||
scriptable.script.call(context, serialized_fields, self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def triggerable
|
||||
trigger && @triggerable ||= DiscourseAutomation::Triggerable.new(trigger, self)
|
||||
end
|
||||
|
||||
def scriptable
|
||||
script && @scriptable ||= DiscourseAutomation::Scriptable.new(script, self)
|
||||
end
|
||||
|
||||
def serialized_fields
|
||||
fields
|
||||
&.pluck(:name, :metadata)
|
||||
&.reduce({}) do |acc, hash|
|
||||
name, field = hash
|
||||
acc[name] = field
|
||||
acc
|
||||
end || {}
|
||||
end
|
||||
|
||||
def reset!
|
||||
pending_automations.delete_all
|
||||
pending_pms.delete_all
|
||||
scriptable&.on_reset&.call(self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_trigger_fields
|
||||
!triggerable || triggerable.valid?(self)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,246 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class Field < ActiveRecord::Base
|
||||
self.table_name = "discourse_automation_fields"
|
||||
|
||||
belongs_to :automation, class_name: "DiscourseAutomation::Automation"
|
||||
|
||||
around_save :on_update_callback
|
||||
|
||||
def on_update_callback
|
||||
previous_fields = automation.serialized_fields
|
||||
|
||||
automation.reset!
|
||||
|
||||
yield
|
||||
|
||||
automation&.triggerable&.on_update&.call(
|
||||
automation,
|
||||
automation.serialized_fields,
|
||||
previous_fields,
|
||||
)
|
||||
end
|
||||
|
||||
validate :required_field
|
||||
def required_field
|
||||
if template && template[:required] && metadata && metadata["value"].blank?
|
||||
raise_required_field(name, target, targetable)
|
||||
end
|
||||
end
|
||||
|
||||
validate :validator
|
||||
def validator
|
||||
if template && template[:validator]
|
||||
error = template[:validator].call(metadata["value"])
|
||||
errors.add(:base, error) if error
|
||||
end
|
||||
end
|
||||
|
||||
def targetable
|
||||
target == "trigger" ? automation.triggerable : automation.scriptable
|
||||
end
|
||||
|
||||
def template
|
||||
targetable&.fields&.find do |tf|
|
||||
targetable.id == target && tf[:name].to_s == name && tf[:component].to_s == component
|
||||
end
|
||||
end
|
||||
|
||||
validate :metadata_schema
|
||||
def metadata_schema
|
||||
if !(targetable.components.include?(component.to_sym))
|
||||
errors.add(
|
||||
:base,
|
||||
I18n.t(
|
||||
"discourse_automation.models.fields.invalid_field",
|
||||
component: component,
|
||||
target: target,
|
||||
target_name: targetable.name,
|
||||
),
|
||||
)
|
||||
else
|
||||
schema = SCHEMAS[component]
|
||||
if !schema ||
|
||||
!JSONSchemer.schema({ "type" => "object", "properties" => schema }).valid?(metadata)
|
||||
errors.add(
|
||||
:base,
|
||||
I18n.t(
|
||||
"discourse_automation.models.fields.invalid_metadata",
|
||||
component: component,
|
||||
field: name,
|
||||
),
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
SCHEMAS = {
|
||||
"key-value" => {
|
||||
"type" => "array",
|
||||
"uniqueItems" => true,
|
||||
"items" => {
|
||||
"type" => "object",
|
||||
"title" => "group",
|
||||
"properties" => {
|
||||
"key" => {
|
||||
"type" => "string",
|
||||
},
|
||||
"value" => {
|
||||
"type" => "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"choices" => {
|
||||
"value" => {
|
||||
"type" => %w[string integer null],
|
||||
},
|
||||
},
|
||||
"tags" => {
|
||||
"value" => {
|
||||
"type" => "array",
|
||||
"items" => [{ type: "string" }],
|
||||
},
|
||||
},
|
||||
"trust-levels" => {
|
||||
"value" => {
|
||||
"type" => "array",
|
||||
"items" => [{ type: "integer" }],
|
||||
},
|
||||
},
|
||||
"categories" => {
|
||||
"value" => {
|
||||
"type" => "array",
|
||||
"items" => [{ type: "string" }],
|
||||
},
|
||||
},
|
||||
"category" => {
|
||||
"value" => {
|
||||
"type" => %w[string integer null],
|
||||
},
|
||||
},
|
||||
"category_notification_level" => {
|
||||
"value" => {
|
||||
"type" => "integer",
|
||||
},
|
||||
},
|
||||
"custom_field" => {
|
||||
"value" => {
|
||||
"type" => "integer",
|
||||
},
|
||||
},
|
||||
"custom_fields" => {
|
||||
"value" => {
|
||||
"type" => [{ type: "string" }],
|
||||
},
|
||||
},
|
||||
"user" => {
|
||||
"value" => {
|
||||
"type" => "string",
|
||||
},
|
||||
},
|
||||
"user_profile" => {
|
||||
"value" => {
|
||||
"type" => "array",
|
||||
"items" => [{ type: "string" }],
|
||||
},
|
||||
},
|
||||
"users" => {
|
||||
"value" => {
|
||||
"type" => "array",
|
||||
"items" => [{ type: "string" }],
|
||||
},
|
||||
},
|
||||
"text" => {
|
||||
"value" => {
|
||||
"type" => %w[string integer null],
|
||||
},
|
||||
},
|
||||
"post" => {
|
||||
"value" => {
|
||||
"type" => %w[string integer null],
|
||||
},
|
||||
},
|
||||
"message" => {
|
||||
"value" => {
|
||||
"type" => %w[string integer null],
|
||||
},
|
||||
},
|
||||
"boolean" => {
|
||||
"value" => {
|
||||
"type" => ["boolean"],
|
||||
},
|
||||
},
|
||||
"text_list" => {
|
||||
"value" => {
|
||||
"type" => "array",
|
||||
"items" => [{ type: "string" }],
|
||||
},
|
||||
},
|
||||
"date_time" => {
|
||||
"value" => {
|
||||
"type" => "string",
|
||||
},
|
||||
},
|
||||
"group" => {
|
||||
"value" => {
|
||||
"type" => "integer",
|
||||
},
|
||||
},
|
||||
"email_group_user" => {
|
||||
"value" => {
|
||||
"type" => "array",
|
||||
"items" => [{ type: "string" }],
|
||||
},
|
||||
},
|
||||
"pms" => {
|
||||
type: "array",
|
||||
items: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
"raw" => {
|
||||
"type" => "string",
|
||||
},
|
||||
"title" => {
|
||||
"type" => "string",
|
||||
},
|
||||
"delay" => {
|
||||
"type" => "integer",
|
||||
},
|
||||
"prefers_encrypt" => {
|
||||
"type" => "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"period" => {
|
||||
"type" => "object",
|
||||
"properties" => {
|
||||
"interval" => {
|
||||
"type" => "integer",
|
||||
},
|
||||
"frequency" => {
|
||||
"type" => "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
private
|
||||
|
||||
def raise_required_field(name, target, targetable)
|
||||
errors.add(
|
||||
:base,
|
||||
I18n.t(
|
||||
"discourse_automation.models.fields.required_field",
|
||||
name: name,
|
||||
target: target,
|
||||
target_name: targetable.name,
|
||||
),
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class PendingAutomation < ActiveRecord::Base
|
||||
self.table_name = "discourse_automation_pending_automations"
|
||||
|
||||
belongs_to :automation, class_name: "DiscourseAutomation::Automation"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class PendingPm < ActiveRecord::Base
|
||||
self.table_name = "discourse_automation_pending_pms"
|
||||
|
||||
belongs_to :automation, class_name: "DiscourseAutomation::Automation"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class UserGlobalNotice < ActiveRecord::Base
|
||||
self.table_name = "discourse_automation_user_global_notices"
|
||||
|
||||
belongs_to :user
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StalledTopicFinder
|
||||
def self.call(stalled_date, tags: nil, categories: nil)
|
||||
sql = <<~SQL
|
||||
SELECT t.id
|
||||
FROM topics t
|
||||
SQL
|
||||
|
||||
sql += <<~SQL if tags
|
||||
JOIN topic_tags ON topic_tags.topic_id = t.id
|
||||
JOIN tags
|
||||
ON tags.name IN (:tags)
|
||||
AND tags.id = topic_tags.tag_id
|
||||
SQL
|
||||
|
||||
sql += <<~SQL
|
||||
WHERE t.deleted_at IS NULL
|
||||
AND t.posts_count > 0
|
||||
AND t.archetype != 'private_message'
|
||||
AND NOT t.closed
|
||||
AND NOT t.archived
|
||||
AND NOT EXISTS (
|
||||
SELECT p.id
|
||||
FROM posts p
|
||||
WHERE t.id = p.topic_id
|
||||
AND p.deleted_at IS NULL
|
||||
AND t.user_id = p.user_id
|
||||
AND p.created_at > :stalled_date
|
||||
LIMIT 1
|
||||
)
|
||||
SQL
|
||||
|
||||
sql += <<~SQL if categories
|
||||
AND t.category_id IN (:categories)
|
||||
SQL
|
||||
|
||||
sql += <<~SQL
|
||||
LIMIT 250
|
||||
SQL
|
||||
|
||||
DB.query(sql, categories: categories, tags: tags, stalled_date: stalled_date)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,120 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class AutomationSerializer < ApplicationSerializer
|
||||
attributes :id
|
||||
attributes :name
|
||||
attributes :enabled
|
||||
attributes :script
|
||||
attributes :trigger
|
||||
attributes :updated_at
|
||||
attributes :last_updated_by
|
||||
attributes :next_pending_automation_at
|
||||
attributes :placeholders
|
||||
|
||||
def last_updated_by
|
||||
BasicUserSerializer.new(
|
||||
User.find_by(id: object.last_updated_by_id) || Discourse.system_user,
|
||||
root: false,
|
||||
).as_json
|
||||
end
|
||||
|
||||
def include_next_pending_automation_at?
|
||||
object.pending_automations.exists?
|
||||
end
|
||||
|
||||
def next_pending_automation_at
|
||||
object&.pending_automations&.first&.execute_at
|
||||
end
|
||||
|
||||
def placeholders
|
||||
scriptable_placeholders =
|
||||
DiscourseAutomation
|
||||
.filter_by_trigger(scriptable&.placeholders || [], object.trigger)
|
||||
.map { |placeholder| placeholder[:name] }
|
||||
triggerable_placeholders = triggerable&.placeholders || []
|
||||
|
||||
(scriptable_placeholders + triggerable_placeholders).map do |placeholder|
|
||||
placeholder.to_s.gsub(/\s+/, "_").underscore
|
||||
end
|
||||
end
|
||||
|
||||
def script
|
||||
key = "discourse_automation.scriptables"
|
||||
doc_key = "#{key}.#{object.script}.doc"
|
||||
script_with_trigger_key = "#{key}.#{object.script}_with_#{object.trigger}.doc"
|
||||
|
||||
{
|
||||
id: object.script,
|
||||
version: scriptable.version,
|
||||
name: I18n.t("#{key}.#{object.script}.title"),
|
||||
description: I18n.t("#{key}.#{object.script}.description"),
|
||||
doc: I18n.exists?(doc_key, :en) ? I18n.t(doc_key) : nil,
|
||||
with_trigger_doc:
|
||||
I18n.exists?(script_with_trigger_key, :en) ? I18n.t(script_with_trigger_key) : nil,
|
||||
forced_triggerable: scriptable.forced_triggerable,
|
||||
not_found: scriptable.not_found,
|
||||
templates:
|
||||
process_templates(filter_fields_with_priority(scriptable.fields, object.trigger&.to_sym)),
|
||||
fields: process_fields(object.fields.where(target: "script")),
|
||||
}
|
||||
end
|
||||
|
||||
def trigger
|
||||
key = "discourse_automation.triggerables"
|
||||
doc_key = "#{key}.#{object.trigger}.doc"
|
||||
|
||||
{
|
||||
id: object.trigger,
|
||||
name: I18n.t("#{key}.#{object.trigger}.title"),
|
||||
description: I18n.t("#{key}.#{object.trigger}.description"),
|
||||
doc: I18n.exists?(doc_key, :en) ? I18n.t(doc_key) : nil,
|
||||
not_found: triggerable&.not_found,
|
||||
templates: process_templates(triggerable&.fields || []),
|
||||
fields: process_fields(object.fields.where(target: "trigger")),
|
||||
settings: triggerable&.settings,
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_fields_with_priority(arr, trigger)
|
||||
unique_with_priority = {}
|
||||
|
||||
arr.each do |item|
|
||||
name = item[:name]
|
||||
if (item[:triggerable]&.to_sym == trigger&.to_sym || item[:triggerable].nil?) &&
|
||||
(!unique_with_priority.key?(name) || unique_with_priority[name][:triggerable].nil?)
|
||||
unique_with_priority[name] = item
|
||||
end
|
||||
end
|
||||
|
||||
unique_with_priority.values
|
||||
end
|
||||
|
||||
def process_templates(fields)
|
||||
ActiveModel::ArraySerializer.new(
|
||||
fields,
|
||||
each_serializer: DiscourseAutomation::TemplateSerializer,
|
||||
scope: {
|
||||
automation: object,
|
||||
},
|
||||
).as_json
|
||||
end
|
||||
|
||||
def process_fields(fields)
|
||||
ActiveModel::ArraySerializer.new(
|
||||
fields || [],
|
||||
each_serializer: DiscourseAutomation::FieldSerializer,
|
||||
).as_json || []
|
||||
end
|
||||
|
||||
def scriptable
|
||||
object.scriptable
|
||||
end
|
||||
|
||||
def triggerable
|
||||
object.triggerable
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class FieldSerializer < ApplicationSerializer
|
||||
attributes :id, :component, :name, :metadata, :is_required
|
||||
|
||||
def metadata
|
||||
object.metadata || {}
|
||||
end
|
||||
|
||||
def is_required
|
||||
object.template&.dig(:required)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class TemplateSerializer < ApplicationSerializer
|
||||
attributes :name,
|
||||
:component,
|
||||
:extra,
|
||||
:accepts_placeholders,
|
||||
:accepted_contexts,
|
||||
:read_only,
|
||||
:default_value,
|
||||
:is_required
|
||||
|
||||
def default_value
|
||||
scope[:automation].scriptable&.forced_triggerable&.dig(:state, name) || object[:default_value]
|
||||
end
|
||||
|
||||
def read_only
|
||||
scope[:automation].scriptable&.forced_triggerable&.dig(:state, name).present?
|
||||
end
|
||||
|
||||
def name
|
||||
object[:name]
|
||||
end
|
||||
|
||||
def component
|
||||
object[:component]
|
||||
end
|
||||
|
||||
def extra
|
||||
object[:extra]
|
||||
end
|
||||
|
||||
def accepts_placeholders
|
||||
object[:accepts_placeholders]
|
||||
end
|
||||
|
||||
def accepted_contexts
|
||||
object[:accepted_contexts]
|
||||
end
|
||||
|
||||
def is_required
|
||||
object[:required]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class TriggerSerializer < ApplicationSerializer
|
||||
attributes :id, :name, :metadata
|
||||
|
||||
def metadata
|
||||
((options[:trigger_metadata] || {}).stringify_keys).merge(object.metadata || {})
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class UserGlobalNoticeSerializer < ApplicationSerializer
|
||||
attributes :id, :notice, :level, :created_at, :updated_at, :identifier
|
||||
|
||||
def level
|
||||
object.level || "info"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class UserBadgeGrantedHandler
|
||||
def self.handle(automation, badge_id, user_id)
|
||||
tracked_badge_id = automation.trigger_field("badge")["value"]
|
||||
return if tracked_badge_id != badge_id
|
||||
|
||||
badge = Badge.find(badge_id)
|
||||
|
||||
only_first_grant = automation.trigger_field("only_first_grant")["value"]
|
||||
|
||||
return if only_first_grant && UserBadge.where(user_id: user_id, badge_id: badge_id).count > 1
|
||||
|
||||
user = User.find(user_id)
|
||||
|
||||
automation.trigger!(
|
||||
"kind" => DiscourseAutomation::Triggers::USER_BADGE_GRANTED,
|
||||
"usernames" => [user.username],
|
||||
"badge" => badge,
|
||||
"placeholders" => {
|
||||
"badge_name" => badge.name,
|
||||
"grant_count" => badge.grant_count,
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
import RestAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default class Adapter extends RestAdapter {
|
||||
basePath() {
|
||||
return "/admin/plugins/discourse-automation/";
|
||||
}
|
||||
|
||||
pathFor() {
|
||||
return super.pathFor(...arguments).replace("_", "-") + ".json";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import DiscourseAutomationAdapter from "./discourse-automation-adapter";
|
||||
|
||||
export default class AutomationAdapter extends DiscourseAutomationAdapter {
|
||||
jsonMode = true;
|
||||
|
||||
apiNameFor() {
|
||||
return "automation";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import DiscourseAutomationAdapter from "./discourse-automation-adapter";
|
||||
|
||||
export default class ScriptableAdapter extends DiscourseAutomationAdapter {
|
||||
jsonMode = true;
|
||||
|
||||
apiNameFor() {
|
||||
return "scriptable";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import DiscourseAutomationAdapter from "./discourse-automation-adapter";
|
||||
|
||||
export default class TriggerableAdapter extends DiscourseAutomationAdapter {
|
||||
jsonMode = true;
|
||||
|
||||
apiNameFor() {
|
||||
return "triggerable";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import RestModel from "discourse/models/rest";
|
||||
|
||||
const ATTRIBUTES = ["name", "script", "fields", "trigger", "id"];
|
||||
|
||||
export default class Automation extends RestModel {
|
||||
updateProperties() {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
fields: this.fields,
|
||||
script: this.script.id,
|
||||
trigger: {
|
||||
id: this.trigger.id,
|
||||
name: this.trigger.name,
|
||||
metadata: this.trigger.metadata,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
createProperties() {
|
||||
return this.getProperties(ATTRIBUTES);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import { TrackedObject } from "@ember-compat/tracked-built-ins";
|
||||
|
||||
export default class DiscourseAutomationField {
|
||||
static create(template, target, json = {}) {
|
||||
const field = new DiscourseAutomationField();
|
||||
field.acceptsPlaceholders = template.accepts_placeholders;
|
||||
field.acceptedContexts = template.accepted_contexts;
|
||||
field.targetName = target.name;
|
||||
field.targetType = target.type;
|
||||
field.name = template.name;
|
||||
field.component = template.component;
|
||||
field.isDisabled = template.read_only;
|
||||
|
||||
// backwards compatibility with forced scriptable fields
|
||||
if (field.isDisabled) {
|
||||
field.metadata.value =
|
||||
template.default_value || template.value || json?.metadata?.value;
|
||||
} else {
|
||||
field.metadata.value =
|
||||
template.value || json?.metadata?.value || template.default_value;
|
||||
}
|
||||
|
||||
// null is not a valid value for metadata.value
|
||||
if (field.metadata.value === null) {
|
||||
field.metadata.value = undefined;
|
||||
}
|
||||
|
||||
field.isRequired = template.is_required;
|
||||
field.extra = new TrackedObject(template.extra);
|
||||
return field;
|
||||
}
|
||||
|
||||
@tracked acceptsPlaceholders = false;
|
||||
@tracked component;
|
||||
@tracked extra = new TrackedObject();
|
||||
@tracked isDisabled = false;
|
||||
@tracked isRequired = false;
|
||||
@tracked metadata = new TrackedObject();
|
||||
@tracked name;
|
||||
@tracked targetType;
|
||||
@tracked targetName;
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
name: this.name,
|
||||
target: this.targetType,
|
||||
component: this.component,
|
||||
metadata: this.metadata,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import RestModel from "discourse/models/rest";
|
||||
|
||||
export default class Script extends RestModel {}
|
|
@ -0,0 +1,97 @@
|
|||
import Component from "@glimmer/component";
|
||||
import I18n from "I18n";
|
||||
import DaBooleanField from "./fields/da-boolean-field";
|
||||
import DaCategoriesField from "./fields/da-categories-field";
|
||||
import DaCategoryField from "./fields/da-category-field";
|
||||
import DaCategoryNotificationlevelField from "./fields/da-category-notification-level-field";
|
||||
import DaChoicesField from "./fields/da-choices-field";
|
||||
import DaCustomField from "./fields/da-custom-field";
|
||||
import DaCustomFields from "./fields/da-custom-fields";
|
||||
import DaDateTimeField from "./fields/da-date-time-field";
|
||||
import DaEmailGroupUserField from "./fields/da-email-group-user-field";
|
||||
import DaGroupField from "./fields/da-group-field";
|
||||
import DaKeyValueField from "./fields/da-key-value-field";
|
||||
import DaMessageField from "./fields/da-message-field";
|
||||
import DaPeriodField from "./fields/da-period-field";
|
||||
import DaPmsField from "./fields/da-pms-field";
|
||||
import DaPostField from "./fields/da-post-field";
|
||||
import DaTagsField from "./fields/da-tags-field";
|
||||
import DaTextField from "./fields/da-text-field";
|
||||
import DaTextListField from "./fields/da-text-list-field";
|
||||
import DaTrustLevelsField from "./fields/da-trust-levels-field";
|
||||
import DaUserField from "./fields/da-user-field";
|
||||
import DaUserProfileField from "./fields/da-user-profile-field";
|
||||
import DaUsersField from "./fields/da-users-field";
|
||||
|
||||
const FIELD_COMPONENTS = {
|
||||
period: DaPeriodField,
|
||||
date_time: DaDateTimeField,
|
||||
text_list: DaTextListField,
|
||||
pms: DaPmsField,
|
||||
text: DaTextField,
|
||||
message: DaMessageField,
|
||||
categories: DaCategoriesField,
|
||||
user: DaUserField,
|
||||
users: DaUsersField,
|
||||
user_profile: DaUserProfileField,
|
||||
post: DaPostField,
|
||||
tags: DaTagsField,
|
||||
"key-value": DaKeyValueField,
|
||||
boolean: DaBooleanField,
|
||||
"trust-levels": DaTrustLevelsField,
|
||||
category: DaCategoryField,
|
||||
group: DaGroupField,
|
||||
choices: DaChoicesField,
|
||||
category_notification_level: DaCategoryNotificationlevelField,
|
||||
email_group_user: DaEmailGroupUserField,
|
||||
custom_field: DaCustomField,
|
||||
custom_fields: DaCustomFields,
|
||||
};
|
||||
|
||||
export default class AutomationField extends Component {
|
||||
<template>
|
||||
{{#if this.displayField}}
|
||||
<this.component
|
||||
@field={{@field}}
|
||||
@placeholders={{@automation.placeholders}}
|
||||
@label={{this.label}}
|
||||
@description={{this.description}}
|
||||
@saveAutomation={{@saveAutomation}}
|
||||
/>
|
||||
{{/if}}
|
||||
</template>
|
||||
|
||||
get component() {
|
||||
return FIELD_COMPONENTS[this.args.field.component];
|
||||
}
|
||||
|
||||
get label() {
|
||||
return I18n.t(
|
||||
`discourse_automation${this.target}fields.${this.args.field.name}.label`
|
||||
);
|
||||
}
|
||||
|
||||
get displayField() {
|
||||
const triggerId = this.args.automation?.trigger?.id;
|
||||
const triggerable = this.args.field?.triggerable;
|
||||
return triggerId && (!triggerable || triggerable === triggerId);
|
||||
}
|
||||
|
||||
get placeholdersString() {
|
||||
return this.args.field.placeholders.join(", ");
|
||||
}
|
||||
|
||||
get target() {
|
||||
return this.args.field.targetType === "script"
|
||||
? `.scriptables.${this.args.automation.script.id.replace(/-/g, "_")}.`
|
||||
: `.triggerables.${this.args.automation.trigger.id.replace(/-/g, "_")}.`;
|
||||
}
|
||||
|
||||
get translationKey() {
|
||||
return `discourse_automation${this.target}fields.${this.args.field.name}.description`;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return I18n.lookup(this.translationKey);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default class BaseField extends Component {
|
||||
get displayPlaceholders() {
|
||||
return (
|
||||
this.args.placeholders?.length && this.args.field?.acceptsPlaceholders
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
mutValue(newValue) {
|
||||
this.args.field.metadata.value = newValue;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { Input } from "@ember/component";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class BooleanField extends BaseField {
|
||||
<template>
|
||||
<section class="field boolean-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<Input
|
||||
@type="checkbox"
|
||||
@checked={{@field.metadata.value}}
|
||||
{{on "input" this.onInput}}
|
||||
disabled={{@field.isDisabled}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@action
|
||||
onInput(event) {
|
||||
this.mutValue(event.target.checked);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { fn, hash } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import Category from "discourse/models/category";
|
||||
import CategorySelector from "select-kit/components/category-selector";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class CategoriesField extends BaseField {
|
||||
<template>
|
||||
{{! template-lint-disable no-redundant-fn }}
|
||||
<section class="field categories-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<CategorySelector
|
||||
@categories={{this.categories}}
|
||||
@onChange={{fn this.onChangeCategories}}
|
||||
@options={{hash clearable=true disabled=@field.isDisabled}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
get categories() {
|
||||
const ids = this.args.field?.metadata?.value || [];
|
||||
return ids.map((id) => Category.findById(id)).filter(Boolean);
|
||||
}
|
||||
|
||||
@action
|
||||
onChangeCategories(categories) {
|
||||
this.mutValue(categories.mapBy("id"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { hash } from "@ember/helper";
|
||||
import CategoryChooser from "select-kit/components/category-chooser";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class CategoryField extends BaseField {
|
||||
<template>
|
||||
<section class="field category-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<CategoryChooser
|
||||
@value={{@field.metadata.value}}
|
||||
@onChange={{this.mutValue}}
|
||||
@options={{hash clearable=true disabled=@field.isDisabled}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { hash } from "@ember/helper";
|
||||
import CategoryNotificationsButton from "select-kit/components/category-notifications-button";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class CategoryNotficationLevelField extends BaseField {
|
||||
<template>
|
||||
<section class="field category-notification-level-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<CategoryNotificationsButton
|
||||
@value={{@field.metadata.value}}
|
||||
@onChange={{this.mutValue}}
|
||||
@options={{hash showFullTitle=true}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { hash } from "@ember/helper";
|
||||
import I18n from "I18n";
|
||||
import ComboBox from "select-kit/components/combo-box";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class ChoicesField extends BaseField {
|
||||
<template>
|
||||
<div class="field control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<ComboBox
|
||||
@value={{@field.metadata.value}}
|
||||
@content={{this.replacedContent}}
|
||||
@onChange={{this.mutValue}}
|
||||
@options={{hash
|
||||
allowAny=false
|
||||
clearable=true
|
||||
disabled=@field.isDisabled
|
||||
}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
get replacedContent() {
|
||||
return (this.args.field.extra.content || []).map((r) => {
|
||||
return {
|
||||
id: r.id,
|
||||
name: r.translated_name || I18n.t(r.name),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import ComboBox from "select-kit/components/combo-box";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class GroupField extends BaseField {
|
||||
@service store;
|
||||
@tracked allCustomFields = [];
|
||||
|
||||
<template>
|
||||
<section class="field group-field" {{didInsert this.loadUserFields}}>
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<ComboBox
|
||||
@content={{this.allCustomFields}}
|
||||
@value={{@field.metadata.value}}
|
||||
@onChange={{this.mutValue}}
|
||||
/>
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@bind
|
||||
loadUserFields() {
|
||||
this.store.findAll("user-field").then((fields) => {
|
||||
this.allCustomFields = fields.content;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import { hash } from "@ember/helper";
|
||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import MultiSelect from "select-kit/components/multi-select";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class GroupField extends BaseField {
|
||||
@service store;
|
||||
@tracked allCustomFields = [];
|
||||
|
||||
<template>
|
||||
<section class="field group-field" {{didInsert this.loadUserFields}}>
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<MultiSelect
|
||||
@value={{@field.metadata.value}}
|
||||
@content={{this.allCustomFields}}
|
||||
@onChange={{this.mutValue}}
|
||||
@nameProperty={{null}}
|
||||
@valueProperty={{null}}
|
||||
@options={{hash allowAny=false disabled=@field.isDisabled}}
|
||||
/>
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@bind
|
||||
loadUserFields() {
|
||||
this.store.findAll("user-field").then((fields) => {
|
||||
this.allCustomFields = fields.content.map((field) => field.name);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import { Input } from "@ember/component";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class DateTimeField extends BaseField {
|
||||
<template>
|
||||
<section class="field date-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<div class="controls-row">
|
||||
<Input
|
||||
@type="datetime-local"
|
||||
@value={{readonly this.localTime}}
|
||||
disabled={{@field.isDisabled}}
|
||||
{{on "input" this.convertToUniversalTime}}
|
||||
/>
|
||||
|
||||
{{#if @field.metadata.value}}
|
||||
<DButton
|
||||
@icon="trash-alt"
|
||||
@action={{this.reset}}
|
||||
@disabled={{@field.isDisabled}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@action
|
||||
convertToUniversalTime(event) {
|
||||
const date = event.target.value;
|
||||
if (!date) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.mutValue(moment(date).utc().format());
|
||||
}
|
||||
|
||||
@action
|
||||
reset() {
|
||||
this.mutValue(null);
|
||||
}
|
||||
|
||||
get localTime() {
|
||||
return (
|
||||
this.args.field.metadata.value &&
|
||||
moment(this.args.field.metadata.value)
|
||||
.local()
|
||||
.format(moment.HTML5_FMT.DATETIME_LOCAL)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import { hash } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import EmailGroupUserChooser from "select-kit/components/email-group-user-chooser";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class EmailGroupUserField extends BaseField {
|
||||
@tracked recipients;
|
||||
@tracked groups = [];
|
||||
|
||||
<template>
|
||||
<section class="field email-group-user-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<EmailGroupUserChooser
|
||||
@value={{@field.metadata.value}}
|
||||
@onChange={{this.mutValue}}
|
||||
@options={{hash
|
||||
includeGroups=true
|
||||
includeMessageableGroups=true
|
||||
allowEmails=true
|
||||
autoWrap=true
|
||||
disabled=@field.isDisabled
|
||||
}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@action
|
||||
updateRecipients(selected, content) {
|
||||
const newGroups = content.filterBy("isGroup").mapBy("id");
|
||||
this._updateGroups(selected, newGroups);
|
||||
this.recipients = selected.join(",");
|
||||
}
|
||||
|
||||
_updateGroups(selected, newGroups) {
|
||||
const groups = [];
|
||||
|
||||
this.groups.forEach((existing) => {
|
||||
if (selected.includes(existing)) {
|
||||
groups.addObject(existing);
|
||||
}
|
||||
});
|
||||
|
||||
newGroups.forEach((newGroup) => {
|
||||
if (!groups.includes(newGroup)) {
|
||||
groups.addObject(newGroup);
|
||||
}
|
||||
});
|
||||
|
||||
this.groups = groups;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
const FieldDescription = <template>
|
||||
{{#if @description}}
|
||||
<p class="control-description">
|
||||
{{@description}}
|
||||
</p>
|
||||
{{/if}}
|
||||
</template>;
|
||||
|
||||
export default FieldDescription;
|
|
@ -0,0 +1,14 @@
|
|||
const FieldLabel = <template>
|
||||
{{#if @label}}
|
||||
<label class="control-label">
|
||||
<span>
|
||||
{{@label}}
|
||||
{{#if @field.isRequired}}
|
||||
*
|
||||
{{/if}}
|
||||
</span>
|
||||
</label>
|
||||
{{/if}}
|
||||
</template>;
|
||||
|
||||
export default FieldLabel;
|
|
@ -0,0 +1,51 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import { hash } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import Group from "discourse/models/group";
|
||||
import GroupChooser from "select-kit/components/group-chooser";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class GroupField extends BaseField {
|
||||
@tracked allGroups = [];
|
||||
|
||||
<template>
|
||||
<section class="field group-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<GroupChooser
|
||||
@content={{this.allGroups}}
|
||||
@value={{@field.metadata.value}}
|
||||
@labelProperty="name"
|
||||
@onChange={{this.setGroupField}}
|
||||
@options={{hash maximum=1 disabled=@field.isDisabled}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
Group.findAll({
|
||||
ignore_automatic: this.args.field.extra.ignore_automatic ?? false,
|
||||
}).then((groups) => {
|
||||
if (this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.allGroups = groups;
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
setGroupField(groupIds) {
|
||||
this.mutValue(groupIds?.firstObject);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import { hash } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import ModalJsonSchemaEditor from "discourse/components/modal/json-schema-editor";
|
||||
import I18n from "I18n";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class KeyValueField extends BaseField {
|
||||
@tracked showJsonEditorModal = false;
|
||||
|
||||
jsonSchema = {
|
||||
type: "array",
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: "object",
|
||||
title: "group",
|
||||
properties: {
|
||||
key: {
|
||||
type: "string",
|
||||
},
|
||||
value: {
|
||||
type: "string",
|
||||
format: "textarea",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
<template>
|
||||
<section class="field key-value-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<DButton class="configure-btn" @action={{this.openModal}}>
|
||||
{{this.showJsonModalLabel}}
|
||||
</DButton>
|
||||
|
||||
{{#if this.showJsonEditorModal}}
|
||||
<ModalJsonSchemaEditor
|
||||
@model={{hash
|
||||
value=this.value
|
||||
updateValue=this.handleValueChange
|
||||
settingName=@label
|
||||
jsonSchema=this.jsonSchema
|
||||
}}
|
||||
@closeModal={{this.closeModal}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
get value() {
|
||||
return (
|
||||
this.args.field.metadata.value ||
|
||||
'[{"key":"example","value":"You posted {{key}}"}]'
|
||||
);
|
||||
}
|
||||
|
||||
get keyCount() {
|
||||
if (this.args.field.metadata.value) {
|
||||
return JSON.parse(this.value).length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
get showJsonModalLabel() {
|
||||
if (this.keyCount === 0) {
|
||||
return I18n.t(
|
||||
"discourse_automation.fields.key_value.label_without_count"
|
||||
);
|
||||
} else {
|
||||
return I18n.t("discourse_automation.fields.key_value.label_with_count", {
|
||||
count: this.keyCount,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
handleValueChange(value) {
|
||||
if (value !== this.args.field.metadata.value) {
|
||||
this.mutValue(value);
|
||||
this.args.saveAutomation();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
openModal() {
|
||||
this.showJsonEditorModal = true;
|
||||
}
|
||||
|
||||
@action
|
||||
closeModal() {
|
||||
this.showJsonEditorModal = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
import { TextArea } from "@ember/legacy-built-in-components";
|
||||
import { action } from "@ember/object";
|
||||
import PlaceholdersList from "../placeholders-list";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class MessageField extends BaseField {
|
||||
<template>
|
||||
<section class="field message-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<div class="field-wrapper">
|
||||
<TextArea
|
||||
@value={{@field.metadata.value}}
|
||||
@input={{this.updateValue}}
|
||||
@disabled={{@field.isDisabled}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
|
||||
{{#if this.displayPlaceholders}}
|
||||
<PlaceholdersList
|
||||
@currentValue={{@field.metadata.value}}
|
||||
@placeholders={{@placeholder}}
|
||||
@onCopy={{this.test}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@action
|
||||
updateValue(event) {
|
||||
this.mutValue(event.target.value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import { Input } from "@ember/component";
|
||||
import { hash } from "@ember/helper";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { next } from "@ember/runloop";
|
||||
import { TrackedObject } from "@ember-compat/tracked-built-ins";
|
||||
import I18n from "I18n";
|
||||
import ComboBox from "select-kit/components/combo-box";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class PeriodField extends BaseField {
|
||||
@tracked interval = 1;
|
||||
@tracked frequency = null;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
next(() => {
|
||||
if (!this.args.field.metadata.value) {
|
||||
this.args.field.metadata.value = new TrackedObject({
|
||||
interval: 1,
|
||||
frequency: null,
|
||||
});
|
||||
}
|
||||
|
||||
this.interval = this.args.field.metadata.value.interval;
|
||||
this.frequency = this.args.field.metadata.value.frequency;
|
||||
});
|
||||
}
|
||||
|
||||
get recurringLabel() {
|
||||
return I18n.t("discourse_automation.triggerables.recurring.every");
|
||||
}
|
||||
|
||||
get replacedContent() {
|
||||
return (this.args.field?.extra?.content || []).map((r) => {
|
||||
return {
|
||||
id: r.id,
|
||||
name: I18n.t(r.name),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
mutInterval(event) {
|
||||
this.args.field.metadata.value.interval = event.target.value;
|
||||
}
|
||||
|
||||
@action
|
||||
mutFrequency(value) {
|
||||
this.args.field.metadata.value.frequency = value;
|
||||
this.frequency = value;
|
||||
}
|
||||
|
||||
<template>
|
||||
<div class="field period-field control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
{{this.recurringLabel}}
|
||||
|
||||
<Input
|
||||
@type="number"
|
||||
defaultValue="1"
|
||||
@value={{this.interval}}
|
||||
disabled={{@field.isDisabled}}
|
||||
required={{@field.isRequired}}
|
||||
{{on "input" this.mutInterval}}
|
||||
/>
|
||||
|
||||
<ComboBox
|
||||
@value={{this.frequency}}
|
||||
@content={{this.replacedContent}}
|
||||
@onChange={{this.mutFrequency}}
|
||||
@options={{hash allowAny=false disabled=@field.isDisabled}}
|
||||
@required={{@field.isRequired}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
import { Input } from "@ember/component";
|
||||
import { concat, fn } from "@ember/helper";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { next } from "@ember/runloop";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { TrackedArray, TrackedObject } from "@ember-compat/tracked-built-ins";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import DEditor from "discourse/components/d-editor";
|
||||
import I18n from "I18n";
|
||||
import PlaceholdersList from "../placeholders-list";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class PmsField extends BaseField {
|
||||
@service dialog;
|
||||
|
||||
noPmCreatedLabel = I18n.t("discourse_automation.fields.pms.no_pm_created");
|
||||
|
||||
prefersEncryptLabel = I18n.t(
|
||||
"discourse_automation.fields.pms.prefers_encrypt.label"
|
||||
);
|
||||
|
||||
delayLabel = I18n.t("discourse_automation.fields.pms.delay.label");
|
||||
|
||||
pmTitleLabel = I18n.t("discourse_automation.fields.pms.title.label");
|
||||
|
||||
rawLabel = I18n.t("discourse_automation.fields.pms.raw.label");
|
||||
|
||||
<template>
|
||||
<section class="field pms-field">
|
||||
{{#if @field.metadata.value.length}}
|
||||
<section class="actions header">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
<DButton
|
||||
@icon="plus"
|
||||
@action={{this.insertPM}}
|
||||
class="btn-primary insert-pm"
|
||||
/>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
{{#each @field.metadata.value as |pm|}}
|
||||
<div class="pm-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{this.pmTitleLabel}} @field={{@field}} />
|
||||
<div class="controls">
|
||||
<div class="field-wrapper">
|
||||
<Input
|
||||
id={{concat @field.targetType @field.name "title"}}
|
||||
@value={{pm.title}}
|
||||
class="pm-input pm-title"
|
||||
{{on "input" (fn this.mutPmTitle pm)}}
|
||||
disabled={{@field.isDisabled}}
|
||||
name={{@field.name}}
|
||||
/>
|
||||
|
||||
{{#if this.displayPlaceholders}}
|
||||
<PlaceholdersList
|
||||
@currentValue={{pm.title}}
|
||||
@placeholders={{@placeholders}}
|
||||
@onCopy={{fn this.updatePmTitle pm}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{this.rawLabel}} @field={{@field}} />
|
||||
<div class="controls">
|
||||
<div class="field-wrapper">
|
||||
<DEditor @value={{pm.raw}} />
|
||||
|
||||
{{#if this.displayPlaceholders}}
|
||||
<PlaceholdersList
|
||||
@currentValue={{pm.raw}}
|
||||
@placeholders={{@placeholders}}
|
||||
@onCopy={{fn this.updatePmRaw pm}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">
|
||||
{{this.delayLabel}}
|
||||
</label>
|
||||
|
||||
<div class="controls">
|
||||
<Input
|
||||
@value={{pm.delay}}
|
||||
class="input-large pm-input pm-delay"
|
||||
{{on "input" (fn this.mutPmDelay pm)}}
|
||||
disabled={{@field.isDisabled}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">
|
||||
{{this.prefersEncryptLabel}}
|
||||
</label>
|
||||
|
||||
<div class="controls">
|
||||
<Input
|
||||
@type="checkbox"
|
||||
class="pm-prefers-encrypt"
|
||||
@checked={{pm.prefers_encrypt}}
|
||||
{{on "click" (fn this.prefersEncrypt pm)}}
|
||||
disabled={{@field.isDisabled}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<section class="actions">
|
||||
<DButton
|
||||
@icon="trash-alt"
|
||||
@action={{fn this.removePM pm}}
|
||||
class="btn-danger"
|
||||
@disabled={{@field.isDisabled}}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="no-pm">
|
||||
<p>{{this.noPmCreatedLabel}}</p>
|
||||
<DButton
|
||||
@icon="plus"
|
||||
@label="discourse_automation.fields.pms.add_pm"
|
||||
@action={{this.insertPM}}
|
||||
class="btn-primary insert-pm"
|
||||
@disabled={{@field.isDisabled}}
|
||||
/>
|
||||
</div>
|
||||
{{/each}}
|
||||
</section>
|
||||
</template>
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
// a hack to prevent warnings about modifying multiple times in the same runloop
|
||||
next(() => {
|
||||
this.args.field.metadata.value = new TrackedArray(
|
||||
(this.args.field.metadata.value || []).map((pm) => {
|
||||
return new TrackedObject(pm);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
removePM(pm) {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("discourse_automation.fields.pms.confirm_remove_pm"),
|
||||
didConfirm: () => {
|
||||
return this.args.field.metadata.value.removeObject(pm);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
insertPM() {
|
||||
this.args.field.metadata.value.pushObject(
|
||||
new TrackedObject({
|
||||
title: "",
|
||||
raw: "",
|
||||
delay: 0,
|
||||
prefers_encrypt: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
prefersEncrypt(pm, event) {
|
||||
pm.prefers_encrypt = event.target.checked;
|
||||
}
|
||||
|
||||
@action
|
||||
mutPmTitle(pm, event) {
|
||||
pm.title = event.target.value;
|
||||
}
|
||||
|
||||
@action
|
||||
mutPmDelay(pm, event) {
|
||||
pm.delay = event.target.value;
|
||||
}
|
||||
|
||||
@action
|
||||
updatePmRaw(pm, newRaw) {
|
||||
pm.raw = newRaw;
|
||||
}
|
||||
|
||||
@action
|
||||
updatePmTitle(pm, newRaw) {
|
||||
pm.title = newRaw;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import DEditor from "discourse/components/d-editor";
|
||||
import PlaceholdersList from "../placeholders-list";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class PostField extends BaseField {
|
||||
<template>
|
||||
<section class="field post-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<div class="field-wrapper">
|
||||
<DEditor @value={{@field.metadata.value}} />
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
|
||||
{{#if this.displayPlaceholders}}
|
||||
<PlaceholdersList
|
||||
@currentValue={{@field.metadata.value}}
|
||||
@placeholders={{@placeholders}}
|
||||
@onCopy={{this.mutValue}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { hash } from "@ember/helper";
|
||||
import TagChooser from "select-kit/components/tag-chooser";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class TagsField extends BaseField {
|
||||
<template>
|
||||
<section class="field tags-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<TagChooser
|
||||
@tags={{@field.metadata.value}}
|
||||
@options={{hash allowAny=false disabled=@field.isDisabled}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import { Input } from "@ember/component";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import PlaceholdersList from "../placeholders-list";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class TextField extends BaseField {
|
||||
<template>
|
||||
<section class="field text-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<div class="field-wrapper">
|
||||
<Input
|
||||
@value={{@field.metadata.value}}
|
||||
disabled={{@field.isDisabled}}
|
||||
{{on "input" this.mutText}}
|
||||
name={{@field.name}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
|
||||
{{#if this.displayPlaceholders}}
|
||||
<PlaceholdersList
|
||||
@currentValue={{@field.metadata.value}}
|
||||
@placeholders={{@placeholders}}
|
||||
@onCopy={{this.mutValue}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@action
|
||||
mutText(event) {
|
||||
this.mutValue(event.target.value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import { hash } from "@ember/helper";
|
||||
import MultiSelect from "select-kit/components/multi-select";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class TextListField extends BaseField {
|
||||
<template>
|
||||
<section class="field text-list-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<MultiSelect
|
||||
@value={{@field.metadata.value}}
|
||||
@content={{@field.metadata.value}}
|
||||
@onChange={{this.mutValue}}
|
||||
@nameProperty={{null}}
|
||||
@valueProperty={{null}}
|
||||
@options={{hash allowAny=true disabled=@field.isDisabled}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { inject as service } from "@ember/service";
|
||||
import MultiSelect from "select-kit/components/multi-select";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class TrustLevelsField extends BaseField {
|
||||
@service site;
|
||||
|
||||
<template>
|
||||
<section class="field category-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<MultiSelect
|
||||
@value={{@field.metadata.value}}
|
||||
@content={{this.site.trustLevels}}
|
||||
@onChange={{this.mutValue}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import { fn, hash } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import I18n from "I18n";
|
||||
import UserChooser from "select-kit/components/user-chooser";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class UserField extends BaseField {
|
||||
@action
|
||||
onChangeUsername(usernames) {
|
||||
this.mutValue(usernames[0]);
|
||||
}
|
||||
|
||||
@action
|
||||
modifyContent(field, content) {
|
||||
content = field.acceptedContexts
|
||||
.map((context) => {
|
||||
return {
|
||||
name: I18n.t(
|
||||
`discourse_automation.scriptables.${field.targetName}.fields.${field.name}.${context}_context`
|
||||
),
|
||||
username: context,
|
||||
};
|
||||
})
|
||||
.concat(content);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
<template>
|
||||
<section class="field user-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<UserChooser
|
||||
@value={{@field.metadata.value}}
|
||||
@onChange={{this.onChangeUsername}}
|
||||
@modifyContent={{fn this.modifyContent @field}}
|
||||
@options={{hash
|
||||
maximum=1
|
||||
excludeCurrentUser=false
|
||||
disabled=@field.isDisabled
|
||||
}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import { hash } from "@ember/helper";
|
||||
import MultiSelect from "select-kit/components/multi-select";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class UserProfileField extends BaseField {
|
||||
@tracked allProfileFields = [];
|
||||
|
||||
userProfileFields = [
|
||||
"bio_raw",
|
||||
"website",
|
||||
"location",
|
||||
"date_of_birth",
|
||||
"timezone",
|
||||
];
|
||||
|
||||
<template>
|
||||
<section class="field group-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
<div class="controls">
|
||||
<MultiSelect
|
||||
@value={{@field.metadata.value}}
|
||||
@content={{this.userProfileFields}}
|
||||
@onChange={{this.mutValue}}
|
||||
@nameProperty={{null}}
|
||||
@valueProperty={{null}}
|
||||
@options={{hash allowAny=true disabled=@field.isDisabled}}
|
||||
/>
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { hash } from "@ember/helper";
|
||||
import UserChooser from "select-kit/components/user-chooser";
|
||||
import BaseField from "./da-base-field";
|
||||
import DAFieldDescription from "./da-field-description";
|
||||
import DAFieldLabel from "./da-field-label";
|
||||
|
||||
export default class UsersField extends BaseField {
|
||||
<template>
|
||||
<section class="field users-field">
|
||||
<div class="control-group">
|
||||
<DAFieldLabel @label={{@label}} @field={{@field}} />
|
||||
|
||||
<div class="controls">
|
||||
<UserChooser
|
||||
@value={{@field.metadata.value}}
|
||||
@onChange={{this.mutValue}}
|
||||
@options={{hash
|
||||
excludeCurrentUser=false
|
||||
disabled=@field.isDisabled
|
||||
allowEmails=true
|
||||
}}
|
||||
/>
|
||||
|
||||
{{#if @field.metadata.allowsAutomation}}
|
||||
<span class="help-inline error">{{@field.metadata.error}}</span>
|
||||
{{/if}}
|
||||
|
||||
<DAFieldDescription @description={{@description}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { fn } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import DButton from "discourse/components/d-button";
|
||||
|
||||
export default class PlaceholdersList extends Component {
|
||||
<template>
|
||||
<div class="placeholders-list">
|
||||
{{#each @placeholders as |placeholder|}}
|
||||
<DButton
|
||||
@translatedLabel={{placeholder}}
|
||||
class="placeholder-item"
|
||||
@action={{fn this.copyPlaceholder placeholder}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@action
|
||||
copyPlaceholder(placeholder) {
|
||||
this.args.onCopy(`${this.args.currentValue}{{${placeholder}}}`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
import Controller from "@ember/controller";
|
||||
import { action, computed, set } from "@ember/object";
|
||||
import { filterBy, reads } from "@ember/object/computed";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { extractError } from "discourse/lib/ajax-error";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default class AutomationEdit extends Controller {
|
||||
@service dialog;
|
||||
error = null;
|
||||
isUpdatingAutomation = false;
|
||||
isTriggeringAutomation = false;
|
||||
|
||||
@reads("model.automation") automation;
|
||||
@filterBy("automationForm.fields", "targetType", "script") scriptFields;
|
||||
@filterBy("automationForm.fields", "targetType", "trigger") triggerFields;
|
||||
|
||||
@computed("model.automation.next_pending_automation_at")
|
||||
get nextPendingAutomationAtFormatted() {
|
||||
const date = this.model?.automation?.next_pending_automation_at;
|
||||
if (date) {
|
||||
return moment(date).format("LLLL");
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
saveAutomation() {
|
||||
this.setProperties({ error: null, isUpdatingAutomation: true });
|
||||
|
||||
return ajax(
|
||||
`/admin/plugins/discourse-automation/automations/${this.model.automation.id}.json`,
|
||||
{
|
||||
type: "PUT",
|
||||
data: JSON.stringify({ automation: this.automationForm }),
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.send("refreshRoute");
|
||||
})
|
||||
.catch((e) => this._showError(e))
|
||||
.finally(() => {
|
||||
this.set("isUpdatingAutomation", false);
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
onChangeTrigger(id) {
|
||||
if (this.automationForm.trigger && this.automationForm.trigger !== id) {
|
||||
this._confirmReset(() => {
|
||||
set(this.automationForm, "trigger", id);
|
||||
this.saveAutomation();
|
||||
});
|
||||
} else if (!this.automationForm.trigger) {
|
||||
set(this.automationForm, "trigger", id);
|
||||
this.saveAutomation();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
onManualAutomationTrigger(id) {
|
||||
this._confirmTrigger(() => {
|
||||
this.set("isTriggeringAutomation", true);
|
||||
|
||||
return ajax(`/automations/${id}/trigger.json`, {
|
||||
type: "post",
|
||||
})
|
||||
.catch((e) => this.set("error", extractError(e)))
|
||||
.finally(() => {
|
||||
this.set("isTriggeringAutomation", false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
onChangeScript(id) {
|
||||
if (this.automationForm.script !== id) {
|
||||
this._confirmReset(() => {
|
||||
set(this.automationForm, "script", id);
|
||||
this.saveAutomation();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_confirmReset(callback) {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("discourse_automation.confirm_automation_reset"),
|
||||
didConfirm: () => {
|
||||
return callback && callback();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
_confirmTrigger(callback) {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("discourse_automation.confirm_automation_trigger"),
|
||||
didConfirm: () => {
|
||||
return callback && callback();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
_showError(error) {
|
||||
this.set("error", extractError(error));
|
||||
|
||||
schedule("afterRender", () => {
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import Controller from "@ember/controller";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import escape from "discourse-common/lib/escape";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default class AutomationIndex extends Controller {
|
||||
@service dialog;
|
||||
@service router;
|
||||
|
||||
@action
|
||||
editAutomation(automation) {
|
||||
this.router.transitionTo(
|
||||
"adminPlugins.discourse-automation.edit",
|
||||
automation.id
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
newAutomation() {
|
||||
this.router.transitionTo("adminPlugins.discourse-automation.new");
|
||||
}
|
||||
|
||||
@action
|
||||
destroyAutomation(automation) {
|
||||
this.dialog.deleteConfirm({
|
||||
message: I18n.t("discourse_automation.destroy_automation.confirm", {
|
||||
name: escape(automation.name),
|
||||
}),
|
||||
didConfirm: () => {
|
||||
return automation
|
||||
.destroyRecord()
|
||||
.then(() => this.send("triggerRefresh"))
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import Controller from "@ember/controller";
|
||||
import EmberObject, { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { extractError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default class AutomationNew extends Controller {
|
||||
@service router;
|
||||
|
||||
form = null;
|
||||
error = null;
|
||||
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
this._resetForm();
|
||||
}
|
||||
|
||||
@action
|
||||
saveAutomation(automation) {
|
||||
this.set("error", null);
|
||||
|
||||
automation
|
||||
.save(this.form.getProperties("name", "script"))
|
||||
.then(() => {
|
||||
this._resetForm();
|
||||
this.router.transitionTo(
|
||||
"adminPlugins.discourse-automation.edit",
|
||||
automation.id
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
this.set("error", extractError(e));
|
||||
});
|
||||
}
|
||||
|
||||
_resetForm() {
|
||||
this.set("form", EmberObject.create({ name: null, script: null }));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import Controller from "@ember/controller";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class Automation extends Controller {
|
||||
@service router;
|
||||
|
||||
@computed("router.currentRouteName")
|
||||
get showNewAutomation() {
|
||||
return (
|
||||
this.router.currentRouteName === "adminPlugins.discourse-automation.index"
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
newAutomation() {
|
||||
this.router.transitionTo("adminPlugins.discourse-automation.new");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
export default {
|
||||
resource: "admin.adminPlugins",
|
||||
|
||||
path: "/plugins",
|
||||
|
||||
map() {
|
||||
this.route(
|
||||
"discourse-automation",
|
||||
|
||||
function () {
|
||||
this.route("new");
|
||||
this.route("edit", { path: "/:id" });
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
import { htmlSafe } from "@ember/template";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
|
||||
export default function formatEnabledAutomation(enabled, trigger) {
|
||||
if (enabled && trigger.id) {
|
||||
return htmlSafe(
|
||||
iconHTML("check", {
|
||||
class: "enabled-automation",
|
||||
title: "discourse_automation.models.automation.enabled.label",
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return htmlSafe(
|
||||
iconHTML("times", {
|
||||
class: "disabled-automation",
|
||||
title: "discourse_automation.models.automation.disabled.label",
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
|
||||
let _lastCheckedByHandlers = {};
|
||||
|
||||
function _handleLastCheckedByEvent(event) {
|
||||
ajax(`/append-last-checked-by/${event.currentTarget.postId}`, {
|
||||
type: "PUT",
|
||||
}).catch(popupAjaxError);
|
||||
}
|
||||
|
||||
function _cleanUp() {
|
||||
Object.values(_lastCheckedByHandlers || {}).forEach((handler) => {
|
||||
handler.removeEventListener("click", _handleLastCheckedByEvent);
|
||||
});
|
||||
|
||||
_lastCheckedByHandlers = {};
|
||||
}
|
||||
|
||||
function _initializeDiscourseAutomation(api) {
|
||||
_initializeGLobalUserNotices(api);
|
||||
|
||||
if (api.getCurrentUser()) {
|
||||
api.decorateCookedElement(_decorateCheckedButton, {
|
||||
id: "discourse-automation",
|
||||
});
|
||||
|
||||
api.cleanupStream(_cleanUp);
|
||||
}
|
||||
}
|
||||
|
||||
function _decorateCheckedButton(element, postDecorator) {
|
||||
if (!postDecorator) {
|
||||
return;
|
||||
}
|
||||
|
||||
const elems = element.querySelectorAll(".btn-checked");
|
||||
const postModel = postDecorator.getModel();
|
||||
|
||||
Array.from(elems).forEach((elem) => {
|
||||
const postId = postModel.id;
|
||||
elem.postId = postId;
|
||||
|
||||
if (_lastCheckedByHandlers[postId]) {
|
||||
_lastCheckedByHandlers[postId].removeEventListener(
|
||||
"click",
|
||||
_handleLastCheckedByEvent,
|
||||
false
|
||||
);
|
||||
delete _lastCheckedByHandlers[postId];
|
||||
}
|
||||
|
||||
_lastCheckedByHandlers[postId] = elem;
|
||||
elem.addEventListener("click", _handleLastCheckedByEvent, false);
|
||||
});
|
||||
}
|
||||
|
||||
function _initializeGLobalUserNotices(api) {
|
||||
const currentUser = api.getCurrentUser();
|
||||
|
||||
makeArray(currentUser?.global_notices).forEach((userGlobalNotice) => {
|
||||
api.addGlobalNotice("", userGlobalNotice.identifier, {
|
||||
html: userGlobalNotice.notice,
|
||||
level: userGlobalNotice.level,
|
||||
dismissable: true,
|
||||
dismissDuration: moment.duration(1, "week"),
|
||||
onDismiss() {
|
||||
ajax(`/user-global-notices/${userGlobalNotice.id}.json`, {
|
||||
type: "DELETE",
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "discourse-automation",
|
||||
|
||||
initialize() {
|
||||
withPluginApi("0.8.24", _initializeDiscourseAutomation);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
Fabricators are used to create fake data for testing purposes.
|
||||
The following fabricators are available in lib folder to allow
|
||||
styleguide to use them, and eventually to generate dummy data
|
||||
in a placeholder component. It should not be used for any other case.
|
||||
*/
|
||||
|
||||
import Automation from "../admin/models/discourse-automation-automation";
|
||||
import Field from "../admin/models/discourse-automation-field";
|
||||
|
||||
let sequence = 0;
|
||||
|
||||
function fieldFabricator(args = {}) {
|
||||
const template = args.template || {};
|
||||
template.accepts_placeholders = args.accepts_placeholders ?? true;
|
||||
template.accepted_contexts = args.accepted_contexts ?? [];
|
||||
template.name = args.name ?? "name";
|
||||
template.component = args.component ?? "boolean";
|
||||
template.value = args.value ?? false;
|
||||
template.is_required = args.is_required ?? false;
|
||||
template.extra = args.extra ?? {};
|
||||
return Field.create(template, {
|
||||
type: args.target ?? "script",
|
||||
name: "script_name",
|
||||
});
|
||||
}
|
||||
|
||||
function automationFabricator(args = {}) {
|
||||
const automation = new Automation();
|
||||
automation.id = args.id || sequence++;
|
||||
automation.trigger = {
|
||||
id: (sequence++).toString(),
|
||||
};
|
||||
automation.script = {
|
||||
id: (sequence++).toString(),
|
||||
};
|
||||
|
||||
return automation;
|
||||
}
|
||||
|
||||
export default {
|
||||
field: fieldFabricator,
|
||||
automation: automationFabricator,
|
||||
};
|
|
@ -0,0 +1,59 @@
|
|||
import { action } from "@ember/object";
|
||||
import { hash } from "rsvp";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import Field from "../admin/models/discourse-automation-field";
|
||||
|
||||
export default class AutomationEdit extends DiscourseRoute {
|
||||
controllerName = "admin-plugins-discourse-automation-edit";
|
||||
|
||||
model(params) {
|
||||
return hash({
|
||||
scriptables: this.store
|
||||
.findAll("discourse-automation-scriptable")
|
||||
.then((result) => result.content),
|
||||
triggerables: ajax(
|
||||
`/admin/plugins/discourse-automation/triggerables.json?automation_id=${params.id}`
|
||||
).then((result) => (result ? result.triggerables : [])),
|
||||
automation: this.store.find("discourse-automation-automation", params.id),
|
||||
});
|
||||
}
|
||||
|
||||
_fieldsForTarget(automation, target) {
|
||||
return (automation[target].templates || []).map((template) => {
|
||||
const jsonField = automation[target].fields.find(
|
||||
(f) => f.name === template.name && f.component === template.component
|
||||
);
|
||||
return Field.create(
|
||||
template,
|
||||
{
|
||||
name: automation[target].id,
|
||||
type: target,
|
||||
},
|
||||
jsonField
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
const automation = model.automation;
|
||||
controller.setProperties({
|
||||
model,
|
||||
error: null,
|
||||
automationForm: {
|
||||
name: automation.name,
|
||||
enabled: automation.enabled,
|
||||
trigger: automation.trigger?.id,
|
||||
script: automation.script?.id,
|
||||
fields: this._fieldsForTarget(automation, "script").concat(
|
||||
this._fieldsForTarget(automation, "trigger")
|
||||
),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
refreshRoute() {
|
||||
return this.refresh();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { action } from "@ember/object";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
|
||||
export default class AutomationIndex extends DiscourseRoute {
|
||||
controllerName = "admin-plugins-discourse-automation-index";
|
||||
|
||||
model() {
|
||||
return this.store.findAll("discourse-automation-automation");
|
||||
}
|
||||
|
||||
@action
|
||||
triggerRefresh() {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import { hash } from "rsvp";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
|
||||
export default class AutomationNew extends DiscourseRoute {
|
||||
controllerName = "admin-plugins-discourse-automation-new";
|
||||
|
||||
model() {
|
||||
return hash({
|
||||
scriptables: this.store.findAll("discourse-automation-scriptable"),
|
||||
automation: this.store.createRecord("discourse-automation-automation"),
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
|
||||
export default class Automation extends DiscourseRoute {
|
||||
controllerName = "admin-plugins-discourse-automation";
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
<ScrollTracker @name="discourse-automation-edit" />
|
||||
|
||||
<section class="discourse-automation-form edit">
|
||||
<form class="form-horizontal">
|
||||
<FormError @error={{error}} />
|
||||
|
||||
<section class="form-section edit">
|
||||
<div class="control-group">
|
||||
<label class="control-label">
|
||||
{{i18n "discourse_automation.models.automation.name.label"}}
|
||||
</label>
|
||||
|
||||
<div class="controls">
|
||||
<TextField
|
||||
@value={{automationForm.name}}
|
||||
@type="text"
|
||||
@autofocus="autofocus"
|
||||
@name="automation-name"
|
||||
class="input-large"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">
|
||||
{{i18n "discourse_automation.models.script.name.label"}}
|
||||
</label>
|
||||
|
||||
<div class="controls">
|
||||
<ComboBox
|
||||
@value={{automationForm.script}}
|
||||
@content={{model.scriptables}}
|
||||
@onChange={{action "onChangeScript"}}
|
||||
@options={{hash filterable=true}}
|
||||
class="scriptables"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="trigger-section form-section edit">
|
||||
<h2 class="title">
|
||||
{{i18n "discourse_automation.edit_automation.trigger_section.title"}}
|
||||
</h2>
|
||||
|
||||
<div class="control-group">
|
||||
{{#if model.automation.script.forced_triggerable}}
|
||||
<div class="alert alert-warning">
|
||||
{{i18n
|
||||
"discourse_automation.edit_automation.trigger_section.forced"
|
||||
}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<label class="control-label">
|
||||
{{i18n "discourse_automation.models.trigger.name.label"}}
|
||||
</label>
|
||||
|
||||
<div class="controls">
|
||||
<ComboBox
|
||||
@value={{automationForm.trigger}}
|
||||
@content={{model.triggerables}}
|
||||
@onChange={{action "onChangeTrigger"}}
|
||||
@options={{hash
|
||||
filterable=true
|
||||
none="discourse_automation.select_trigger"
|
||||
disabled=model.automation.script.forced_triggerable
|
||||
}}
|
||||
class="triggerables"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if automationForm.trigger}}
|
||||
{{#if model.automation.trigger.doc}}
|
||||
<div class="alert alert-info">
|
||||
<p>{{model.automation.trigger.doc}}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if
|
||||
(and
|
||||
model.automation.enabled
|
||||
model.automation.trigger.settings.manual_trigger
|
||||
)
|
||||
}}
|
||||
<div class="alert alert-info next-trigger">
|
||||
|
||||
{{#if nextPendingAutomationAtFormatted}}
|
||||
<p>
|
||||
{{i18n
|
||||
"discourse_automation.edit_automation.trigger_section.next_pending_automation"
|
||||
date=nextPendingAutomationAtFormatted
|
||||
}}
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
<DButton
|
||||
@label="discourse_automation.edit_automation.trigger_section.trigger_now"
|
||||
@isLoading={{isTriggeringAutomation}}
|
||||
@action={{action "onManualAutomationTrigger" model.automation.id}}
|
||||
class="btn-primary trigger-now-btn"
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#each triggerFields as |field|}}
|
||||
<AutomationField
|
||||
@automation={{automation}}
|
||||
@field={{field}}
|
||||
@saveAutomation={{action "saveAutomation" automation}}
|
||||
/>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</section>
|
||||
|
||||
{{#if automationForm.trigger}}
|
||||
{{#if scriptFields}}
|
||||
<section class="fields-section form-section edit">
|
||||
<h2 class="title">
|
||||
{{i18n "discourse_automation.edit_automation.fields_section.title"}}
|
||||
</h2>
|
||||
|
||||
{{#if model.automation.script.with_trigger_doc}}
|
||||
<div class="alert alert-info">
|
||||
<p>{{model.automation.script.with_trigger_doc}}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="control-group">
|
||||
{{#each scriptFields as |field|}}
|
||||
<AutomationField
|
||||
@automation={{automation}}
|
||||
@field={{field}}
|
||||
@saveAutomation={{action "saveAutomation" automation}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
{{#if automationForm.trigger}}
|
||||
<div class="control-group automation-enabled alert alert-warning">
|
||||
<span>{{i18n
|
||||
"discourse_automation.models.automation.enabled.label"
|
||||
}}</span>
|
||||
<Input
|
||||
@type="checkbox"
|
||||
@checked={{automationForm.enabled}}
|
||||
{{on
|
||||
"click"
|
||||
(action (mut automationForm.enabled) value="target.checked")
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="control-group">
|
||||
<DButton
|
||||
@isLoading={{isUpdatingAutomation}}
|
||||
@label="discourse_automation.update"
|
||||
@type="submit"
|
||||
@action={{action "saveAutomation" automation}}
|
||||
class="btn-primary update-automation"
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
</form>
|
||||
</section>
|
|
@ -0,0 +1,88 @@
|
|||
{{#if model.length}}
|
||||
<table class="automations">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{{i18n "discourse_automation.models.automation.name.label"}}</th>
|
||||
<th>{{i18n "discourse_automation.models.automation.trigger.label"}}</th>
|
||||
<th>{{i18n "discourse_automation.models.automation.script.label"}}</th>
|
||||
<th>{{i18n
|
||||
"discourse_automation.models.automation.last_updated_by.label"
|
||||
}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each model as |automation|}}
|
||||
<tr>
|
||||
{{#if automation.script.not_found}}
|
||||
<td colspan="5" class="alert alert-danger">
|
||||
{{i18n
|
||||
"discourse_automation.scriptables.not_found"
|
||||
script=automation.script.id
|
||||
automation=automation.name
|
||||
}}
|
||||
</td>
|
||||
{{else if automation.trigger.not_found}}
|
||||
<td colspan="5" class="alert alert-danger">
|
||||
{{i18n
|
||||
"discourse_automation.triggerables.not_found"
|
||||
trigger=automation.trigger.id
|
||||
automation=automation.name
|
||||
}}
|
||||
</td>
|
||||
{{else}}
|
||||
<td
|
||||
role="button"
|
||||
{{on "click" (fn this.editAutomation automation)}}
|
||||
>{{format-enabled-automation
|
||||
automation.enabled
|
||||
automation.trigger
|
||||
}}</td>
|
||||
<td
|
||||
tabindex="0"
|
||||
role="button"
|
||||
{{on "keypress" (fn this.editAutomation automation)}}
|
||||
{{on "click" (fn this.editAutomation automation)}}
|
||||
>{{automation.name}}</td>
|
||||
<td
|
||||
role="button"
|
||||
{{on "click" (fn this.editAutomation automation)}}
|
||||
>{{if automation.trigger.id automation.trigger.name "-"}}</td>
|
||||
<td
|
||||
role="button"
|
||||
{{on "click" (fn this.editAutomation automation)}}
|
||||
>{{automation.script.name}} (v{{automation.script.version}})</td>
|
||||
<td>
|
||||
<a
|
||||
href={{automation.last_updated_by.userPath}}
|
||||
data-user-card={{automation.last_updated_by.username}}
|
||||
>
|
||||
{{avatar automation.last_updated_by imageSize="small"}}
|
||||
</a>
|
||||
{{format-date automation.updated_at leaveAgo="true"}}
|
||||
</td>
|
||||
{{/if}}
|
||||
|
||||
<td>
|
||||
<DButton
|
||||
@icon="trash-alt"
|
||||
@action={{action "destroyAutomation" automation}}
|
||||
class="btn-danger"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<div class="alert alert-info">
|
||||
<p>{{i18n "discourse_automation.no_automation_yet"}}</p>
|
||||
<DButton
|
||||
@label="discourse_automation.create"
|
||||
@icon="plus"
|
||||
@action={{action "newAutomation"}}
|
||||
class="btn-primary"
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -0,0 +1,53 @@
|
|||
<section class="discourse-automation-form new">
|
||||
<form class="form-horizontal">
|
||||
<FormError @error={{error}} />
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">
|
||||
{{i18n "discourse_automation.models.automation.name.label"}}
|
||||
</label>
|
||||
|
||||
<div class="controls">
|
||||
<TextField
|
||||
@value={{form.name}}
|
||||
@type="text"
|
||||
@autofocus="autofocus"
|
||||
@name="automation-name"
|
||||
class="input-large"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">
|
||||
{{i18n "discourse_automation.models.automation.script.label"}}
|
||||
</label>
|
||||
|
||||
<div class="controls">
|
||||
<DropdownSelectBox
|
||||
@value={{form.script}}
|
||||
@content={{model.scriptables.content}}
|
||||
@onChange={{action (mut form.script)}}
|
||||
@options={{hash
|
||||
showCaret=true
|
||||
filterable=true
|
||||
none="discourse_automation.select_script"
|
||||
}}
|
||||
class="scriptables"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
|
||||
<div class="controls">
|
||||
<DButton
|
||||
@icon="plus"
|
||||
@label="discourse_automation.create"
|
||||
@action={{action "saveAutomation" model.automation}}
|
||||
class="btn-primary create-automation"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
|
@ -0,0 +1,53 @@
|
|||
<LinkTo
|
||||
@route="adminPlugins.discourse-automation"
|
||||
class="discourse-automation-title"
|
||||
>
|
||||
<svg
|
||||
width="32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 41.3 41.3"
|
||||
><title>Asset 4</title><path
|
||||
d="M20.42,40.21a2.52,2.52,0,0,1-1.78-.78l-.08-.07-1.94-2a2.48,2.48,0,0,1-.72-1.23l-1.15.64a2.51,2.51,0,0,1-1.25.33,2.59,2.59,0,0,1-1.86-.79l-.73-.76a1.79,1.79,0,0,1-.24-.21l-.84-.88a1.31,1.31,0,0,1-.27-.24l-4.67-4.9A2.5,2.5,0,0,1,4.5,26.3l.92-1.47-.48-.14a2.3,2.3,0,0,1-.9-.47l-.17-.14L1.72,21.93a2.09,2.09,0,0,1-.14-.17A2.55,2.55,0,0,1,1,20.1l.08-3.85a2.57,2.57,0,0,1,2-2.44l2.73-.65L6,12.78,4.59,10.33a2.57,2.57,0,0,1,.47-3.11L7.84,4.56a2.56,2.56,0,0,1,1.77-.72A2.74,2.74,0,0,1,11,4.23l2.46,1.52.3-.11.78-2.77A2.6,2.6,0,0,1,17,1l3.88.08.14,0a1,1,0,0,1,.24,0,2.65,2.65,0,0,1,.51.15h0a1.11,1.11,0,0,1,.31.15l.28.2.08.06a.52.52,0,0,1,.11.1l2.08,2.08a2.57,2.57,0,0,1,.73,1.25l.1.45,1.56-.87a2.5,2.5,0,0,1,1.24-.33,2.62,2.62,0,0,1,1.87.8l.55.57.21.19L32,7a1.43,1.43,0,0,1,.29.24l4.63,4.86a2.56,2.56,0,0,1,.43,3.12l-.84,1.36a2.66,2.66,0,0,1,.86.5,1.27,1.27,0,0,1,.12.1l.09.1,2,2a.64.64,0,0,1,.1.13,2.56,2.56,0,0,1,.69,1.77L40.21,25a2.56,2.56,0,0,1-2,2.45l-2.62.61c-.07.18-.14.35-.22.53l1.3,2.33A2.55,2.55,0,0,1,36.24,34l-2.78,2.67a2.6,2.6,0,0,1-1.78.71h0A2.61,2.61,0,0,1,30.33,37l-2.2-1.36c-.21.08-.42.17-.63.24l-.71,2.52a2.57,2.57,0,0,1-2.47,1.87Z"
|
||||
></path><path
|
||||
d="M17,2l3.85.08.13,0h0l.07,0a1.27,1.27,0,0,1,.38.11l.1,0,.08.06.16.12.11.08,2,2h0a1.54,1.54,0,0,1,.52.83l.41,1.74,2.72-1.52a1.61,1.61,0,0,1,.75-.2h.05a1.57,1.57,0,0,1,1.1.48l.65.69h0a.25.25,0,0,1,.1.08l1.14,1.19a.32.32,0,0,1,.17.12l4.64,4.86a1.58,1.58,0,0,1,.3,1.91l-1.53,2.48,1.16.33a1.54,1.54,0,0,1,.64.38h0l0,0h0L37.82,19l0,0,1,1h0a1.55,1.55,0,0,1,.51,1.17L39.21,25A1.55,1.55,0,0,1,38,26.48l-3.13.74a12.5,12.5,0,0,1-.61,1.41l1.56,2.79a1.56,1.56,0,0,1-.28,1.9L32.77,36a1.56,1.56,0,0,1-1.09.44,1.53,1.53,0,0,1-.82-.24L28.2,34.54a13.33,13.33,0,0,1-1.52.6l-.85,3a1.57,1.57,0,0,1-1.51,1.14h0l-3.85-.09a1.57,1.57,0,0,1-1.17-.57h0L17.32,36.7a1.47,1.47,0,0,1-.45-.77l-.3-1.27L14.26,36a1.49,1.49,0,0,1-.76.2,1.61,1.61,0,0,1-1.14-.48l-.82-.86a.49.49,0,0,1-.14-.11l-1-1a.53.53,0,0,1-.17-.12L5.61,28.68a1.49,1.49,0,0,1-.26-1.85l1.58-2.55,0-.07-1.69-.48a1.55,1.55,0,0,1-.63-.37h0l-.1-.1h0L3.31,22.1h0l-.86-.87h0A1.58,1.58,0,0,1,2,20.12l.08-3.85a1.58,1.58,0,0,1,1.21-1.49L6.52,14a13.1,13.1,0,0,1,.58-1.27L5.47,9.84a1.56,1.56,0,0,1,.28-1.9L8.53,5.28a1.55,1.55,0,0,1,1.08-.43h0a1.48,1.48,0,0,1,.79.23l2.9,1.79c.39-.17.79-.32,1.21-.46l.92-3.27A1.6,1.6,0,0,1,17,2m0-2h0a3.62,3.62,0,0,0-3.46,2.6l-.49,1.74-1.56-1a3.47,3.47,0,0,0-1.8-.53H9.61a3.58,3.58,0,0,0-2.46,1L4.37,6.5a3.55,3.55,0,0,0-.65,4.32l.9,1.59-1.79.42a3.57,3.57,0,0,0-2.74,3.4L0,20.08a3.55,3.55,0,0,0,.77,2.27,2.09,2.09,0,0,0,.24.28l.87.87h0L3,24.67h0l.11.11a2.48,2.48,0,0,0,.3.25,4.53,4.53,0,0,0,.46.3l-.27.44a3.47,3.47,0,0,0,.52,4.28L8.83,35a2.67,2.67,0,0,0,.34.29l.78.82a2,2,0,0,0,.3.27l.67.7a3.59,3.59,0,0,0,2.58,1.1,3.67,3.67,0,0,0,1.74-.45l.21-.12a3.64,3.64,0,0,0,.48.56l1.92,1.92.11.11a3.55,3.55,0,0,0,2.43,1l3.85.09h.08a3.58,3.58,0,0,0,3.43-2.6l.51-1.78,1.55,1a3.55,3.55,0,0,0,4.34-.45l2.78-2.67a3.57,3.57,0,0,0,.65-4.32l-.9-1.59,1.78-.42A3.55,3.55,0,0,0,41.21,25l.09-3.84a3.55,3.55,0,0,0-.92-2.45l-.17-.19-1-.95,0,0h0l-1-1.06-.13-.12-.19-.16.3-.48a3.58,3.58,0,0,0-.57-4.35L33,6.56a2.3,2.3,0,0,0-.35-.31l-1-1A2.53,2.53,0,0,0,31.39,5l-.5-.52a3.52,3.52,0,0,0-2.49-1.1h-.1a3.53,3.53,0,0,0-1.73.46l-.49.27a3.87,3.87,0,0,0-.69-.93.46.46,0,0,0-.07-.07l-2-2a1.59,1.59,0,0,0-.24-.2L23,.83,22.75.66l0,0a2,2,0,0,0-.62-.31h0a3.83,3.83,0,0,0-.58-.16A1.59,1.59,0,0,0,21.13.1h-.22L17.06,0Z"
|
||||
></path><path
|
||||
d="M8.78,24.14l1.93,2.21a13.21,13.21,0,0,0,.83,2.11L9.85,31.19A1.15,1.15,0,0,0,10,32.6l2.66,2.78a1.15,1.15,0,0,0,1.4.21L17,34a11.35,11.35,0,0,0,1.6.69l.77,3.27a1.15,1.15,0,0,0,1.1.89l3.85.09A1.16,1.16,0,0,0,25.43,38l.91-3.23a12.77,12.77,0,0,0,1.89-.74l2.85,1.76a1.13,1.13,0,0,0,1.4-.15L35.26,33a1.15,1.15,0,0,0,.21-1.4l-1.67-3a12.63,12.63,0,0,0,.77-1.77l3.34-.79A1.15,1.15,0,0,0,38.8,25l.09-3.85A1.16,1.16,0,0,0,38,20l-3.4-1a11.44,11.44,0,0,0-.52-1.35l2-3.2a1.18,1.18,0,0,0-.25-1.41L33.19,10.3a1.18,1.18,0,0,0-.81-.36.85.85,0,0,0-.48.15l-3.17,1.77a14.55,14.55,0,0,0-1.8-.81L24.67,9.76c-.12-.52-.23-1.5-.75-1.52L21.15,6.56A1.16,1.16,0,0,0,20,7.4l-1,3.48a11.87,11.87,0,0,0-1.57.61L14.37,9.58a1.23,1.23,0,0,0-.58-.18,1.2,1.2,0,0,0-.83.33l-2.78,2.66a1.17,1.17,0,0,0-.21,1.4l1.74,3.1A11.92,11.92,0,0,0,11,18.52l-3.45.81a1.16,1.16,0,0,0-.89,1.1l.63,2.19C7.26,23.15,9.4,22.1,8.78,24.14Zm14.1-8.74a7.33,7.33,0,1,1-7.48,7.16A7.32,7.32,0,0,1,22.88,15.4Z"
|
||||
fill="#00aeef"
|
||||
></path><path
|
||||
d="M8.54,23.9l1.2,1.48a12.89,12.89,0,0,0,.84,2.11l-1.7,2.74A1.16,1.16,0,0,0,9,31.63l2.66,2.78a1.14,1.14,0,0,0,1.4.21L16,33a12.46,12.46,0,0,0,1.59.68l.77,3.27a1.14,1.14,0,0,0,1.1.89l3.85.09a1.17,1.17,0,0,0,1.14-.84l.91-3.24a11.3,11.3,0,0,0,1.88-.74l2.85,1.76a1.16,1.16,0,0,0,1.41-.14l2.78-2.67a1.15,1.15,0,0,0,.21-1.39l-1.67-3a12.74,12.74,0,0,0,.76-1.77l3.34-.79A1.14,1.14,0,0,0,37.83,24l.09-3.85A1.16,1.16,0,0,0,37.08,19l-3.41-1a11,11,0,0,0-.51-1.35l1.93-3.15a1.15,1.15,0,0,0-.15-1.41L32.28,9.38A1.17,1.17,0,0,0,31.47,9a1,1,0,0,0-.54.1L27.76,10.9a11.24,11.24,0,0,0-1.8-.81l-2.28.18c-1.46,2.2-.07-2.12-.59-2.13L20.18,5.59a1.16,1.16,0,0,0-1.13.84l-1,3.48a12,12,0,0,0-1.58.61L13.4,8.61a1.15,1.15,0,0,0-1.4.15L9.22,11.42A1.14,1.14,0,0,0,9,12.82l1.74,3.11A11.85,11.85,0,0,0,10,17.55l-3.44.81a1.14,1.14,0,0,0-.89,1.1l1.07,2.71C6.74,22.7,9.89,23.35,8.54,23.9Zm13.38-9.47a7.33,7.33,0,1,1-7.49,7.17A7.33,7.33,0,0,1,21.92,14.43Z"
|
||||
fill="#00a94f"
|
||||
></path><path
|
||||
d="M3.25,21.27l3.31.93a12.89,12.89,0,0,0,.84,2.11L5.7,27.05a1.09,1.09,0,0,0,.21,1.35l2.66,2.78a1.08,1.08,0,0,0,1.34.26l2.93-1.63a12.46,12.46,0,0,0,1.59.68l.77.31a1.16,1.16,0,0,0,1.1.89l3.85,3.05a1.17,1.17,0,0,0,1.14-.84l.91-3.24a11.3,11.3,0,0,0,1.88-.74l2.85,1.77a1.18,1.18,0,0,0,1.41-.15l2.78-2.67a1.15,1.15,0,0,0,.21-1.39l-1.67-3a12.74,12.74,0,0,0,.76-1.77l3.34-.79a1.14,1.14,0,0,0,.89-1.09l-2.88-2.37a1.16,1.16,0,0,0-.84-1.14l-.44-2.44A11,11,0,0,0,30,13.54l2-3.2a1.16,1.16,0,0,0-.15-1.4L29.15,6.15a1.15,1.15,0,0,0-.81-.35,1.12,1.12,0,0,0-.59.14L24.58,7.72a11.24,11.24,0,0,0-1.8-.81L22,3.39a1.14,1.14,0,0,0-1.1-.89L17,2.41a1.16,1.16,0,0,0-1.13.84l-1,3.48a12,12,0,0,0-1.58.61L10.22,5.43a1.15,1.15,0,0,0-1.4.15L6,8.24a1.14,1.14,0,0,0-.21,1.4l1.74,3.11a11.85,11.85,0,0,0-.74,1.62l-3.44.81a1.14,1.14,0,0,0-.89,1.1l-.09,3.85A1.17,1.17,0,0,0,3.25,21.27Zm15.49-10a7.33,7.33,0,1,1-7.49,7.17A7.33,7.33,0,0,1,18.74,11.25Z"
|
||||
fill="#d0232b"
|
||||
></path><path
|
||||
d="M24.39,20.28A4.71,4.71,0,0,1,16.8,24a4.71,4.71,0,1,0,6.61-6.61A4.74,4.74,0,0,1,24.39,20.28Z"
|
||||
fill="#00a94f"
|
||||
></path><path
|
||||
d="M4,22,7.32,23a12.12,12.12,0,0,0,.83,2.12L6.46,27.8a1.16,1.16,0,0,0,.22,1.41L9.34,32a1.05,1.05,0,0,0,1.33.21l2.92-1.64a11.35,11.35,0,0,0,1.6.69L17.44,33a1.16,1.16,0,0,0,1.1.89l2.36,1.56A1.14,1.14,0,0,0,22,34.65L23,31.42a13.35,13.35,0,0,0,1.88-.74l2.85,1.76a1.15,1.15,0,0,0,1.41-.15l2.78-2.66a1.15,1.15,0,0,0,.21-1.4l-1.67-3a12.63,12.63,0,0,0,.77-1.77l3.34-.79a1.15,1.15,0,0,0,.89-1.1L34,19.23a1.15,1.15,0,0,0-.84-1.14l-1.92-2.45a12,12,0,0,0-.52-1.34l2-3.2a1.16,1.16,0,0,0-.14-1.41L29.9,6.91a1.15,1.15,0,0,0-.81-.36,1.26,1.26,0,0,0-.59.15L25.34,8.47a14.55,14.55,0,0,0-1.8-.81l-.83-3.52a1.15,1.15,0,0,0-1.1-.89l-3.85-.08A1.15,1.15,0,0,0,16.62,4l-1,3.48a12.58,12.58,0,0,0-1.58.61L11,6.19A1.13,1.13,0,0,0,10.4,6a1.23,1.23,0,0,0-.83.32L6.79,9a1.15,1.15,0,0,0-.21,1.4l1.74,3.1a11.4,11.4,0,0,0-.73,1.63l-3.45.81A1.15,1.15,0,0,0,3.25,17l-.08,3.85A1.15,1.15,0,0,0,4,22ZM19.49,12A7.33,7.33,0,1,1,12,19.17,7.33,7.33,0,0,1,19.49,12Z"
|
||||
fill="#f15d22"
|
||||
></path><circle cx="20.64" cy="20.86" r="8.33" fill="#fff"></circle><path
|
||||
d="M36,17.92l-3.41-1q-.23-.69-.51-1.35l2-3.2A1.18,1.18,0,0,0,33.88,11L31.22,8.22a1.13,1.13,0,0,0-.81-.35,1.1,1.1,0,0,0-.59.14L26.65,9.79A12.73,12.73,0,0,0,24.85,9L24,5.46a1.14,1.14,0,0,0-1.1-.89l-3.85-.09a1.15,1.15,0,0,0-1.13.84L17,8.8a11.87,11.87,0,0,0-1.57.61L12.29,7.5a1.15,1.15,0,0,0-1.4.15L8.1,10.31a1.17,1.17,0,0,0-.21,1.4l1.74,3.1a14,14,0,0,0-.73,1.63l-3.44.81a1.14,1.14,0,0,0-.89,1.1L4.48,22.2a1.16,1.16,0,0,0,.84,1.13l3.31.94a12.89,12.89,0,0,0,.84,2.11l-1.7,2.73a1.16,1.16,0,0,0,.15,1.41l2.66,2.78a1.15,1.15,0,0,0,1.4.21l2.92-1.64a11.87,11.87,0,0,0,1.6.69l.77,3.27a1.14,1.14,0,0,0,1.1.89l3.85.09A1.16,1.16,0,0,0,23.35,36l.92-3.24A11.3,11.3,0,0,0,26.15,32L29,33.75a1.15,1.15,0,0,0,1.4-.14l2.78-2.67a1.14,1.14,0,0,0,.21-1.4l-1.67-3a12.14,12.14,0,0,0,.77-1.77L35.83,24a1.14,1.14,0,0,0,.89-1.1l.09-3.85A1.16,1.16,0,0,0,36,17.92ZM25.72,23.64l-3.84,1.78-2.38.87-3-1.46-1.35-2.51-.22-3.75,2.65-2.65h3.56l3.2,1.55,2.2,2.63Z"
|
||||
fill="#fff9ae"
|
||||
></path><path
|
||||
d="M20.45,26.15a5.92,5.92,0,1,1,5.93-5.92A5.93,5.93,0,0,1,20.45,26.15Zm0-10.57a4.65,4.65,0,1,0,4.65,4.65A4.65,4.65,0,0,0,20.45,15.58Z"
|
||||
stroke="#000"
|
||||
stroke-miterlimit="10"
|
||||
></path></svg>
|
||||
|
||||
<h1 class="title">
|
||||
{{i18n "discourse_automation.title"}}
|
||||
</h1>
|
||||
|
||||
{{#if showNewAutomation}}
|
||||
<DButton
|
||||
@label="discourse_automation.create"
|
||||
@icon="plus"
|
||||
@action={{action "newAutomation"}}
|
||||
class="new-automation"
|
||||
/>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
|
||||
<hr />
|
||||
|
||||
{{outlet}}
|
|
@ -0,0 +1,5 @@
|
|||
{{#if error}}
|
||||
<div class="alert alert-error form-errors">
|
||||
{{html-safe error}}
|
||||
</div>
|
||||
{{/if}}
|
|
@ -0,0 +1,12 @@
|
|||
<div class="control-group">
|
||||
<label class="control-label">
|
||||
{{i18n "discourse_automation.triggerables.topic.topic_id.label"}}
|
||||
</label>
|
||||
|
||||
<div class="controls">
|
||||
<Input
|
||||
@value={{metadata.topic_id}}
|
||||
{{on "input" (action (mut metadata.topic_id) value="target.value")}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,313 @@
|
|||
.discourse-automation {
|
||||
.automations {
|
||||
.relative-date {
|
||||
font-size: $font-down-1;
|
||||
}
|
||||
|
||||
td[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discourse-automation-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
|
||||
.title {
|
||||
margin: 0 0 0 0.5em;
|
||||
font-weight: 700;
|
||||
font-size: $font-up-3;
|
||||
}
|
||||
|
||||
.new-automation {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.enabled-automation {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.disabled-automation {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.discourse-automation-form {
|
||||
.scriptables,
|
||||
.triggerables {
|
||||
.select-kit-body {
|
||||
max-height: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 1em;
|
||||
background: var(--primary-very-low);
|
||||
border-left-style: solid;
|
||||
border-left-width: 5px;
|
||||
|
||||
&.alert-info {
|
||||
border-left-color: var(--tertiary-low);
|
||||
}
|
||||
|
||||
&.alert-warning {
|
||||
border-left-color: var(--highlight);
|
||||
background: var(--highlight-low);
|
||||
}
|
||||
|
||||
&.alert-error {
|
||||
border-left-color: var(--danger);
|
||||
background: var(--danger-low);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-horizontal {
|
||||
.control-label {
|
||||
margin: 0.25em 0.25em 0.25em 0;
|
||||
text-align: left;
|
||||
width: 180px;
|
||||
line-height: 27px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-left: 185px;
|
||||
}
|
||||
|
||||
.boolean-field {
|
||||
.controls {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.automation-presentation {
|
||||
border-left: 5px solid var(--primary-very-low);
|
||||
padding: 0.5em;
|
||||
|
||||
.automation-name {
|
||||
font-size: $font-up-2;
|
||||
}
|
||||
|
||||
.automation-doc {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.automation-presentation + .control-group {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.scripts-list {
|
||||
width: 210px;
|
||||
|
||||
.select-kit-header {
|
||||
background: var(--secondary);
|
||||
border: 1px solid var(--primary-medium);
|
||||
border-radius: 0;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.script-doc {
|
||||
background: var(--primary-very-low);
|
||||
border-left: 3px solid var(--primary-low);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
.title {
|
||||
padding-bottom: 0.5em;
|
||||
margin-bottom: 1em;
|
||||
border-bottom: 1px solid var(--primary-low);
|
||||
}
|
||||
|
||||
.input-large,
|
||||
.select-kit,
|
||||
.d-date-time-input {
|
||||
width: 300px;
|
||||
|
||||
.select-kit,
|
||||
input[type="text"] {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.period-field {
|
||||
line-height: 34px;
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.select-kit {
|
||||
width: 150px;
|
||||
margin-left: 6px;
|
||||
|
||||
.select-kit-header {
|
||||
height: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100px;
|
||||
max-height: 34px;
|
||||
margin: 0 0 0 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.d-date-time-input {
|
||||
border-color: var(--primary-medium);
|
||||
}
|
||||
}
|
||||
|
||||
.control-group {
|
||||
.control-description {
|
||||
color: var(--primary-medium);
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0.25em 0;
|
||||
}
|
||||
|
||||
&.automation-enabled {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 1em;
|
||||
|
||||
.ember-checkbox {
|
||||
margin: 0 0 0 1em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.control-label.disabled {
|
||||
color: var(--danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.controls-row {
|
||||
display: flex;
|
||||
|
||||
input {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.field-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.text-field {
|
||||
.ember-text-field {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.message-field {
|
||||
textarea {
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.next-trigger {
|
||||
padding: 1em;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0 0 0.5em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.pms-field {
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
&.header {
|
||||
justify-content: space-between;
|
||||
margin: 1em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.pm-field {
|
||||
margin-bottom: 1em;
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.control-label {
|
||||
width: 100%;
|
||||
}
|
||||
.controls {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.d-editor,
|
||||
.d-editor-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
|
||||
.d-editor-input {
|
||||
min-height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.d-editor-textarea-wrapper {
|
||||
box-sizing: border-box;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.pm-field:not(:last-child) {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.pm-title {
|
||||
width: 100%;
|
||||
}
|
||||
.pm-textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.no-pm {
|
||||
border: 1px solid var(--tertiary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1em;
|
||||
margin: 1em 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.placeholders-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.placeholder-item {
|
||||
border-radius: 3px;
|
||||
font-size: $font-down-2;
|
||||
margin: 0.5em 0.5em 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
details[open] > summary:first-of-type ~ .btn-checked,
|
||||
details.open > summary:first-of-type ~ .btn-checked {
|
||||
display: inline-flex;
|
||||
}
|
|
@ -0,0 +1,368 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
ar:
|
||||
js:
|
||||
discourse_automation:
|
||||
title: الأتمتة
|
||||
create: إنشاء
|
||||
update: تحديث
|
||||
select_script: حدِّد برنامجًا نصيًا
|
||||
select_trigger: حدِّد مشغلًا
|
||||
confirm_automation_reset: سيؤدي هذا الإجراء إلى إعادة تعيين البرنامج النصي وتشغيل الخيارات، وسيتم حفظ الحالة الجديدة، هل تريد المتابعة؟
|
||||
confirm_automation_trigger: سيؤدي هذا الإجراء إلى تشغيل الأتمتة، هل تريد المتابعة؟
|
||||
no_automation_yet: لم تُنشئ أي أتمتة حتى الآن.
|
||||
edit_automation:
|
||||
trigger_section:
|
||||
forced: يتم فرض هذا المشغِّل بواسطة البرنامج النصي.
|
||||
next_pending_automation: "سيتم تشغيل الأتمتة التالية في: %{date}"
|
||||
trigger_now: "التشغيل الآن"
|
||||
title: متى/ماذا...
|
||||
fields_section:
|
||||
title: خيارات البرنامج النصي
|
||||
destroy_automation:
|
||||
confirm: "هل تريد بالتأكيد حذف `%{name}`؟"
|
||||
fields:
|
||||
key_value:
|
||||
label:
|
||||
zero: تعديل التكوين (%{count})
|
||||
one: تعديل التكوين (%{count})
|
||||
two: تعديل التكوين (%{count})
|
||||
few: تعديل التكوين (%{count})
|
||||
many: تعديل التكوين (%{count})
|
||||
other: تعديل التكوين (%{count})
|
||||
user:
|
||||
label: المستخدم
|
||||
pm:
|
||||
title:
|
||||
label: العنوان
|
||||
raw:
|
||||
label: النص
|
||||
pms:
|
||||
confirm_remove_pm: "هل تريد بالتأكيد إزالة هذه الرسالة الشخصية؟"
|
||||
placeholder_title: عنوان الرسالة الشخصية
|
||||
add_pm: إضافة رسالة شخصية
|
||||
no_pm_created: لم تُنشئ أي رسالة شخصية بعد. سيتم إرسال الرسائل الشخصية بعد تشغيل الأتمتة.
|
||||
title:
|
||||
label: العنوان
|
||||
raw:
|
||||
label: النص
|
||||
delay:
|
||||
label: التأخير (بالدقائق)
|
||||
prefers_encrypt:
|
||||
label: يشفِّر الرسائل الشخصية إذا كانت متاحة
|
||||
group:
|
||||
label: المجموعة
|
||||
text:
|
||||
label: النص
|
||||
triggerables:
|
||||
not_found: تعذَّر العثور على المشغِّل `%{trigger}` للأتمتة `%{automation}`، تأكَّد من تثبيت المكوِّن الإضافي ذي الصلة
|
||||
user_badge_granted:
|
||||
fields:
|
||||
badge:
|
||||
label: شارة
|
||||
only_first_grant:
|
||||
label: على المنحة الأولى فقط
|
||||
stalled_topic:
|
||||
durations:
|
||||
PT1H: "ساعة واحدة"
|
||||
P1D: "يوم واحد"
|
||||
P1W: "أسبوع واحد"
|
||||
P2W: "أسبوعان"
|
||||
P1M: "شهر واحد"
|
||||
P3M: "ثلاثة أشهر"
|
||||
P6M: "ستة أشهر"
|
||||
P1Y: "عام واحد"
|
||||
fields:
|
||||
categories:
|
||||
label: يقتصر على الفئات
|
||||
tags:
|
||||
label: يقتصر على الوسوم
|
||||
stalled_after:
|
||||
label: توقفت بعد
|
||||
recurring:
|
||||
every: كل
|
||||
frequencies:
|
||||
minute: دقيقة
|
||||
hour: ساعة
|
||||
day: يوم
|
||||
weekday: يوم من أيام الأسبوع
|
||||
week: أسبوع
|
||||
month: شهر
|
||||
year: عام
|
||||
fields:
|
||||
recurrence:
|
||||
label: التكرار
|
||||
start_date:
|
||||
label: تاريخ البدء
|
||||
stalled_wiki:
|
||||
durations:
|
||||
PT1H: "ساعة واحدة"
|
||||
P1D: "يوم واحد"
|
||||
P1W: "أسبوع واحد"
|
||||
P2W: "أسبوعان"
|
||||
P1M: "شهر واحد"
|
||||
P3M: "ثلاثة أشهر"
|
||||
P6M: "ستة أشهر"
|
||||
P1Y: "عام واحد"
|
||||
fields:
|
||||
restricted_category:
|
||||
label: مقيَّد إلى الفئة
|
||||
stalled_after:
|
||||
label: تأخير المشغِّل
|
||||
description: يحدِّد التأخير بين تعديل Wiki الأخير ومشغِّل الأتمتة
|
||||
retriggered_after:
|
||||
label: التأخير في إعادة التشغيل
|
||||
description: يحدِّد التأخير بين المشغِّل الأول والمشغِّل التالي، إذا لم يتم تعديل Wiki بعد أول مشغِّل
|
||||
user_added_to_group:
|
||||
fields:
|
||||
joined_group:
|
||||
label: مجموعة متتبَّعة
|
||||
user_removed_from_group:
|
||||
fields:
|
||||
left_group:
|
||||
label: مجموعة متتبَّعة
|
||||
user_promoted:
|
||||
fields:
|
||||
restricted_group:
|
||||
label: التقييد إلى المجموعة
|
||||
trust_level_transition:
|
||||
label: نقل مستوى الثقة
|
||||
trust_levels:
|
||||
ALL: "كل مستويات الثقة"
|
||||
TL01: "مستوى الثقة 0 إلى المستوى الثقة 1"
|
||||
TL12: "مستوى الثقة 1 إلى المستوى الثقة 2"
|
||||
TL23: "مستوى الثقة 2 إلى المستوى الثقة 3"
|
||||
TL34: "مستوى الثقة 3 إلى المستوى الثقة 4"
|
||||
point_in_time:
|
||||
fields:
|
||||
execute_at:
|
||||
label: التنفيذ في
|
||||
topic:
|
||||
fields:
|
||||
restricted_topic:
|
||||
label: معرِّف الموضوع
|
||||
post_created_edited:
|
||||
fields:
|
||||
action_type:
|
||||
label: نوع الإجراء
|
||||
description: "اختياري، تقييد التشغيل إلى الأحداث التي تم إنشاؤها أو تحريرها فقط"
|
||||
valid_trust_levels:
|
||||
label: مستويات الثقة الصالحة
|
||||
description: سيتم التشغيل فقط إذا تم إنشاء المنشور بواسطة مستخدم في مستويات الثقة هذه، ويتم تعيينه افتراضيًا على أي مستوى ثقة
|
||||
restricted_category:
|
||||
label: الفئة
|
||||
description: اختياري، لن يتم تشغيله إلا إذا كان موضوع المشاركة ضمن هذه الفئة
|
||||
restricted_group:
|
||||
label: مجموعة
|
||||
description: اختياري، لن يتم تشغيله إلا إذا كان موضوع المشاركة عبارة عن رسالة خاصة في البريد الوارد لهذه المجموعة
|
||||
ignore_group_members:
|
||||
label: تجاهل أعضاء المجموعة
|
||||
description: تخطي المشغِّل إذا كان المرسل عضوًا في المجموعة المحدَّدة أعلاه
|
||||
ignore_automated:
|
||||
label: تجاهل الرسائل الآلية
|
||||
description: تخطي المشغِّل إذا كان لدى المرسل بريد إلكتروني غير صحيح أو كان من مصدرٍ آلي. لا ينطبق ذلك إلا على المنشورات التي تم إنشاؤها عبر البريد الإلكتروني
|
||||
first_post_only:
|
||||
label: المنشور الأول فقط
|
||||
description: لن يتم تشغيله إلا إذا كان المنشور هو أول منشور أنشأه المستخدم
|
||||
first_topic_only:
|
||||
label: الموضوع الأول فقط
|
||||
description: لن يتم تشغيله إلا إذا كان الموضوع هو أول موضوع أنشأه المستخدم
|
||||
created: تم إنشاؤها
|
||||
edited: تم تحريرها
|
||||
category_created_edited:
|
||||
fields:
|
||||
restricted_category:
|
||||
label: الفئة الرئيسية
|
||||
description: اختياري، يسمح بتقييد تنفيذ المشغِّل لهذه الفئة
|
||||
pm_created:
|
||||
fields:
|
||||
restricted_user:
|
||||
label: المستخدمون
|
||||
description: لن يتم تشغيله إلا للرسائل الخاصة المُرسَلة إلى هذا المستخدم
|
||||
restricted_group:
|
||||
label: مجموعة
|
||||
description: لن يتم تشغيله إلا للرسائل الخاصة المُرسَلة إلى هذه المجموعة
|
||||
ignore_staff:
|
||||
label: تجاهل فريق العمل
|
||||
description: تخطي المشغِّل إذا كان المُرسل مستخدمًا من فريق العمل
|
||||
ignore_group_members:
|
||||
label: تجاهل أعضاء المجموعة
|
||||
description: تخطي المشغِّل إذا كان المرسل عضوًا في المجموعة المحدَّدة أعلاه
|
||||
ignore_automated:
|
||||
label: تجاهل الرسائل الآلية
|
||||
description: تخطي المشغِّل إذا كان لدى المرسل بريد إلكتروني غير صحيح أو كان من مصدرٍ آلي. لا ينطبق ذلك إلا على الرسائل الخاصة التي تم إنشاؤها عبر البريد الإلكتروني
|
||||
valid_trust_levels:
|
||||
label: مستويات الثقة الصالحة
|
||||
description: سيتم التشغيل فقط إذا تم إنشاء المنشور بواسطة مستخدم في مستويات الثقة هذه، ويتم تعيينه افتراضيًا على أي مستوى ثقة
|
||||
after_post_cook:
|
||||
fields:
|
||||
valid_trust_levels:
|
||||
label: مستويات الثقة الصالحة
|
||||
description: سيتم التشغيل فقط إذا تم إنشاء المنشور بواسطة مستخدم في مستويات الثقة هذه، ويتم تعيينه افتراضيًا على أي مستوى ثقة
|
||||
restricted_category:
|
||||
label: الفئة
|
||||
description: اختياري، لن يتم تشغيله إلا إذا كان موضوع المشاركة ضمن هذه الفئة
|
||||
restricted_tags:
|
||||
label: الوسوم
|
||||
description: اختياري، لن يتم تشغيله إلا إذا كان المنشور يحتوي على أي هذه الوسوم
|
||||
scriptables:
|
||||
not_found: تعذَّر العثور على البرنامج النصي `%{script}` للأتمتة `%{automation}`، تأكَّد من تثبيت المكوِّن الإضافي ذي الصلة
|
||||
zapier_webhook:
|
||||
fields:
|
||||
webhook_url:
|
||||
label: عنوان URL لخطاف الويب
|
||||
description: "يتوقع عنوان URL صالحًا لخطاف ويب Zapier؛ على سبيل المثال: https://hooks.zapier.com/hooks/catch/xxx/yyy/"
|
||||
auto_responder:
|
||||
fields:
|
||||
once:
|
||||
label: مرة واحدة
|
||||
description: يستجيب مرة واحدة فقط لكل موضوع
|
||||
word_answer_list:
|
||||
label: قائمة أزواج الكلمات/الإجابات
|
||||
answering_user:
|
||||
label: المستخدم المجيب
|
||||
description: يتم تعيينه افتراضيًا إلى مستخدم النظام
|
||||
auto_tag_topic:
|
||||
fields:
|
||||
tags:
|
||||
label: الوسوم
|
||||
description: قائمة الوسوم لإضافتها إلى الموضوع.
|
||||
post:
|
||||
fields:
|
||||
creator:
|
||||
label: المُنشئ
|
||||
topic:
|
||||
label: معرِّف الموضوع
|
||||
post:
|
||||
label: محتوى المنشور
|
||||
group_category_notification_default:
|
||||
fields:
|
||||
group:
|
||||
label: المجموعة
|
||||
notification_level:
|
||||
label: مستوى الإشعارات
|
||||
update_existing_members:
|
||||
label: تحديث الأعضاء الحاليين
|
||||
description: يحدِّث مستوى الإشعارات لأعضاء المجموعة الحاليين
|
||||
user_global_notice:
|
||||
fields:
|
||||
level:
|
||||
label: المستوى
|
||||
notice:
|
||||
label: الإخطار
|
||||
description: يقبل HTML، لا تملأ هذا بإدخال غير موثوق فيه!
|
||||
levels:
|
||||
warning: تحذير
|
||||
info: المعلومات
|
||||
success: تم بنجاح
|
||||
error: خطأ
|
||||
user_group_membership_through_badge:
|
||||
fields:
|
||||
badge_name:
|
||||
label: اسم الشارة
|
||||
group:
|
||||
label: مجموعة
|
||||
description: المجموعة المستهدفة. ستتم إضافة المستخدمين الذين لديهم الشارة المحدَّدة إلى هذه المجموعة
|
||||
update_user_title_and_flair:
|
||||
label: تحديث عنوان المستخدم والطابع
|
||||
description: اختياري، تحديث عنوان المستخدم والطابع
|
||||
remove_members_without_badge:
|
||||
label: إزالة الأعضاء الحاليين دون شارة
|
||||
description: اختياري، إزالة أعضاء المجموعة الحاليين دون الشارة المحدَّدة
|
||||
badge:
|
||||
label: شارة
|
||||
description: تحديد الشارة
|
||||
suspend_user_by_email:
|
||||
fields:
|
||||
suspend_until:
|
||||
label: التعليق حتى (الإعداد الافتراضي)
|
||||
reason:
|
||||
label: السبب (الإعداد الافتراضي)
|
||||
actor:
|
||||
label: المستخدم
|
||||
description: "المستخدم المسؤول عن التعليق (الإعداد الافتراضي: النظام)"
|
||||
pin_topic:
|
||||
fields:
|
||||
pinnable_topic:
|
||||
label: معرِّف الموضوع
|
||||
pinned_globally:
|
||||
label: مثبَّت بشكلٍ عام
|
||||
pinned_until:
|
||||
label: مثبَّت حتى
|
||||
banner_topic:
|
||||
fields:
|
||||
topic_id:
|
||||
label: معرِّف الموضوع
|
||||
banner_until:
|
||||
label: التحويل إلى لافتة حتى
|
||||
user:
|
||||
label: المستخدم
|
||||
description: "المستخدم الذي يُنشئ البانر (الإعداد الافتراضي: النظام)"
|
||||
flag_post_on_words:
|
||||
fields:
|
||||
words:
|
||||
label: الكلمات المتحقَّق منها
|
||||
topic_required_words:
|
||||
fields:
|
||||
words:
|
||||
label: قائمة الكلمات المطلوبة
|
||||
gift_exchange:
|
||||
fields:
|
||||
gift_exchangers_group:
|
||||
label: اسم مجموعة المشاركين
|
||||
giftee_assignment_messages:
|
||||
label: الرسائل إلى مُرسل الهدية
|
||||
send_pms:
|
||||
add_a_pm_btn:
|
||||
label: إضافة رسالة شخصية
|
||||
fields:
|
||||
receiver:
|
||||
label: مستقبل الرسالة الشخصية
|
||||
sendable_pms:
|
||||
label: الرسائل الشخصية
|
||||
sender:
|
||||
label: مُرسل الرسائل الشخصية
|
||||
close_topic:
|
||||
fields:
|
||||
topic:
|
||||
label: معرِّف الموضوع
|
||||
message:
|
||||
label: الرسالة الختامية
|
||||
description: "رسالة اختيارية لإظهارها في سجل \"تم إغلاق الموضوع\""
|
||||
user:
|
||||
label: المستخدم
|
||||
description: "المستخدم الذي يغلق الموضوع (الافتراضي: النظام)"
|
||||
add_user_to_group_through_custom_field:
|
||||
fields:
|
||||
custom_field_name:
|
||||
label: "اسم الحقل المخصَّص للمستخدم"
|
||||
models:
|
||||
script:
|
||||
name:
|
||||
label: البرنامج النصي
|
||||
trigger:
|
||||
name:
|
||||
label: المشغِّل
|
||||
automation:
|
||||
name:
|
||||
label: الاسم
|
||||
trigger:
|
||||
label: المشغِّل
|
||||
script:
|
||||
label: البرنامج النصي
|
||||
version:
|
||||
label: الإصدار
|
||||
enabled:
|
||||
label: مفعَّل
|
||||
disabled:
|
||||
label: متوقف
|
||||
placeholders:
|
||||
label: العناصر النائبة
|
||||
last_updated_at:
|
||||
label: آخر تحديث
|
||||
last_updated_by:
|
||||
label: تم التحديث بواسطة
|
|
@ -0,0 +1,7 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
be:
|
|
@ -0,0 +1,7 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
bg:
|
|
@ -0,0 +1,7 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
bs_BA:
|
|
@ -0,0 +1,7 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
ca:
|
|
@ -0,0 +1,7 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
cs:
|
|
@ -0,0 +1,257 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
da:
|
||||
js:
|
||||
discourse_automation:
|
||||
title: Automatisering
|
||||
create: Opret
|
||||
update: Opdater
|
||||
select_script: Vælg et script
|
||||
select_trigger: Vælg en udløser
|
||||
no_automation_yet: Du har ikke oprettet nogen automatisering endnu.
|
||||
edit_automation:
|
||||
trigger_section:
|
||||
trigger_now: "Udløs nu"
|
||||
title: Hvornår/hvad...
|
||||
fields_section:
|
||||
title: Script indstillinger
|
||||
destroy_automation:
|
||||
confirm: "Er du sikker på, at du vil slette `%{name}`?"
|
||||
fields:
|
||||
user:
|
||||
label: Bruger
|
||||
pm:
|
||||
title:
|
||||
label: Titel
|
||||
pms:
|
||||
confirm_remove_pm: "Er du sikker på, at du vil fjerne denne PM?"
|
||||
placeholder_title: PM titel
|
||||
add_pm: Tilføj PM
|
||||
no_pm_created: Du har ikke oprettet nogen PM endnu. PM'er vil blive sendt, når din automatisering er udløst.
|
||||
title:
|
||||
label: Titel
|
||||
delay:
|
||||
label: Forsinkelse (minutter)
|
||||
prefers_encrypt:
|
||||
label: Krypterer PM hvis tilgængelig
|
||||
group:
|
||||
label: Gruppe
|
||||
text:
|
||||
label: Tekst
|
||||
triggerables:
|
||||
user_badge_granted:
|
||||
fields:
|
||||
badge:
|
||||
label: Emblem
|
||||
stalled_topic:
|
||||
durations:
|
||||
PT1H: "En time"
|
||||
P1D: "En dag"
|
||||
P1W: "En uge"
|
||||
P2W: "To uger"
|
||||
P1M: "En måned"
|
||||
P3M: "Tre måneder"
|
||||
P6M: "Seks måneder"
|
||||
P1Y: "Et år"
|
||||
fields:
|
||||
categories:
|
||||
label: Begrænset til kategorier
|
||||
tags:
|
||||
label: Begrænset til mærker
|
||||
stalled_after:
|
||||
label: Gået i stå efter
|
||||
recurring:
|
||||
every: Hver
|
||||
frequencies:
|
||||
minute: minut
|
||||
hour: time
|
||||
day: dag
|
||||
weekday: hverdag
|
||||
week: uge
|
||||
month: måned
|
||||
year: år
|
||||
fields:
|
||||
start_date:
|
||||
label: Start dato
|
||||
stalled_wiki:
|
||||
durations:
|
||||
PT1H: "En time"
|
||||
P1D: "En dag"
|
||||
P1W: "En uge"
|
||||
P2W: "To uger"
|
||||
P1M: "En måned"
|
||||
P3M: "Tre måneder"
|
||||
P6M: "Seks måneder"
|
||||
P1Y: "Et år"
|
||||
fields:
|
||||
restricted_category:
|
||||
label: Begrænset til kategori
|
||||
stalled_after:
|
||||
label: Udløserforsinkelse
|
||||
user_added_to_group:
|
||||
fields:
|
||||
joined_group:
|
||||
label: Sporet gruppe
|
||||
user_removed_from_group:
|
||||
fields:
|
||||
left_group:
|
||||
label: Sporet gruppe
|
||||
user_promoted:
|
||||
fields:
|
||||
restricted_group:
|
||||
label: Begræns til gruppe
|
||||
trust_level_transition:
|
||||
label: Overgang til tillidsniveau
|
||||
trust_levels:
|
||||
ALL: "Alle tillidsniveauer"
|
||||
TL01: "TL0 til TL1"
|
||||
TL12: "TL1 til TL2"
|
||||
TL23: "TL2 til TL3"
|
||||
TL34: "TL3 til TL4"
|
||||
topic:
|
||||
fields:
|
||||
restricted_topic:
|
||||
label: Emne ID
|
||||
post_created_edited:
|
||||
fields:
|
||||
action_type:
|
||||
label: Handlingstype
|
||||
valid_trust_levels:
|
||||
label: Gyldige tillidsniveauer
|
||||
restricted_category:
|
||||
label: Kategori
|
||||
restricted_group:
|
||||
label: Gruppe
|
||||
created: Skabt
|
||||
edited: Redigeret
|
||||
category_created_edited:
|
||||
fields:
|
||||
restricted_category:
|
||||
label: Overordnet Kategori
|
||||
pm_created:
|
||||
fields:
|
||||
restricted_group:
|
||||
label: Gruppe
|
||||
valid_trust_levels:
|
||||
label: Gyldige tillidsniveauer
|
||||
after_post_cook:
|
||||
fields:
|
||||
valid_trust_levels:
|
||||
label: Gyldige tillidsniveauer
|
||||
restricted_category:
|
||||
label: Kategori
|
||||
restricted_tags:
|
||||
label: Mærker
|
||||
scriptables:
|
||||
zapier_webhook:
|
||||
fields:
|
||||
webhook_url:
|
||||
label: Webhook URL
|
||||
auto_responder:
|
||||
fields:
|
||||
once:
|
||||
label: En Gang
|
||||
auto_tag_topic:
|
||||
fields:
|
||||
tags:
|
||||
label: Mærker
|
||||
post:
|
||||
fields:
|
||||
topic:
|
||||
label: Emne ID
|
||||
group_category_notification_default:
|
||||
fields:
|
||||
group:
|
||||
label: Gruppe
|
||||
notification_level:
|
||||
label: Notifikationsniveau
|
||||
update_existing_members:
|
||||
label: Opdater eksisterende medlemmer
|
||||
description: Opdaterer meddelelsesniveauet for eksisterende gruppemedlemmer
|
||||
user_global_notice:
|
||||
fields:
|
||||
level:
|
||||
label: Niveau
|
||||
notice:
|
||||
label: Bemærk
|
||||
levels:
|
||||
warning: Advarsel
|
||||
info: Info
|
||||
success: Succes
|
||||
error: Fejl
|
||||
user_group_membership_through_badge:
|
||||
fields:
|
||||
badge_name:
|
||||
label: Emblem Navn
|
||||
group:
|
||||
label: Gruppe
|
||||
badge:
|
||||
label: Emblem
|
||||
suspend_user_by_email:
|
||||
fields:
|
||||
actor:
|
||||
label: Bruger
|
||||
pin_topic:
|
||||
fields:
|
||||
pinnable_topic:
|
||||
label: Emne ID
|
||||
pinned_globally:
|
||||
label: Fastgjort globalt
|
||||
pinned_until:
|
||||
label: Fastgjort indtil
|
||||
banner_topic:
|
||||
fields:
|
||||
topic_id:
|
||||
label: Emne ID
|
||||
user:
|
||||
label: Bruger
|
||||
description: "Brugeren, der opretter banneret (standard: system)"
|
||||
topic_required_words:
|
||||
fields:
|
||||
words:
|
||||
label: Liste over påkrævede ord
|
||||
send_pms:
|
||||
add_a_pm_btn:
|
||||
label: Tilføj en PM
|
||||
fields:
|
||||
sendable_pms:
|
||||
label: PM'er
|
||||
close_topic:
|
||||
fields:
|
||||
topic:
|
||||
label: Emne ID
|
||||
message:
|
||||
label: Lukkebesked
|
||||
user:
|
||||
label: Bruger
|
||||
description: "Brugeren, der lukker emnet (standard: system)"
|
||||
models:
|
||||
script:
|
||||
name:
|
||||
label: Script
|
||||
trigger:
|
||||
name:
|
||||
label: Udløser
|
||||
automation:
|
||||
name:
|
||||
label: Navn
|
||||
trigger:
|
||||
label: Aftrækkeren
|
||||
script:
|
||||
label: Script
|
||||
version:
|
||||
label: Version
|
||||
enabled:
|
||||
label: Aktiveret
|
||||
disabled:
|
||||
label: Deaktiveret
|
||||
placeholders:
|
||||
label: Pladsholdere
|
||||
last_updated_at:
|
||||
label: Sidst opdateret
|
||||
last_updated_by:
|
||||
label: Opdateret af
|
|
@ -0,0 +1,378 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
de:
|
||||
js:
|
||||
discourse_automation:
|
||||
title: Automatisierung
|
||||
create: Erstellen
|
||||
update: Aktualisieren
|
||||
select_script: Skript auswählen
|
||||
select_trigger: Auslöser auswählen
|
||||
confirm_automation_reset: Diese Aktion setzt die Skript- und Auslöseroptionen zurück, der neue Zustand wird gespeichert. Willst du fortfahren?
|
||||
confirm_automation_trigger: Diese Aktion löst die Automatisierung aus. Willst du fortfahren?
|
||||
no_automation_yet: Du hast noch keine Automatisierung erstellt.
|
||||
edit_automation:
|
||||
trigger_section:
|
||||
forced: Dieser Auslöser wird vom Skript erzwungen.
|
||||
next_pending_automation: "Die nächste Automatisierung wird ausgelöst am: %{date}"
|
||||
trigger_now: "Jetzt auslösen"
|
||||
title: Wann/Was …
|
||||
fields_section:
|
||||
title: Skriptoptionen
|
||||
destroy_automation:
|
||||
confirm: "Bist du sicher, dass du `%{name}` löschen willst?"
|
||||
fields:
|
||||
key_value:
|
||||
label:
|
||||
one: Konfiguration bearbeiten (%{count})
|
||||
other: Konfiguration bearbeiten (%{count})
|
||||
user:
|
||||
label: Benutzer
|
||||
pm:
|
||||
title:
|
||||
label: Titel
|
||||
raw:
|
||||
label: Body
|
||||
pms:
|
||||
confirm_remove_pm: "Bist du sicher, dass du diese PN entfernen möchtest?"
|
||||
placeholder_title: PN-Titel
|
||||
add_pm: PN hinzufügen
|
||||
no_pm_created: Du hast noch keine PN erstellt. PN werden verschickt, sobald deine Automatisierung ausgelöst wird.
|
||||
title:
|
||||
label: Titel
|
||||
raw:
|
||||
label: Body
|
||||
delay:
|
||||
label: Verzögerung (Minuten)
|
||||
prefers_encrypt:
|
||||
label: Verschlüsselt PN, falls verfügbar
|
||||
group:
|
||||
label: Gruppe
|
||||
text:
|
||||
label: Text
|
||||
triggerables:
|
||||
not_found: Konnte den Auslöser `%{trigger}` für die Automatisierung `%{automation}` nicht finden. Stelle sicher, dass das zugehörige Plug-in installiert ist
|
||||
user_badge_granted:
|
||||
fields:
|
||||
badge:
|
||||
label: Abzeichen
|
||||
only_first_grant:
|
||||
label: Nur bei erster Vergabe
|
||||
stalled_topic:
|
||||
durations:
|
||||
PT1H: "Eine Stunde"
|
||||
P1D: "Ein Tag"
|
||||
P1W: "Eine Woche"
|
||||
P2W: "Zwei Wochen"
|
||||
P1M: "Ein Monat"
|
||||
P3M: "Drei Monate"
|
||||
P6M: "Sechs Monate"
|
||||
P1Y: "Ein Jahr"
|
||||
fields:
|
||||
categories:
|
||||
label: Beschränkt auf Kategorien
|
||||
tags:
|
||||
label: Beschränkt auf Tags
|
||||
stalled_after:
|
||||
label: Festgefahren nach
|
||||
recurring:
|
||||
every: Jede(n/s)
|
||||
frequencies:
|
||||
minute: Minute
|
||||
hour: Stunde
|
||||
day: Tag
|
||||
weekday: Wochentag
|
||||
week: Woche
|
||||
month: Monat
|
||||
year: Jahr
|
||||
fields:
|
||||
recurrence:
|
||||
label: Wiederholung
|
||||
start_date:
|
||||
label: Startdatum
|
||||
stalled_wiki:
|
||||
durations:
|
||||
PT1H: "Eine Stunde"
|
||||
P1D: "Ein Tag"
|
||||
P1W: "Eine Woche"
|
||||
P2W: "Zwei Wochen"
|
||||
P1M: "Ein Monat"
|
||||
P3M: "Drei Monate"
|
||||
P6M: "Sechs Monate"
|
||||
P1Y: "Ein Jahr"
|
||||
fields:
|
||||
restricted_category:
|
||||
label: Auf Kategorie beschränkt
|
||||
stalled_after:
|
||||
label: Auslöseverzögerung
|
||||
description: Definiert die Verzögerung zwischen der letzten Wiki-Bearbeitung und dem Auslöser der Automatisierung
|
||||
retriggered_after:
|
||||
label: Verzögerung der erneuten Auslösung
|
||||
description: Definiert die Verzögerung zwischen dem ersten Auslöser und dem nächsten Auslöser, falls das Wiki nach dem ersten Auslöser noch nicht bearbeitet wurde
|
||||
user_added_to_group:
|
||||
fields:
|
||||
joined_group:
|
||||
label: Verfolgte Gruppe
|
||||
user_removed_from_group:
|
||||
fields:
|
||||
left_group:
|
||||
label: Verfolgte Gruppe
|
||||
user_promoted:
|
||||
fields:
|
||||
restricted_group:
|
||||
label: Auf Gruppe beschränken
|
||||
trust_level_transition:
|
||||
label: Vertrauensstufenübergang
|
||||
trust_levels:
|
||||
ALL: "Alle Vertrauensstufen"
|
||||
TL01: "VS0 auf VS1"
|
||||
TL12: "VS1 auf VS2"
|
||||
TL23: "VS2 auf VS3"
|
||||
TL34: "VS3 auf VS4"
|
||||
point_in_time:
|
||||
fields:
|
||||
execute_at:
|
||||
label: Ausführen um
|
||||
topic:
|
||||
fields:
|
||||
restricted_topic:
|
||||
label: Themen-ID
|
||||
post_created_edited:
|
||||
fields:
|
||||
action_type:
|
||||
label: Aktionstyp
|
||||
description: "Optional, beschränke die Auslösung auf erstellte oder bearbeitete Ereignisse"
|
||||
valid_trust_levels:
|
||||
label: Gültige Vertrauensstufen
|
||||
description: Wird nur ausgelöst, wenn ein Beitrag von einem Benutzer mit diesen Vertrauensstufen erstellt wird, standardmäßig eine beliebige Vertrauensstufe
|
||||
restricted_category:
|
||||
label: Kategorie
|
||||
description: Optional. Wird nur ausgelöst, wenn das Thema des Beitrags in dieser Kategorie ist
|
||||
restricted_group:
|
||||
label: Gruppe
|
||||
description: Optional. Wird nur ausgelöst, wenn das Thema des Beitrags eine private Nachricht im Posteingang dieser Gruppe ist
|
||||
ignore_group_members:
|
||||
label: Gruppenmitglieder ignorieren
|
||||
description: Überspringe den Auslöser, wenn der Absender ein Mitglied der oben angegebenen Gruppe ist
|
||||
ignore_automated:
|
||||
label: Automatisierte Nachrichten ignorieren
|
||||
description: Überspringe den Auslöser, wenn der Absender eine Noreply-E-Mail-Adresse nutzt oder die Nachricht von einer automatischen Quelle stammt. Gilt nur für Beiträge, die per E-Mail erstellt wurden
|
||||
first_post_only:
|
||||
label: Nur erster Beitrag
|
||||
description: Wird nur ausgelöst, wenn der Beitrag der erste Beitrag ist, den ein Benutzer erstellt hat
|
||||
first_topic_only:
|
||||
label: Nur erstes Thema
|
||||
description: Wird nur ausgelöst, wenn das Thema das erste Thema ist, das ein Benutzer erstellt hat
|
||||
created: Erstellt
|
||||
edited: Bearbeitet
|
||||
user_updated:
|
||||
fields:
|
||||
user_profile:
|
||||
label: Benutzerprofilfelder
|
||||
description: Wird nur ausgelöst, wenn der Benutzer alle diese Felder ausgefüllt hat
|
||||
custom_fields:
|
||||
label: Benutzerdefinierte Felder
|
||||
description: Wird nur ausgelöst, wenn der Benutzer alle diese Felder ausgefüllt hat
|
||||
once_per_user:
|
||||
label: Einmal pro Benutzer
|
||||
description: Wird nur einmal pro Benutzer ausgelöst
|
||||
category_created_edited:
|
||||
fields:
|
||||
restricted_category:
|
||||
label: Übergeordnete Kategorie
|
||||
description: Optional, ermöglicht es, die Ausführung des Auslösers auf diese Kategorie zu beschränken
|
||||
pm_created:
|
||||
fields:
|
||||
restricted_user:
|
||||
label: Benutzer
|
||||
description: Wird nur für PN ausgelöst, die an diesen Benutzer gesendet werden
|
||||
restricted_group:
|
||||
label: Gruppe
|
||||
description: Wird nur für PN ausgelöst, die an diese Gruppe gesendet werden
|
||||
ignore_staff:
|
||||
label: Teammitglieder ignorieren
|
||||
description: Den Auslöser überspringen, falls der Absender ein Teammitglied ist
|
||||
ignore_group_members:
|
||||
label: Gruppenmitglieder ignorieren
|
||||
description: Überspringe den Auslöser, wenn der Absender ein Mitglied der oben angegebenen Gruppe ist
|
||||
ignore_automated:
|
||||
label: Automatisierte Nachrichten ignorieren
|
||||
description: Überspringe den Auslöser, wenn der Absender eine Noreply-E-Mail-Adresse nutzt oder die Nachricht von einer automatischen Quelle stammt. Gilt nur für PN, die per E-Mail erstellt wurden
|
||||
valid_trust_levels:
|
||||
label: Gültige Vertrauensstufen
|
||||
description: Wird nur ausgelöst, wenn ein Beitrag von einem Benutzer mit diesen Vertrauensstufen erstellt wird, standardmäßig eine beliebige Vertrauensstufe
|
||||
after_post_cook:
|
||||
fields:
|
||||
valid_trust_levels:
|
||||
label: Gültige Vertrauensstufen
|
||||
description: Wird nur ausgelöst, wenn ein Beitrag von einem Benutzer mit diesen Vertrauensstufen erstellt wird, standardmäßig eine beliebige Vertrauensstufe
|
||||
restricted_category:
|
||||
label: Kategorie
|
||||
description: Optional. Wird nur ausgelöst, wenn das Thema des Beitrags in dieser Kategorie ist
|
||||
restricted_tags:
|
||||
label: Tags
|
||||
description: Optional. Wird nur ausgelöst, wenn der Beitrag eines dieser Schlagwörter enthält
|
||||
scriptables:
|
||||
not_found: Konnte das Skript `%{script}` für die Automatisierung `%{automation}` nicht finden. Stelle sicher, dass das zugehörige Plug-in installiert ist
|
||||
zapier_webhook:
|
||||
fields:
|
||||
webhook_url:
|
||||
label: Webhook-URL
|
||||
description: "Erwartet eine gültige Zapier-Webhook-URL, z. B.: https://hooks.zapier.com/hooks/catch/xxx/yyy/"
|
||||
auto_responder:
|
||||
fields:
|
||||
once:
|
||||
label: Einmal
|
||||
description: Antwortet nur einmal pro Thema
|
||||
word_answer_list:
|
||||
label: Liste von Wort/Antwort-Paaren
|
||||
description: "Definiert eine Liste von Schlüssel/Wert-Gruppen, wobei `key` der gesuchte Begriff und `value` der Text der Antwort ist. `key` kann leer gelassen werden, um unabhängig vom Inhalt auf alle Auslöser zu reagieren. Beachte, dass `value` `{{key}}` als Platzhalter akzeptiert, der in der Antwort durch den Wert von `key` ersetzt wird. Beachte weiterhin, dass `key` als Regex ausgewertet wird und Sonderzeichen wie `.` maskiert werden müssen, wenn du tatsächlich einen Punkt meinst, z. B.: `\\.`"
|
||||
answering_user:
|
||||
label: Antwortender Benutzer
|
||||
description: Standardmäßig Systembenutzer
|
||||
auto_tag_topic:
|
||||
fields:
|
||||
tags:
|
||||
label: Schlagwörter
|
||||
description: Liste der Schlagwörter, die dem Thema hinzugefügt werden sollen.
|
||||
post:
|
||||
fields:
|
||||
creator:
|
||||
label: Ersteller
|
||||
post_creator_context: Der Ersteller des Beitrags
|
||||
updated_user_context: Der aktualisierte Benutzer
|
||||
topic:
|
||||
label: Themen-ID
|
||||
post:
|
||||
label: Beitragsinhalt
|
||||
group_category_notification_default:
|
||||
fields:
|
||||
group:
|
||||
label: Gruppe
|
||||
notification_level:
|
||||
label: Benachrichtigungsstufe
|
||||
update_existing_members:
|
||||
label: Bestehende Mitglieder aktualisieren
|
||||
description: Aktualisiert die Benachrichtigungsstufe für bestehende Gruppenmitglieder
|
||||
user_global_notice:
|
||||
fields:
|
||||
level:
|
||||
label: Level
|
||||
notice:
|
||||
label: Hinweis
|
||||
description: Akzeptiert HTML. Gib nur vertrauenswürdige Inhalte ein!
|
||||
levels:
|
||||
warning: Warnung
|
||||
info: Info
|
||||
success: Erfolg
|
||||
error: Fehler
|
||||
user_group_membership_through_badge:
|
||||
fields:
|
||||
badge_name:
|
||||
label: Abzeichenname
|
||||
group:
|
||||
label: Gruppe
|
||||
description: Zielgruppe. Benutzer mit dem angegebenen Abzeichen werden dieser Gruppe hinzugefügt
|
||||
update_user_title_and_flair:
|
||||
label: Benutzertitel und Flair aktualisieren
|
||||
description: Optional. Benutzertitel und Flair aktualisieren
|
||||
remove_members_without_badge:
|
||||
label: Bestehende Mitglieder ohne Abzeichen entfernen
|
||||
description: Optional. Bestehende Gruppenmitglieder ohne das angegebene Abzeichen entfernen
|
||||
badge:
|
||||
label: Abzeichen
|
||||
description: Abzeichen auswählen
|
||||
suspend_user_by_email:
|
||||
fields:
|
||||
suspend_until:
|
||||
label: Aussetzen bis (Standard)
|
||||
reason:
|
||||
label: Grund (Standard)
|
||||
actor:
|
||||
label: Benutzer
|
||||
description: "Der für die Aussetzung verantwortliche Benutzer (Standard: System)"
|
||||
pin_topic:
|
||||
fields:
|
||||
pinnable_topic:
|
||||
label: Themen-ID
|
||||
pinned_globally:
|
||||
label: Global angeheftet
|
||||
pinned_until:
|
||||
label: Angeheftet bis
|
||||
banner_topic:
|
||||
fields:
|
||||
topic_id:
|
||||
label: Themen-ID
|
||||
banner_until:
|
||||
label: Banner machen bis
|
||||
user:
|
||||
label: Benutzer
|
||||
description: "Der Benutzer, der das Banner erstellt (Standard: System)"
|
||||
flag_post_on_words:
|
||||
fields:
|
||||
words:
|
||||
label: Geprüfte Wörter
|
||||
topic_required_words:
|
||||
fields:
|
||||
words:
|
||||
label: Liste der erforderlichen Wörter
|
||||
gift_exchange:
|
||||
fields:
|
||||
gift_exchangers_group:
|
||||
label: Gruppenname der Teilnehmer
|
||||
giftee_assignment_messages:
|
||||
label: An den Schenkenden gesendete Nachrichten
|
||||
send_pms:
|
||||
add_a_pm_btn:
|
||||
label: PN hinzufügen
|
||||
fields:
|
||||
receiver:
|
||||
label: PN-Empfänger
|
||||
sendable_pms:
|
||||
label: PN
|
||||
sender:
|
||||
label: PN-Absender
|
||||
close_topic:
|
||||
fields:
|
||||
topic:
|
||||
label: Themen-ID
|
||||
message:
|
||||
label: Nachricht bzgl. Schließung
|
||||
description: "Optionale Nachricht, die im Eintrag „Thema geschlossen“ angezeigt wird"
|
||||
user:
|
||||
label: Benutzer
|
||||
description: "Der Benutzer, der das Thema schließt (Standard: System)"
|
||||
add_user_to_group_through_custom_field:
|
||||
fields:
|
||||
custom_field_name:
|
||||
label: "Name des benutzerdefinierten Benutzerfelds"
|
||||
models:
|
||||
script:
|
||||
name:
|
||||
label: Skript
|
||||
trigger:
|
||||
name:
|
||||
label: Auslöser
|
||||
automation:
|
||||
name:
|
||||
label: Name
|
||||
trigger:
|
||||
label: Auslöser
|
||||
script:
|
||||
label: Skript
|
||||
version:
|
||||
label: Version
|
||||
enabled:
|
||||
label: Aktiviert
|
||||
disabled:
|
||||
label: Deaktiviert
|
||||
placeholders:
|
||||
label: Platzhalter
|
||||
last_updated_at:
|
||||
label: Letzte Aktualisierung
|
||||
last_updated_by:
|
||||
label: Aktualisiert von
|
|
@ -0,0 +1,7 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
el:
|
|
@ -0,0 +1,374 @@
|
|||
en:
|
||||
js:
|
||||
discourse_automation:
|
||||
title: Automation
|
||||
create: Create
|
||||
update: Update
|
||||
select_script: Select a script
|
||||
select_trigger: Select a trigger
|
||||
confirm_automation_reset: This action will reset script and trigger options, new state will be saved, do you want to proceed?
|
||||
confirm_automation_trigger: This action will trigger the automation, do you want to proceed?
|
||||
no_automation_yet: You haven’t created any automation yet.
|
||||
edit_automation:
|
||||
trigger_section:
|
||||
forced: This trigger is forced by script.
|
||||
next_pending_automation: "Next automation will trigger at: %{date}"
|
||||
trigger_now: "Trigger now"
|
||||
title: When/What...
|
||||
fields_section:
|
||||
title: Script options
|
||||
destroy_automation:
|
||||
confirm: "Are you sure you want to delete `%{name}`?"
|
||||
fields:
|
||||
key_value:
|
||||
label_without_count: "Configure"
|
||||
label_with_count:
|
||||
one: "Edit Configuration (%{count})"
|
||||
other: "Edit Configuration (%{count})"
|
||||
user:
|
||||
label: User
|
||||
pm:
|
||||
title:
|
||||
label: Title
|
||||
raw:
|
||||
label: Body
|
||||
pms:
|
||||
confirm_remove_pm: "Are you sure you want to remove this PM?"
|
||||
placeholder_title: PM title
|
||||
add_pm: Add PM
|
||||
no_pm_created: You haven’t created any PM yet. PMs will be sent once your automation is triggered.
|
||||
title:
|
||||
label: Title
|
||||
raw:
|
||||
label: Body
|
||||
delay:
|
||||
label: Delay (minutes)
|
||||
prefers_encrypt:
|
||||
label: Encrypts PM if available
|
||||
group:
|
||||
label: Group
|
||||
text:
|
||||
label: Text
|
||||
triggerables:
|
||||
not_found: Couldn’t find trigger `%{trigger}` for automation `%{automation}`, ensure the associated plugin is installed
|
||||
user_badge_granted:
|
||||
fields:
|
||||
badge:
|
||||
label: Badge
|
||||
only_first_grant:
|
||||
label: Only on first grant
|
||||
stalled_topic:
|
||||
durations:
|
||||
PT1H: "One hour"
|
||||
P1D: "One day"
|
||||
P1W: "One week"
|
||||
P2W: "Two weeks"
|
||||
P1M: "One month"
|
||||
P3M: "Three months"
|
||||
P6M: "Six months"
|
||||
P1Y: "One year"
|
||||
fields:
|
||||
categories:
|
||||
label: Limited to categories
|
||||
tags:
|
||||
label: Limited to tags
|
||||
stalled_after:
|
||||
label: Stalled after
|
||||
recurring:
|
||||
every: Every
|
||||
frequencies:
|
||||
minute: minute
|
||||
hour: hour
|
||||
day: day
|
||||
weekday: weekday
|
||||
week: week
|
||||
month: month
|
||||
year: year
|
||||
fields:
|
||||
recurrence:
|
||||
label: Recurrence
|
||||
start_date:
|
||||
label: Start date
|
||||
stalled_wiki:
|
||||
durations:
|
||||
PT1H: "One hour"
|
||||
P1D: "One day"
|
||||
P1W: "One week"
|
||||
P2W: "Two weeks"
|
||||
P1M: "One month"
|
||||
P3M: "Three months"
|
||||
P6M: "Six months"
|
||||
P1Y: "One year"
|
||||
fields:
|
||||
restricted_category:
|
||||
label: Restricted to category
|
||||
stalled_after:
|
||||
label: Trigger delay
|
||||
description: Defines delay between last wiki edit and automation’s trigger
|
||||
retriggered_after:
|
||||
label: Re-trigger delay
|
||||
description: Defines delay between first trigger and next trigger, if wiki has still not been edited after first trigger
|
||||
user_added_to_group:
|
||||
fields:
|
||||
joined_group:
|
||||
label: Tracked group
|
||||
user_removed_from_group:
|
||||
fields:
|
||||
left_group:
|
||||
label: Tracked group
|
||||
user_promoted:
|
||||
fields:
|
||||
restricted_group:
|
||||
label: Restrict to group
|
||||
trust_level_transition:
|
||||
label: Trust level transition
|
||||
trust_levels:
|
||||
ALL: "All trust levels"
|
||||
TL01: "TL0 to TL1"
|
||||
TL12: "TL1 to TL2"
|
||||
TL23: "TL2 to TL3"
|
||||
TL34: "TL3 to TL4"
|
||||
point_in_time:
|
||||
fields:
|
||||
execute_at:
|
||||
label: Execute at
|
||||
topic:
|
||||
fields:
|
||||
restricted_topic:
|
||||
label: Topic ID
|
||||
post_created_edited:
|
||||
fields:
|
||||
action_type:
|
||||
label: Action type
|
||||
description: "Optional, limit triggering to only created or edited events"
|
||||
valid_trust_levels:
|
||||
label: Valid trust levels
|
||||
description: Will trigger only if post is created by user in these trust levels, defaults to any trust level
|
||||
restricted_category:
|
||||
label: Category
|
||||
description: Optional, will trigger only if the post's topic is in this category
|
||||
restricted_group:
|
||||
label: Group
|
||||
description: Optional, will trigger only if the post's topic is a private message in this group's inbox
|
||||
ignore_group_members:
|
||||
label: Ignore group members
|
||||
description: Skip the trigger if sender is a member of the Group specified above
|
||||
ignore_automated:
|
||||
label: Ignore automated
|
||||
description: Skip the trigger if the sender has a noreply email or is from an automated source. Only applies to posts created via email
|
||||
first_post_only:
|
||||
label: First post only
|
||||
description: Will trigger only if the post is the first post a user created
|
||||
first_topic_only:
|
||||
label: First topic only
|
||||
description: Will trigger only if the topic is the first topic a user created
|
||||
created: Created
|
||||
edited: Edited
|
||||
user_updated:
|
||||
fields:
|
||||
user_profile:
|
||||
label: User profile fields
|
||||
description: Will trigger only if the user has filled all these fields
|
||||
custom_fields:
|
||||
label: User custom fields
|
||||
description: Will trigger only if the user has filled all these fields
|
||||
once_per_user:
|
||||
label: Once per user
|
||||
description: Will trigger only once per user
|
||||
category_created_edited:
|
||||
fields:
|
||||
restricted_category:
|
||||
label: Parent Category
|
||||
description: Optional, allows to limit trigger execution to this category
|
||||
pm_created:
|
||||
fields:
|
||||
restricted_user:
|
||||
label: Users
|
||||
description: Will trigger only for PMs sent to this user
|
||||
restricted_group:
|
||||
label: Group
|
||||
description: Will trigger only for PMs sent to this group
|
||||
ignore_staff:
|
||||
label: Ignore staff
|
||||
description: Skip the trigger if sender is a staff user
|
||||
ignore_group_members:
|
||||
label: Ignore group members
|
||||
description: Skip the trigger if sender is a member of the Group specified above
|
||||
ignore_automated:
|
||||
label: Ignore automated
|
||||
description: Skip the trigger if the sender has a noreply email or is from an automated source. Only applies to PMs created via email
|
||||
valid_trust_levels:
|
||||
label: Valid trust levels
|
||||
description: Will trigger only if post is created by user in these trust levels, defaults to any trust level
|
||||
after_post_cook:
|
||||
fields:
|
||||
valid_trust_levels:
|
||||
label: Valid trust levels
|
||||
description: Will trigger only if post is created by user in these trust levels, defaults to any trust level
|
||||
restricted_category:
|
||||
label: Category
|
||||
description: Optional, will trigger only if the post's topic is in this category
|
||||
restricted_tags:
|
||||
label: Tags
|
||||
description: Optional, will trigger only if the post has any of these tags
|
||||
scriptables:
|
||||
not_found: Couldn’t find script `%{script}` for automation `%{automation}`, ensure the associated plugin is installed
|
||||
zapier_webhook:
|
||||
fields:
|
||||
webhook_url:
|
||||
label: Webhook URL
|
||||
description: "Expects a valid Zapier webhook URL, eg: https://hooks.zapier.com/hooks/catch/xxx/yyy/"
|
||||
auto_responder:
|
||||
fields:
|
||||
once:
|
||||
label: Once
|
||||
description: Only responds once by topic
|
||||
word_answer_list:
|
||||
label: List of word/answer pairs
|
||||
description: "Defines a list of key/value groups, where the `key` is the searched term, and `value` the text of the reply. The `key` can be left blank to respond to all triggers, regardless of content. Note that `value` accepts `{{key}}` as a placeholder to be replaced by the value of `key` in the reply. Note that `key` will be evaluated as a regex, and special chars like `.` should be escaped if you actually mean a dot, eg: `\\.`"
|
||||
answering_user:
|
||||
label: Answering user
|
||||
description: Defaults to System user
|
||||
auto_tag_topic:
|
||||
fields:
|
||||
tags:
|
||||
label: Tags
|
||||
description: List of tags to add to the topic.
|
||||
post:
|
||||
fields:
|
||||
creator:
|
||||
label: Creator
|
||||
post_creator_context: The creator of the post
|
||||
updated_user_context: The updated user
|
||||
topic:
|
||||
label: Topic ID
|
||||
post:
|
||||
label: Post content
|
||||
group_category_notification_default:
|
||||
fields:
|
||||
group:
|
||||
label: Group
|
||||
notification_level:
|
||||
label: Notification level
|
||||
update_existing_members:
|
||||
label: Update existing members
|
||||
description: Updates the notification level for existing group members
|
||||
user_global_notice:
|
||||
fields:
|
||||
level:
|
||||
label: Level
|
||||
notice:
|
||||
label: Notice
|
||||
description: Accepts HTML, do not fill this with untrusted input!
|
||||
levels:
|
||||
warning: Warning
|
||||
info: Info
|
||||
success: Success
|
||||
error: Error
|
||||
user_group_membership_through_badge:
|
||||
fields:
|
||||
badge_name:
|
||||
label: Badge Name
|
||||
group:
|
||||
label: Group
|
||||
description: Target group. Users with the specified badge will be added to this group
|
||||
update_user_title_and_flair:
|
||||
label: Update user title and flair
|
||||
description: Optional, Update user title and flair
|
||||
remove_members_without_badge:
|
||||
label: Remove existing members without badge
|
||||
description: Optional, Remove existing group members without the specified badge
|
||||
badge:
|
||||
label: Badge
|
||||
description: Select badge
|
||||
suspend_user_by_email:
|
||||
fields:
|
||||
suspend_until:
|
||||
label: Suspend until (default)
|
||||
reason:
|
||||
label: Reason (default)
|
||||
actor:
|
||||
label: User
|
||||
description: "The user responsible for the suspension (default: system)"
|
||||
pin_topic:
|
||||
fields:
|
||||
pinnable_topic:
|
||||
label: Topic ID
|
||||
pinned_globally:
|
||||
label: Pinned globally
|
||||
pinned_until:
|
||||
label: Pinned until
|
||||
banner_topic:
|
||||
fields:
|
||||
topic_id:
|
||||
label: Topic ID
|
||||
banner_until:
|
||||
label: Make banner until
|
||||
user:
|
||||
label: User
|
||||
description: "The user creating the banner (default: system)"
|
||||
flag_post_on_words:
|
||||
fields:
|
||||
words:
|
||||
label: Checked words
|
||||
topic_required_words:
|
||||
fields:
|
||||
words:
|
||||
label: Required words list
|
||||
gift_exchange:
|
||||
fields:
|
||||
gift_exchangers_group:
|
||||
label: Group name of participants
|
||||
giftee_assignment_messages:
|
||||
label: Messages sent to gifter
|
||||
send_pms:
|
||||
add_a_pm_btn:
|
||||
label: Add a PM
|
||||
fields:
|
||||
receiver:
|
||||
label: PM receiver
|
||||
sendable_pms:
|
||||
label: PMs
|
||||
sender:
|
||||
label: PMs sender
|
||||
close_topic:
|
||||
fields:
|
||||
topic:
|
||||
label: Topic ID
|
||||
message:
|
||||
label: Closing message
|
||||
description: "Optional message to show on the Topic Closed record"
|
||||
user:
|
||||
label: User
|
||||
description: "The user closing the topic (default: system)"
|
||||
add_user_to_group_through_custom_field:
|
||||
fields:
|
||||
custom_field_name:
|
||||
label: "User Custom Field name"
|
||||
|
||||
models:
|
||||
script:
|
||||
name:
|
||||
label: Script
|
||||
trigger:
|
||||
name:
|
||||
label: Trigger
|
||||
automation:
|
||||
name:
|
||||
label: Name
|
||||
trigger:
|
||||
label: Trigger
|
||||
script:
|
||||
label: Script
|
||||
version:
|
||||
label: Version
|
||||
enabled:
|
||||
label: Enabled
|
||||
disabled:
|
||||
label: Disabled
|
||||
placeholders:
|
||||
label: Placeholders
|
||||
last_updated_at:
|
||||
label: Last update
|
||||
last_updated_by:
|
||||
label: Updated by
|
|
@ -0,0 +1,7 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
en_GB:
|
|
@ -0,0 +1,364 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
es:
|
||||
js:
|
||||
discourse_automation:
|
||||
title: Automatización
|
||||
create: Crear
|
||||
update: Actualizar
|
||||
select_script: Selecciona un script
|
||||
select_trigger: Selecciona un activador
|
||||
confirm_automation_reset: Esta acción restablecerá las opciones del script y del activador, y se guardará el nuevo estado. ¿Quieres continuar?
|
||||
confirm_automation_trigger: Esta acción activará la automatización. ¿Quieres continuar?
|
||||
no_automation_yet: Todavía no has creado ninguna automatización.
|
||||
edit_automation:
|
||||
trigger_section:
|
||||
forced: Este activador está forzado por script.
|
||||
next_pending_automation: "La siguiente automatización se activará el: %{date}"
|
||||
trigger_now: "Activar ahora"
|
||||
title: Cuándo/qué…
|
||||
fields_section:
|
||||
title: Opciones del script
|
||||
destroy_automation:
|
||||
confirm: "¿Seguro que quieres eliminar «%{name}»?"
|
||||
fields:
|
||||
key_value:
|
||||
label:
|
||||
one: Editar configuración (%{count})
|
||||
other: Editar configuración (%{count})
|
||||
user:
|
||||
label: Usuario
|
||||
pm:
|
||||
title:
|
||||
label: Título
|
||||
raw:
|
||||
label: Cuerpo
|
||||
pms:
|
||||
confirm_remove_pm: "¿Seguro que quieres eliminar este MP?"
|
||||
placeholder_title: Título del mensaje
|
||||
add_pm: Añadir mensaje
|
||||
no_pm_created: Todavía no has creado ningún MP. Los MP se enviarán una vez que se active tu automatización.
|
||||
title:
|
||||
label: Título
|
||||
raw:
|
||||
label: Cuerpo
|
||||
delay:
|
||||
label: Retardo (en minutos)
|
||||
prefers_encrypt:
|
||||
label: Cifra el MP cuando sea posible
|
||||
group:
|
||||
label: Grupo
|
||||
text:
|
||||
label: Texto
|
||||
triggerables:
|
||||
not_found: No se ha podido encontrar el activador «%{trigger}» para la automatización «%{automation}», asegúrate de que el plugin asociado está instalado
|
||||
user_badge_granted:
|
||||
fields:
|
||||
badge:
|
||||
label: Insignia
|
||||
only_first_grant:
|
||||
label: Solo en la primera concesión
|
||||
stalled_topic:
|
||||
durations:
|
||||
PT1H: "Una hora"
|
||||
P1D: "Un día"
|
||||
P1W: "Una semana"
|
||||
P2W: "Dos semanas"
|
||||
P1M: "Un mes"
|
||||
P3M: "Tres meses"
|
||||
P6M: "Seis meses"
|
||||
P1Y: "Un año"
|
||||
fields:
|
||||
categories:
|
||||
label: Limitado a las categorías
|
||||
tags:
|
||||
label: Limitado a las etiquetas
|
||||
stalled_after:
|
||||
label: Estancado tras
|
||||
recurring:
|
||||
every: Cada
|
||||
frequencies:
|
||||
minute: minuto
|
||||
hour: hora
|
||||
day: día
|
||||
weekday: día laborable (lunes a viernes)
|
||||
week: semana
|
||||
month: mes
|
||||
year: año
|
||||
fields:
|
||||
recurrence:
|
||||
label: Frecuencia
|
||||
start_date:
|
||||
label: Fecha de inicio
|
||||
stalled_wiki:
|
||||
durations:
|
||||
PT1H: "Una hora"
|
||||
P1D: "Un día"
|
||||
P1W: "Una semana"
|
||||
P2W: "Dos semanas"
|
||||
P1M: "Un mes"
|
||||
P3M: "Tres meses"
|
||||
P6M: "Seis meses"
|
||||
P1Y: "Un año"
|
||||
fields:
|
||||
restricted_category:
|
||||
label: Restringido a la categoría
|
||||
stalled_after:
|
||||
label: Retardo de activación
|
||||
description: Define el retraso entre la última edición del wiki y la activación de la automatización
|
||||
retriggered_after:
|
||||
label: Retardo antes de volver a activar
|
||||
description: Define el retraso entre la primera activación y la siguiente, si la wiki todavía no ha sido editada desde la primera activación
|
||||
user_added_to_group:
|
||||
fields:
|
||||
joined_group:
|
||||
label: Grupo rastreado
|
||||
user_removed_from_group:
|
||||
fields:
|
||||
left_group:
|
||||
label: Grupo rastreado
|
||||
user_promoted:
|
||||
fields:
|
||||
restricted_group:
|
||||
label: Restringir al grupo
|
||||
trust_level_transition:
|
||||
label: Transición a nivel de confianza
|
||||
trust_levels:
|
||||
ALL: "Todos los niveles de confianza"
|
||||
TL01: "nivel 0 a nivel 1"
|
||||
TL12: "nivel 1 a nivel 2"
|
||||
TL23: "nivel 2 a nivel 3"
|
||||
TL34: "nivel 3 a nivel 4"
|
||||
point_in_time:
|
||||
fields:
|
||||
execute_at:
|
||||
label: Ejecutar en
|
||||
topic:
|
||||
fields:
|
||||
restricted_topic:
|
||||
label: ID del tema
|
||||
post_created_edited:
|
||||
fields:
|
||||
action_type:
|
||||
label: Tipo de acción
|
||||
description: "Opcional, limite la activación solo a eventos creados o editados"
|
||||
valid_trust_levels:
|
||||
label: Niveles de confianza válidos
|
||||
description: Se activará solo si la publicación es creada por un usuario en estos niveles de confianza. Por defecto, cualquier nivel de confianza
|
||||
restricted_category:
|
||||
label: Categoría
|
||||
description: Opcional, solo se activará si el tema de la publicación está en esta categoría
|
||||
restricted_group:
|
||||
label: Grupo
|
||||
description: Opcional, solo se activará si el tema de la publicación es un mensaje privado en la bandeja de entrada de este grupo
|
||||
ignore_group_members:
|
||||
label: Ignorar a los miembros del grupo
|
||||
description: Omitir el activador si el remitente es un miembro del Grupo especificado anteriormente
|
||||
ignore_automated:
|
||||
label: Ignorar la automatización
|
||||
description: Omite el activador si el remitente tiene un correo electrónico de no respuesta o procede de una fuente automatizada. Solo se aplica a las publicaciones creadas por correo electrónico
|
||||
first_post_only:
|
||||
label: Solo la primera publicación
|
||||
description: Se activará solo si la publicación es la primera que creó un usuario.
|
||||
first_topic_only:
|
||||
label: Solo el primer tema
|
||||
description: Se activará solo si el tema es el primer tema que creó un usuario
|
||||
created: Creado
|
||||
edited: Editado
|
||||
category_created_edited:
|
||||
fields:
|
||||
restricted_category:
|
||||
label: Categoría principal
|
||||
description: Opcional, permite limitar la ejecución del activador a esta categoría
|
||||
pm_created:
|
||||
fields:
|
||||
restricted_user:
|
||||
label: Usuarios
|
||||
description: Se activará solo para los MP enviados a este usuario
|
||||
restricted_group:
|
||||
label: Grupo
|
||||
description: Se activará solo para los MP enviados a este grupo
|
||||
ignore_staff:
|
||||
label: Ignorar personal
|
||||
description: Omitir la activación si el remitente es un usuario del personal
|
||||
ignore_group_members:
|
||||
label: Ignorar a los miembros del grupo
|
||||
description: Omitir el activador si el remitente es un miembro del Grupo especificado anteriormente
|
||||
ignore_automated:
|
||||
label: Ignorar la automatización
|
||||
description: Omite el activador si el remitente tiene un correo electrónico de no respuesta o procede de una fuente automatizada. Solo se aplica a los MP creados por correo electrónico
|
||||
valid_trust_levels:
|
||||
label: Niveles de confianza válidos
|
||||
description: Se activará solo si la publicación es creada por un usuario en estos niveles de confianza. Por defecto, cualquier nivel de confianza
|
||||
after_post_cook:
|
||||
fields:
|
||||
valid_trust_levels:
|
||||
label: Niveles de confianza válidos
|
||||
description: Se activará solo si la publicación es creada por un usuario en estos niveles de confianza. Por defecto, cualquier nivel de confianza
|
||||
restricted_category:
|
||||
label: Categoría
|
||||
description: Opcional, solo se activará si el tema de la publicación está en esta categoría
|
||||
restricted_tags:
|
||||
label: Etiquetas
|
||||
description: Opcional, solo se activará si la publicación tiene alguna de estas etiquetas
|
||||
scriptables:
|
||||
not_found: No se ha podido encontrar el script «%{script}» para la automatización «%{automation}», asegúrate de que el plugin asociado está instalado
|
||||
zapier_webhook:
|
||||
fields:
|
||||
webhook_url:
|
||||
label: URL del webhook
|
||||
description: "Espera una URL válida del webhook de Zapier, por ejemplo: https://hooks.zapier.com/hooks/catch/xxx/yyy/"
|
||||
auto_responder:
|
||||
fields:
|
||||
once:
|
||||
label: Una vez
|
||||
description: Solo responde una vez por tema
|
||||
word_answer_list:
|
||||
label: Lista de pares de palabras/respuestas
|
||||
answering_user:
|
||||
label: Usuario que responde
|
||||
description: Por defecto, usuario del sistema
|
||||
auto_tag_topic:
|
||||
fields:
|
||||
tags:
|
||||
label: Etiquetas
|
||||
description: Lista de etiquetas para añadir al tema.
|
||||
post:
|
||||
fields:
|
||||
creator:
|
||||
label: Creador
|
||||
topic:
|
||||
label: ID del tema
|
||||
post:
|
||||
label: Contenido de la publicación
|
||||
group_category_notification_default:
|
||||
fields:
|
||||
group:
|
||||
label: Grupo
|
||||
notification_level:
|
||||
label: Nivel de notificación
|
||||
update_existing_members:
|
||||
label: Actualizar miembros existentes
|
||||
description: Actualiza el nivel de notificación de los miembros del grupo existentes
|
||||
user_global_notice:
|
||||
fields:
|
||||
level:
|
||||
label: Nivel
|
||||
notice:
|
||||
label: Aviso
|
||||
description: Acepta HTML, ¡no incluyas contenido inseguro o de terceros!
|
||||
levels:
|
||||
warning: Advertencia
|
||||
info: Información
|
||||
success: Éxito
|
||||
error: Error
|
||||
user_group_membership_through_badge:
|
||||
fields:
|
||||
badge_name:
|
||||
label: Nombre de la insignia
|
||||
group:
|
||||
label: Grupo
|
||||
description: Grupo objetivo. Los usuarios con la insignia especificada se añadirán a este grupo
|
||||
update_user_title_and_flair:
|
||||
label: Actualizar el título y el estilo del usuario
|
||||
description: Opcional, actualizar el título y el estilo del usuario
|
||||
remove_members_without_badge:
|
||||
label: Eliminar miembros existentes sin insignia
|
||||
description: Opcional, eliminar miembros del grupo existentes sin la insignia especificada
|
||||
badge:
|
||||
label: Insignia
|
||||
description: Seleccionar insignia
|
||||
suspend_user_by_email:
|
||||
fields:
|
||||
suspend_until:
|
||||
label: Suspender hasta (por defecto)
|
||||
reason:
|
||||
label: Motivo (por defecto)
|
||||
actor:
|
||||
label: Usuario
|
||||
description: "Usuario responsable de la suspensión (por defecto: system)"
|
||||
pin_topic:
|
||||
fields:
|
||||
pinnable_topic:
|
||||
label: ID del tema
|
||||
pinned_globally:
|
||||
label: Fijado globalmente
|
||||
pinned_until:
|
||||
label: Fijado hasta
|
||||
banner_topic:
|
||||
fields:
|
||||
topic_id:
|
||||
label: ID del tema
|
||||
banner_until:
|
||||
label: Hacer banner hasta
|
||||
user:
|
||||
label: Usuario
|
||||
description: "Usuario que crea el banner (por defecto: system)"
|
||||
flag_post_on_words:
|
||||
fields:
|
||||
words:
|
||||
label: Palabras revisadas
|
||||
topic_required_words:
|
||||
fields:
|
||||
words:
|
||||
label: Lista de palabras necesarias
|
||||
gift_exchange:
|
||||
fields:
|
||||
gift_exchangers_group:
|
||||
label: Nombre del grupo de participantes
|
||||
giftee_assignment_messages:
|
||||
label: Mensajes enviados a quien regala
|
||||
send_pms:
|
||||
add_a_pm_btn:
|
||||
label: Añadir un MP
|
||||
fields:
|
||||
receiver:
|
||||
label: Destinatario del MP
|
||||
sendable_pms:
|
||||
label: MPs
|
||||
sender:
|
||||
label: Remitente del MP
|
||||
close_topic:
|
||||
fields:
|
||||
topic:
|
||||
label: ID del tema
|
||||
message:
|
||||
label: Mensaje de cierre
|
||||
description: "Mensaje opcional para mostrar en el indicador al cerrar el tema"
|
||||
user:
|
||||
label: Usuario
|
||||
description: "Usuario que cierra el tema (por defecto: system)"
|
||||
add_user_to_group_through_custom_field:
|
||||
fields:
|
||||
custom_field_name:
|
||||
label: "Nombre del campo personalizado del usuario"
|
||||
models:
|
||||
script:
|
||||
name:
|
||||
label: Script
|
||||
trigger:
|
||||
name:
|
||||
label: Activador
|
||||
automation:
|
||||
name:
|
||||
label: Nombre
|
||||
trigger:
|
||||
label: Activador
|
||||
script:
|
||||
label: Script
|
||||
version:
|
||||
label: Versión
|
||||
enabled:
|
||||
label: Activado
|
||||
disabled:
|
||||
label: Desactivado
|
||||
placeholders:
|
||||
label: Marcadores de posición
|
||||
last_updated_at:
|
||||
label: Última actualización
|
||||
last_updated_by:
|
||||
label: Actualizado por
|
|
@ -0,0 +1,7 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
et:
|
|
@ -0,0 +1,187 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
fa_IR:
|
||||
js:
|
||||
discourse_automation:
|
||||
title: اتوماسیون
|
||||
create: ایجاد
|
||||
update: بهروزرسانی
|
||||
destroy_automation:
|
||||
confirm: "آیا مطمئنید هستید که میخواهید «%{name}» را حذف کنید؟"
|
||||
fields:
|
||||
user:
|
||||
label: کاربر
|
||||
pm:
|
||||
title:
|
||||
label: عنوان
|
||||
raw:
|
||||
label: بدنه
|
||||
pms:
|
||||
title:
|
||||
label: عنوان
|
||||
raw:
|
||||
label: بدنه
|
||||
delay:
|
||||
label: تاخیر (دقیقه)
|
||||
group:
|
||||
label: گروه
|
||||
text:
|
||||
label: متن
|
||||
triggerables:
|
||||
user_badge_granted:
|
||||
fields:
|
||||
badge:
|
||||
label: نشان
|
||||
stalled_topic:
|
||||
durations:
|
||||
PT1H: "یک ساعت"
|
||||
P1D: "یک روز"
|
||||
P1W: "یک هفته"
|
||||
P2W: "دو هفته"
|
||||
P1M: "یک ماه"
|
||||
P3M: "سه ماه"
|
||||
P6M: "شش ماه"
|
||||
P1Y: "یک سال"
|
||||
fields:
|
||||
categories:
|
||||
label: محدود شده به دستهبندیها
|
||||
tags:
|
||||
label: محدود شده به برچسبها
|
||||
recurring:
|
||||
every: هر
|
||||
frequencies:
|
||||
minute: دقیقه
|
||||
hour: ساعت
|
||||
day: روز
|
||||
weekday: روز هفته
|
||||
week: هفته
|
||||
month: ماه
|
||||
year: سال
|
||||
fields:
|
||||
start_date:
|
||||
label: تاریخ شروع
|
||||
stalled_wiki:
|
||||
durations:
|
||||
PT1H: "یک ساعت"
|
||||
P1D: "یک روز"
|
||||
P1W: "یک هفته"
|
||||
P2W: "دو هفته"
|
||||
P1M: "یک ماه"
|
||||
P3M: "سه ماه"
|
||||
P6M: "شش ماه"
|
||||
P1Y: "یک سال"
|
||||
fields:
|
||||
restricted_category:
|
||||
label: محدود به دستهبندی
|
||||
topic:
|
||||
fields:
|
||||
restricted_topic:
|
||||
label: شناسه موضوع
|
||||
post_created_edited:
|
||||
fields:
|
||||
action_type:
|
||||
label: نوع عمل
|
||||
restricted_category:
|
||||
label: دستهبندی
|
||||
restricted_group:
|
||||
label: گروه
|
||||
created: ایجاد شده
|
||||
edited: ویرایش شده
|
||||
pm_created:
|
||||
fields:
|
||||
restricted_group:
|
||||
label: گروه
|
||||
after_post_cook:
|
||||
fields:
|
||||
restricted_category:
|
||||
label: دستهبندی
|
||||
restricted_tags:
|
||||
label: برچسبها
|
||||
scriptables:
|
||||
auto_responder:
|
||||
fields:
|
||||
once:
|
||||
label: یک بار
|
||||
description: فقط یک بار بر اساس موضوع پاسخ میدهد
|
||||
auto_tag_topic:
|
||||
fields:
|
||||
tags:
|
||||
label: برچسبها
|
||||
post:
|
||||
fields:
|
||||
creator:
|
||||
label: ایجاد کننده
|
||||
topic:
|
||||
label: شناسه موضوع
|
||||
post:
|
||||
label: محتوای نوشته
|
||||
group_category_notification_default:
|
||||
fields:
|
||||
group:
|
||||
label: گروه
|
||||
user_global_notice:
|
||||
fields:
|
||||
level:
|
||||
label: سطح
|
||||
levels:
|
||||
warning: هشدار
|
||||
info: اطلاعات
|
||||
success: موفقیت
|
||||
error: خطا
|
||||
user_group_membership_through_badge:
|
||||
fields:
|
||||
group:
|
||||
label: گروه
|
||||
badge:
|
||||
label: نشان
|
||||
suspend_user_by_email:
|
||||
fields:
|
||||
reason:
|
||||
label: دلیل (پیشفرض)
|
||||
actor:
|
||||
label: کاربر
|
||||
pin_topic:
|
||||
fields:
|
||||
pinnable_topic:
|
||||
label: شناسه موضوع
|
||||
pinned_until:
|
||||
label: سنجاق شده تا
|
||||
banner_topic:
|
||||
fields:
|
||||
topic_id:
|
||||
label: شناسه موضوع
|
||||
user:
|
||||
label: کاربر
|
||||
close_topic:
|
||||
fields:
|
||||
topic:
|
||||
label: شناسه موضوع
|
||||
user:
|
||||
label: کاربر
|
||||
add_user_to_group_through_custom_field:
|
||||
fields:
|
||||
custom_field_name:
|
||||
label: "نام فیلد سفارشی کاربر"
|
||||
models:
|
||||
script:
|
||||
name:
|
||||
label: اسکریپت
|
||||
automation:
|
||||
name:
|
||||
label: نام
|
||||
script:
|
||||
label: اسکریپت
|
||||
version:
|
||||
label: نسخه
|
||||
enabled:
|
||||
label: فعال شد
|
||||
disabled:
|
||||
label: غیرفعال شد
|
||||
last_updated_at:
|
||||
label: آخرین بهروزرسانی
|
||||
last_updated_by:
|
||||
label: بهروز شده توسط
|
|
@ -0,0 +1,364 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
fi:
|
||||
js:
|
||||
discourse_automation:
|
||||
title: Automaatio
|
||||
create: Luo
|
||||
update: Päivitä
|
||||
select_script: Valitse skripti
|
||||
select_trigger: Valitse triggeri
|
||||
confirm_automation_reset: Tämä toiminto nollaa skripti- ja triggeriasetukset, uusi tila tallennetaan. Haluatko jatkaa?
|
||||
confirm_automation_trigger: Tämä toiminto käynnistää automaation, haluatko jatkaa?
|
||||
no_automation_yet: Et ole vielä luonut automaatioita.
|
||||
edit_automation:
|
||||
trigger_section:
|
||||
forced: Tämä triggeri on skriptin pakottama.
|
||||
next_pending_automation: "Seuraava automaatio käynnistyy: %{date}"
|
||||
trigger_now: "Käynnistä nyt"
|
||||
title: Milloin/mitä...
|
||||
fields_section:
|
||||
title: Skriptiasetukset
|
||||
destroy_automation:
|
||||
confirm: "Haluatko varmasti poistaa kohteen \"%{name}\"?"
|
||||
fields:
|
||||
key_value:
|
||||
label:
|
||||
one: Muokkaa määritystä (%{count})
|
||||
other: Muokkaa määritystä (%{count})
|
||||
user:
|
||||
label: Käyttäjä
|
||||
pm:
|
||||
title:
|
||||
label: Otsikko
|
||||
raw:
|
||||
label: Teksti
|
||||
pms:
|
||||
confirm_remove_pm: "Haluatko varmasti poistaa tämän yksityisviestin?"
|
||||
placeholder_title: Yksityisviestin otsikko
|
||||
add_pm: Lisää yksityisviesti
|
||||
no_pm_created: Et ole vielä luonut yksityisviestejä. Yksityisviestit lähetetään, kun automaatio käynnistyy.
|
||||
title:
|
||||
label: Otsikko
|
||||
raw:
|
||||
label: Teksti
|
||||
delay:
|
||||
label: Viive (minuutteina)
|
||||
prefers_encrypt:
|
||||
label: Salaa yksityisviestin, jos käytettävissä
|
||||
group:
|
||||
label: Ryhmä
|
||||
text:
|
||||
label: Teksti
|
||||
triggerables:
|
||||
not_found: Triggeriä %{trigger} automaatiolle %{automation} ei löytynyt. Varmista, että siihen liittyvä lisäosa on asennettuna.
|
||||
user_badge_granted:
|
||||
fields:
|
||||
badge:
|
||||
label: Kunniamerkki
|
||||
only_first_grant:
|
||||
label: Vain ensimmäisellä myöntämiskerralla
|
||||
stalled_topic:
|
||||
durations:
|
||||
PT1H: "Yksi tunti"
|
||||
P1D: "Yksi päivä"
|
||||
P1W: "Yksi viikko"
|
||||
P2W: "Kaksi viikkoa"
|
||||
P1M: "Yksi kuukausi"
|
||||
P3M: "Kolme kuukautta"
|
||||
P6M: "Kuusi kuukautta"
|
||||
P1Y: "Yksi vuosi"
|
||||
fields:
|
||||
categories:
|
||||
label: Rajoitettu alueisiin
|
||||
tags:
|
||||
label: Rajoitettu tunnisteisiin
|
||||
stalled_after:
|
||||
label: Pysähtyi jälkeen
|
||||
recurring:
|
||||
every: Joka
|
||||
frequencies:
|
||||
minute: minuutti
|
||||
hour: tunti
|
||||
day: päivä
|
||||
weekday: arkipäivä
|
||||
week: viikko
|
||||
month: kuukausi
|
||||
year: vuosi
|
||||
fields:
|
||||
recurrence:
|
||||
label: Toistuminen
|
||||
start_date:
|
||||
label: Alkamispäivä
|
||||
stalled_wiki:
|
||||
durations:
|
||||
PT1H: "Yksi tunti"
|
||||
P1D: "Yksi päivä"
|
||||
P1W: "Yksi viikko"
|
||||
P2W: "Kaksi viikkoa"
|
||||
P1M: "Yksi kuukausi"
|
||||
P3M: "Kolme kuukautta"
|
||||
P6M: "Kuusi kuukautta"
|
||||
P1Y: "Yksi vuosi"
|
||||
fields:
|
||||
restricted_category:
|
||||
label: Rajoitettu alueeseen
|
||||
stalled_after:
|
||||
label: Triggerin viive
|
||||
description: Määrittää viiveen viimeisimmän wiki-muokkauksen ja automaation triggerin välillä
|
||||
retriggered_after:
|
||||
label: Käynnistä viive uudelleen
|
||||
description: Määrittää viiveen ensimmäisen ja seuraavan triggerin välillä, jos wikiä ei ole vieläkään muokattu ensimmäisen triggerin jälkeen
|
||||
user_added_to_group:
|
||||
fields:
|
||||
joined_group:
|
||||
label: Seurattu ryhmä
|
||||
user_removed_from_group:
|
||||
fields:
|
||||
left_group:
|
||||
label: Seurattu ryhmä
|
||||
user_promoted:
|
||||
fields:
|
||||
restricted_group:
|
||||
label: Rajoita ryhmään
|
||||
trust_level_transition:
|
||||
label: Luottamustasosiirtymä
|
||||
trust_levels:
|
||||
ALL: "Kaikki luottamustasot"
|
||||
TL01: "LT0 → LT1"
|
||||
TL12: "LT1 → LT2"
|
||||
TL23: "LT2 → LT3"
|
||||
TL34: "LT3 → LT4"
|
||||
point_in_time:
|
||||
fields:
|
||||
execute_at:
|
||||
label: Suorita
|
||||
topic:
|
||||
fields:
|
||||
restricted_topic:
|
||||
label: Ketjun tunnus
|
||||
post_created_edited:
|
||||
fields:
|
||||
action_type:
|
||||
label: Toiminnon tyyppi
|
||||
description: "Valinnainen, rajaa käynnistyksen vain luomis- tai muokkaustapahtumiin"
|
||||
valid_trust_levels:
|
||||
label: Kelvolliset luottamustasot
|
||||
description: Käynnistyy vain, jos viestin on luonut käyttäjä, jonka luottamustaso on jokin näistä, oletusarvo on mikä tahansa luottamustaso
|
||||
restricted_category:
|
||||
label: Alue
|
||||
description: Valinnainen, käynnistyy vain, jos viestin ketju on tällä alueella
|
||||
restricted_group:
|
||||
label: Ryhmä
|
||||
description: Valinnainen, käynnistyy vain, jos viestin ketju on yksityisviesti tämän ryhmän postilaatikossa
|
||||
ignore_group_members:
|
||||
label: Ohita ryhmän jäsenet
|
||||
description: Ohita triggeri, jos lähettäjä on edellä määritellyn ryhmän jäsen
|
||||
ignore_automated:
|
||||
label: Ohita automaattiset
|
||||
description: Ohita triggeri, jos lähettäjällä on sähköpostiosoite, joka ei ota vastaan vastauksia, tai se on automatisoidusta lähteestä. Koskee vain sähköpostitse luotuja viestejä.
|
||||
first_post_only:
|
||||
label: Vain ensimmäinen viesti
|
||||
description: Käynnistyy vain, jos viesti on ensimmäinen käyttäjän luoma viesti
|
||||
first_topic_only:
|
||||
label: Vain ensimmäinen ketju
|
||||
description: Käynnistyy vain, jos ketju on ensimmäinen käyttäjän luoma ketju
|
||||
created: Luotu
|
||||
edited: Muokattu
|
||||
category_created_edited:
|
||||
fields:
|
||||
restricted_category:
|
||||
label: Ylätason alue
|
||||
description: Valinnainen, mahdollistaa triggerin suorittamisen rajoittamisen tähän alueeseen
|
||||
pm_created:
|
||||
fields:
|
||||
restricted_user:
|
||||
label: Käyttäjät
|
||||
description: Käynnistyy vain tälle käyttäjälle lähetetyille yksityisviesteille
|
||||
restricted_group:
|
||||
label: Ryhmä
|
||||
description: Käynnistyy vain tälle ryhmälle lähetetyille yksityisviesteille
|
||||
ignore_staff:
|
||||
label: Ohita henkilökunta
|
||||
description: Ohita triggeri, jos lähettäjä on henkilökunnan käyttäjä
|
||||
ignore_group_members:
|
||||
label: Ohita ryhmän jäsenet
|
||||
description: Ohita triggeri, jos lähettäjä on edellä määritellyn ryhmän jäsen
|
||||
ignore_automated:
|
||||
label: Ohita automaattiset
|
||||
description: Ohita triggeri, jos lähettäjällä on sähköpostiosoite, joka ei ota vastaan vastauksia, tai se on automatisoidusta lähteestä. Koskee vain sähköpostitse luotuja yksityisviestejä.
|
||||
valid_trust_levels:
|
||||
label: Kelvolliset luottamustasot
|
||||
description: Käynnistyy vain, jos viestin on luonut käyttäjä, jonka luottamustaso on jokin näistä; oletusarvo on mikä tahansa luottamustaso
|
||||
after_post_cook:
|
||||
fields:
|
||||
valid_trust_levels:
|
||||
label: Kelvolliset luottamustasot
|
||||
description: Käynnistyy vain, jos viestin on luonut käyttäjä, jonka luottamustaso on jokin näistä; oletusarvo on mikä tahansa luottamustaso
|
||||
restricted_category:
|
||||
label: Alue
|
||||
description: Valinnainen, käynnistyy vain, jos viestin ketju on tällä alueella
|
||||
restricted_tags:
|
||||
label: Tunnisteet
|
||||
description: Valinnainen, käynnistyy vain, jos viestillä on jokin näistä tunnisteista
|
||||
scriptables:
|
||||
not_found: Skriptiä %{script} automaatiolle %{automation} ei löytynyt. Varmista, että siihen liittyvä lisäosa on asennettuna.
|
||||
zapier_webhook:
|
||||
fields:
|
||||
webhook_url:
|
||||
label: Webhookin URL-osoite
|
||||
description: "Odottaa kelvollista Zapierin webhook-URL-osoitetta, esim. https://hooks.zapier.com/hooks/catch/xxx/yyy/"
|
||||
auto_responder:
|
||||
fields:
|
||||
once:
|
||||
label: Kerran
|
||||
description: Vastaa vain kerran ketjua kohden
|
||||
word_answer_list:
|
||||
label: Sana ja vastaus -parien luettelo
|
||||
answering_user:
|
||||
label: Vastaava käyttäjä
|
||||
description: Oletuksena järjestelmäkäyttäjä
|
||||
auto_tag_topic:
|
||||
fields:
|
||||
tags:
|
||||
label: Tunnisteet
|
||||
description: Luettelo ketjuun lisättävistä tunnisteista.
|
||||
post:
|
||||
fields:
|
||||
creator:
|
||||
label: Luoja
|
||||
topic:
|
||||
label: Ketjun tunnus
|
||||
post:
|
||||
label: Viestin sisältö
|
||||
group_category_notification_default:
|
||||
fields:
|
||||
group:
|
||||
label: Ryhmä
|
||||
notification_level:
|
||||
label: Ilmoitustaso
|
||||
update_existing_members:
|
||||
label: Päivitä nykyiset jäsenet
|
||||
description: Päivittää ryhmän nykyisten jäsenten ilmoitustason
|
||||
user_global_notice:
|
||||
fields:
|
||||
level:
|
||||
label: Taso
|
||||
notice:
|
||||
label: Ilmoitus
|
||||
description: Hyväksyy HTML:n, älä täytä tätä epäluotettavalla syötteellä!
|
||||
levels:
|
||||
warning: Varoitus
|
||||
info: Info
|
||||
success: Onnistuminen
|
||||
error: Virhe
|
||||
user_group_membership_through_badge:
|
||||
fields:
|
||||
badge_name:
|
||||
label: Kunniamerkin nimi
|
||||
group:
|
||||
label: Ryhmä
|
||||
description: Kohderyhmä. Käyttäjät, joilla on määritetty kunniamerkki, lisätään tähän ryhmään
|
||||
update_user_title_and_flair:
|
||||
label: Päivitä käyttäjän nimike ja flair
|
||||
description: Valinnainen, päivitä käyttäjän nimike ja flair
|
||||
remove_members_without_badge:
|
||||
label: Poista olemassa olevat jäsenet ilman kunniamerkkiä
|
||||
description: Valinnainen, poista olemassa olevat ryhmän jäsenet ilman määritettyä kunniamerkkiä
|
||||
badge:
|
||||
label: Kunniamerkki
|
||||
description: Valitse kunniaerkki
|
||||
suspend_user_by_email:
|
||||
fields:
|
||||
suspend_until:
|
||||
label: Keskeytä asti (oletus)
|
||||
reason:
|
||||
label: Syy (oletus)
|
||||
actor:
|
||||
label: Käyttäjä
|
||||
description: "Keskeytyksestä vastaava käyttäjä (oletus: järjestelmä)"
|
||||
pin_topic:
|
||||
fields:
|
||||
pinnable_topic:
|
||||
label: Ketjun tunnus
|
||||
pinned_globally:
|
||||
label: Kiinnitetty yleisesti
|
||||
pinned_until:
|
||||
label: Kiinnitetty asti
|
||||
banner_topic:
|
||||
fields:
|
||||
topic_id:
|
||||
label: Ketjun tunnus
|
||||
banner_until:
|
||||
label: Tee banneriksi asti
|
||||
user:
|
||||
label: Käyttäjä
|
||||
description: "Bannerin luova käyttäjä (oletus: järjestelmä)"
|
||||
flag_post_on_words:
|
||||
fields:
|
||||
words:
|
||||
label: Tarkistetut sanat
|
||||
topic_required_words:
|
||||
fields:
|
||||
words:
|
||||
label: Pakollisten sanojen luettelo
|
||||
gift_exchange:
|
||||
fields:
|
||||
gift_exchangers_group:
|
||||
label: Osallistujien ryhmän nimi
|
||||
giftee_assignment_messages:
|
||||
label: Viestit lähetetty lahjanantajalle
|
||||
send_pms:
|
||||
add_a_pm_btn:
|
||||
label: Lisää yksityisviesti
|
||||
fields:
|
||||
receiver:
|
||||
label: Yksityisviestin vastaanottaja
|
||||
sendable_pms:
|
||||
label: Yksityisviestit
|
||||
sender:
|
||||
label: Yksityisviestien lähettäjä
|
||||
close_topic:
|
||||
fields:
|
||||
topic:
|
||||
label: Ketjun tunnus
|
||||
message:
|
||||
label: Sulkemisviesti
|
||||
description: "Valinnainen viesti, joka näytetään ketju suljettu -tietueessa"
|
||||
user:
|
||||
label: Käyttäjä
|
||||
description: "Ketjun sulkeva käyttäjä (oletus: järjestelmä)"
|
||||
add_user_to_group_through_custom_field:
|
||||
fields:
|
||||
custom_field_name:
|
||||
label: "Käyttäjän mukautetun kentän nimi"
|
||||
models:
|
||||
script:
|
||||
name:
|
||||
label: Skripti
|
||||
trigger:
|
||||
name:
|
||||
label: Triggeri
|
||||
automation:
|
||||
name:
|
||||
label: Nimi
|
||||
trigger:
|
||||
label: Triggeri
|
||||
script:
|
||||
label: Skripti
|
||||
version:
|
||||
label: Versio
|
||||
enabled:
|
||||
label: Käytössä
|
||||
disabled:
|
||||
label: Ei käytössä
|
||||
placeholders:
|
||||
label: Paikkamerkit
|
||||
last_updated_at:
|
||||
label: Viimeisin päivitys
|
||||
last_updated_by:
|
||||
label: Päivittänyt
|
|
@ -0,0 +1,364 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
fr:
|
||||
js:
|
||||
discourse_automation:
|
||||
title: Automatisation
|
||||
create: Créer
|
||||
update: Actualiser
|
||||
select_script: Sélectionner un script
|
||||
select_trigger: Sélectionner un déclencheur
|
||||
confirm_automation_reset: Cette action réinitialisera les options de script et de déclencheur, le nouvel état sera enregistré, voulez-vous continuer ?
|
||||
confirm_automation_trigger: Cette action déclenchera l'automatisation, voulez-vous continuer ?
|
||||
no_automation_yet: Vous n'avez pas encore créé d'automatisation.
|
||||
edit_automation:
|
||||
trigger_section:
|
||||
forced: Ce déclencheur est forcé par le script.
|
||||
next_pending_automation: "La prochaine automatisation se déclenchera le : %{date}"
|
||||
trigger_now: "Déclencher maintenant"
|
||||
title: Quand/quoi…
|
||||
fields_section:
|
||||
title: Options de script
|
||||
destroy_automation:
|
||||
confirm: "Voulez-vous vraiment supprimer « %{name} » ?"
|
||||
fields:
|
||||
key_value:
|
||||
label:
|
||||
one: Modifier la configuration (%{count})
|
||||
other: Modifier la configuration (%{count})
|
||||
user:
|
||||
label: Utilisateur
|
||||
pm:
|
||||
title:
|
||||
label: Titre
|
||||
raw:
|
||||
label: Corps
|
||||
pms:
|
||||
confirm_remove_pm: "Voulez-vous vraiment supprimer ce MP ?"
|
||||
placeholder_title: Titre du MP
|
||||
add_pm: Ajouter un MP
|
||||
no_pm_created: Vous n'avez pas encore créé de MP. Les MP seront envoyés une fois votre automatisation déclenchée.
|
||||
title:
|
||||
label: Titre
|
||||
raw:
|
||||
label: Corps
|
||||
delay:
|
||||
label: Délai (minutes)
|
||||
prefers_encrypt:
|
||||
label: Chiffrer le MP si disponible
|
||||
group:
|
||||
label: Groupe
|
||||
text:
|
||||
label: Texte
|
||||
triggerables:
|
||||
not_found: Impossible de trouver le déclencheur « %{trigger} » pour l'automatisation « %{automation} », assurez-vous que l'extension associée est installée
|
||||
user_badge_granted:
|
||||
fields:
|
||||
badge:
|
||||
label: Badge
|
||||
only_first_grant:
|
||||
label: Uniquement lors de la première autorisation
|
||||
stalled_topic:
|
||||
durations:
|
||||
PT1H: "Une heure"
|
||||
P1D: "Un jour"
|
||||
P1W: "Une semaine"
|
||||
P2W: "Deux semaines"
|
||||
P1M: "Un mois"
|
||||
P3M: "Trois mois"
|
||||
P6M: "Six mois"
|
||||
P1Y: "Un an"
|
||||
fields:
|
||||
categories:
|
||||
label: Limité aux catégories
|
||||
tags:
|
||||
label: Limité aux étiquettes
|
||||
stalled_after:
|
||||
label: Bloqué après
|
||||
recurring:
|
||||
every: Chaque
|
||||
frequencies:
|
||||
minute: minute
|
||||
hour: heure
|
||||
day: jour
|
||||
weekday: jour de la semaine
|
||||
week: semaine
|
||||
month: mois
|
||||
year: année
|
||||
fields:
|
||||
recurrence:
|
||||
label: Récurrence
|
||||
start_date:
|
||||
label: Date de début
|
||||
stalled_wiki:
|
||||
durations:
|
||||
PT1H: "Une heure"
|
||||
P1D: "Un jour"
|
||||
P1W: "Une semaine"
|
||||
P2W: "Deux semaines"
|
||||
P1M: "Un mois"
|
||||
P3M: "Trois mois"
|
||||
P6M: "Six mois"
|
||||
P1Y: "Un an"
|
||||
fields:
|
||||
restricted_category:
|
||||
label: Limité à la catégorie
|
||||
stalled_after:
|
||||
label: Délai de déclenchement
|
||||
description: Définit le délai entre la dernière modification du wiki et le déclenchement de l'automatisation
|
||||
retriggered_after:
|
||||
label: Délai de redéclenchement
|
||||
description: Définit le délai entre le premier déclencheur et le déclencheur suivant, si le wiki n'a toujours pas été modifié après le premier déclencheur
|
||||
user_added_to_group:
|
||||
fields:
|
||||
joined_group:
|
||||
label: Groupe suivi
|
||||
user_removed_from_group:
|
||||
fields:
|
||||
left_group:
|
||||
label: Groupe suivi
|
||||
user_promoted:
|
||||
fields:
|
||||
restricted_group:
|
||||
label: Restreindre au groupe
|
||||
trust_level_transition:
|
||||
label: Transition de niveau de confiance
|
||||
trust_levels:
|
||||
ALL: "Tous les niveaux de confiance"
|
||||
TL01: "TL0 à TL1"
|
||||
TL12: "TL1 à TL2"
|
||||
TL23: "TL2 à TL3"
|
||||
TL34: "TL3 à TL4"
|
||||
point_in_time:
|
||||
fields:
|
||||
execute_at:
|
||||
label: Exécuter à
|
||||
topic:
|
||||
fields:
|
||||
restricted_topic:
|
||||
label: ID de sujet
|
||||
post_created_edited:
|
||||
fields:
|
||||
action_type:
|
||||
label: Type d'action
|
||||
description: "Facultatif, limitez le déclenchement aux seuls événements créés ou modifiés"
|
||||
valid_trust_levels:
|
||||
label: Niveaux de confiance valides
|
||||
description: Se déclenchera uniquement si la publication est créée par l'utilisateur dans ces niveaux de confiance. Cette valeur est fixée par défaut sur tous les niveaux de confiance
|
||||
restricted_category:
|
||||
label: Catégorie
|
||||
description: Facultatif, ne se déclenchera que si le sujet de la publication se trouve dans cette catégorie
|
||||
restricted_group:
|
||||
label: Groupe
|
||||
description: Facultatif, se déclenchera uniquement si le sujet de la publication est un message privé dans la boîte de réception de ce groupe
|
||||
ignore_group_members:
|
||||
label: Ignorer les membres du groupe
|
||||
description: Ignorer le déclencheur si l'expéditeur est membre du groupe spécifié ci-dessus
|
||||
ignore_automated:
|
||||
label: Ignorer l'automatisation
|
||||
description: Ignorer le déclencheur si l'expéditeur a un e-mail de non-réponse ou provient d'une source automatique. Ne s'applique qu'aux messages créés par e-mail
|
||||
first_post_only:
|
||||
label: Premier message uniquement
|
||||
description: Se déclenchera uniquement si la publication est la première publication créée par un utilisateur
|
||||
first_topic_only:
|
||||
label: Premier sujet uniquement
|
||||
description: Se déclenchera uniquement si le sujet est le premier sujet créé par un utilisateur
|
||||
created: Créé
|
||||
edited: Édité
|
||||
category_created_edited:
|
||||
fields:
|
||||
restricted_category:
|
||||
label: Catégorie parente
|
||||
description: Facultatif, permet de limiter l'exécution du déclencheur à cette catégorie
|
||||
pm_created:
|
||||
fields:
|
||||
restricted_user:
|
||||
label: Utilisateurs
|
||||
description: Se déclenchera uniquement pour les MP envoyés à cet utilisateur
|
||||
restricted_group:
|
||||
label: Groupe
|
||||
description: Se déclenchera uniquement pour les MP envoyés à ce groupe
|
||||
ignore_staff:
|
||||
label: Ignorer le responsable
|
||||
description: Ignorer le déclencheur si l'expéditeur est un responsable
|
||||
ignore_group_members:
|
||||
label: Ignorer les membres du groupe
|
||||
description: Ignorer le déclencheur si l'expéditeur est membre du groupe spécifié ci-dessus
|
||||
ignore_automated:
|
||||
label: Ignorer l'automatisation
|
||||
description: Ignorer le déclencheur si l'expéditeur a un e-mail de non-réponse ou provient d'une source automatique. Ne s'applique qu'aux MP créés par e-mail
|
||||
valid_trust_levels:
|
||||
label: Niveaux de confiance valides
|
||||
description: Se déclenchera uniquement si la publication est créée par l'utilisateur dans ces niveaux de confiance. Cette valeur est fixée par défaut sur tous les niveaux de confiance
|
||||
after_post_cook:
|
||||
fields:
|
||||
valid_trust_levels:
|
||||
label: Niveaux de confiance valides
|
||||
description: Se déclenchera uniquement si la publication est créée par l'utilisateur dans ces niveaux de confiance. Cette valeur est fixée par défaut sur tous les niveaux de confiance
|
||||
restricted_category:
|
||||
label: Catégorie
|
||||
description: Facultatif, ne se déclenchera que si le sujet de la publication se trouve dans cette catégorie
|
||||
restricted_tags:
|
||||
label: Étiquettes
|
||||
description: Facultatif, ne se déclenchera que si la publication contient l'une de ces étiquettes
|
||||
scriptables:
|
||||
not_found: Impossible de trouver le script « %{script} » pour l'automatisation « %{automation} », assurez-vous que l'extension associée est installée
|
||||
zapier_webhook:
|
||||
fields:
|
||||
webhook_url:
|
||||
label: URL du webhook
|
||||
description: "Attend une URL de webhook Zapier valide, par exemple : https://hooks.zapier.com/hooks/catch/xxx/yyy/"
|
||||
auto_responder:
|
||||
fields:
|
||||
once:
|
||||
label: Une fois
|
||||
description: Ne répond qu'une seule fois par sujet
|
||||
word_answer_list:
|
||||
label: Liste des paires mot/réponse
|
||||
answering_user:
|
||||
label: Utilisateur qui répond
|
||||
description: Par défaut, utilisateur système
|
||||
auto_tag_topic:
|
||||
fields:
|
||||
tags:
|
||||
label: Étiquettes
|
||||
description: Liste des étiquettes à ajouter au sujet.
|
||||
post:
|
||||
fields:
|
||||
creator:
|
||||
label: Créateur
|
||||
topic:
|
||||
label: ID de sujet
|
||||
post:
|
||||
label: Contenu du message
|
||||
group_category_notification_default:
|
||||
fields:
|
||||
group:
|
||||
label: Groupe
|
||||
notification_level:
|
||||
label: Niveau de notification
|
||||
update_existing_members:
|
||||
label: Mettre à jour les membres existants
|
||||
description: Met à jour le niveau de notification pour les membres du groupe existant
|
||||
user_global_notice:
|
||||
fields:
|
||||
level:
|
||||
label: Niveau
|
||||
notice:
|
||||
label: Avis
|
||||
description: Accepte le HTML, ne le remplissez pas avec une entrée non fiable !
|
||||
levels:
|
||||
warning: Avertissement
|
||||
info: Informations
|
||||
success: Succès
|
||||
error: Erreur
|
||||
user_group_membership_through_badge:
|
||||
fields:
|
||||
badge_name:
|
||||
label: Nom du badge
|
||||
group:
|
||||
label: Groupe
|
||||
description: Groupe ciblé. Les utilisateurs ayant le badge spécifié seront ajoutés à ce groupe
|
||||
update_user_title_and_flair:
|
||||
label: Mettre à jour le titre et le style de l'utilisateur
|
||||
description: Facultatif, mettre à jour le titre et le style de l'utilisateur
|
||||
remove_members_without_badge:
|
||||
label: Supprimer les membres existants sans badge
|
||||
description: Facultatif, supprimer les membres du groupe existants sans le badge spécifié.
|
||||
badge:
|
||||
label: Badge
|
||||
description: Sélectionner un badge
|
||||
suspend_user_by_email:
|
||||
fields:
|
||||
suspend_until:
|
||||
label: Suspendre jusqu'à (par défaut)
|
||||
reason:
|
||||
label: Motif (par défaut)
|
||||
actor:
|
||||
label: Utilisateur
|
||||
description: "L'utilisateur responsable de la suspension (par défaut : système)"
|
||||
pin_topic:
|
||||
fields:
|
||||
pinnable_topic:
|
||||
label: ID de sujet
|
||||
pinned_globally:
|
||||
label: Épinglé globalement
|
||||
pinned_until:
|
||||
label: Épinglé jusqu'à
|
||||
banner_topic:
|
||||
fields:
|
||||
topic_id:
|
||||
label: ID de sujet
|
||||
banner_until:
|
||||
label: Faire une bannière jusqu'au
|
||||
user:
|
||||
label: Utilisateur
|
||||
description: "L'utilisateur qui crée la bannière (par défaut : système)"
|
||||
flag_post_on_words:
|
||||
fields:
|
||||
words:
|
||||
label: Mots vérifiés
|
||||
topic_required_words:
|
||||
fields:
|
||||
words:
|
||||
label: Liste des mots requis
|
||||
gift_exchange:
|
||||
fields:
|
||||
gift_exchangers_group:
|
||||
label: Nom du groupe de participants
|
||||
giftee_assignment_messages:
|
||||
label: Messages envoyés au donateur
|
||||
send_pms:
|
||||
add_a_pm_btn:
|
||||
label: Ajouter un MP
|
||||
fields:
|
||||
receiver:
|
||||
label: Récepteur du MP
|
||||
sendable_pms:
|
||||
label: MP
|
||||
sender:
|
||||
label: Expéditeur du MP
|
||||
close_topic:
|
||||
fields:
|
||||
topic:
|
||||
label: ID de sujet
|
||||
message:
|
||||
label: Message de fermeture
|
||||
description: "Message facultatif à afficher dans l'enregistrement Sujet fermé"
|
||||
user:
|
||||
label: Utilisateur
|
||||
description: "L'utilisateur qui ferme le sujet (par défaut : système)"
|
||||
add_user_to_group_through_custom_field:
|
||||
fields:
|
||||
custom_field_name:
|
||||
label: "Nom du champ personnalisé de l'utilisateur"
|
||||
models:
|
||||
script:
|
||||
name:
|
||||
label: Script
|
||||
trigger:
|
||||
name:
|
||||
label: Déclencheur
|
||||
automation:
|
||||
name:
|
||||
label: Nom
|
||||
trigger:
|
||||
label: Déclencheur
|
||||
script:
|
||||
label: Script
|
||||
version:
|
||||
label: Version
|
||||
enabled:
|
||||
label: Activé
|
||||
disabled:
|
||||
label: Désactivé
|
||||
placeholders:
|
||||
label: Espaces réservés
|
||||
last_updated_at:
|
||||
label: Dernière mise à jour
|
||||
last_updated_by:
|
||||
label: Mis à jour par
|
|
@ -0,0 +1,7 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
gl:
|
|
@ -0,0 +1,362 @@
|
|||
# WARNING: Never edit this file.
|
||||
# It will be overwritten when translations are pulled from Crowdin.
|
||||
#
|
||||
# To work with us on translations, join this project:
|
||||
# https://translate.discourse.org/
|
||||
|
||||
he:
|
||||
js:
|
||||
discourse_automation:
|
||||
title: אוטומציה
|
||||
create: יצירה
|
||||
update: עדכון
|
||||
select_script: בחירת סקריפט
|
||||
select_trigger: בחירת גורם מפעיל
|
||||
confirm_automation_trigger: פעולה זו תקפיץ את האוטומציה, להמשיך?
|
||||
no_automation_yet: לא יצרת אף אוטומציה עדיין.
|
||||
edit_automation:
|
||||
trigger_section:
|
||||
forced: הקפצה זו נאכפת על ידי סקריפט.
|
||||
next_pending_automation: "האוטומציה הבאה תוקפץ ב־: %{date}"
|
||||
trigger_now: "להקפיץ כעת"
|
||||
title: מתי/מה…
|
||||
fields_section:
|
||||
title: אפשרויות סקריפט
|
||||
destroy_automation:
|
||||
confirm: "למחוק את `%{name}`?"
|
||||
fields:
|
||||
key_value:
|
||||
label:
|
||||
one: עריכת הגדרה (%{count})
|
||||
two: עריכת הגדרות (%{count})
|
||||
many: עריכת הגדרות (%{count})
|
||||
other: עריכת הגדרות (%{count})
|
||||
user:
|
||||
label: משתמש
|
||||
pm:
|
||||
title:
|
||||
label: כותרת
|
||||
raw:
|
||||
label: גוף
|
||||
pms:
|
||||
confirm_remove_pm: "להסיר את ההודעה הפרטית הזאת?"
|
||||
placeholder_title: כותרת ההודעה הפרטית
|
||||
add_pm: הוספת הודעה פרטית
|
||||
title:
|
||||
label: כותרת
|
||||
raw:
|
||||
label: גוף
|
||||
delay:
|
||||
label: השהיה (דקות)
|
||||
prefers_encrypt:
|
||||
label: מצפין הודעות פרטיות אם זמין
|
||||
group:
|
||||
label: קבוצה
|
||||
text:
|
||||
label: טקסט
|
||||
triggerables:
|
||||
not_found: לא ניתן למצוא את גורם ההקפצה `%{trigger}` לאוטומציה `%{automation}`, יש לוודא שהתוסף המשויך מותקן
|
||||
user_badge_granted:
|
||||
fields:
|
||||
badge:
|
||||
label: עיטור
|
||||
only_first_grant:
|
||||
label: רק עם ההענקה הראשונה
|
||||
stalled_topic:
|
||||
durations:
|
||||
PT1H: "שעה"
|
||||
P1D: "יום"
|
||||
P1W: "שבוע"
|
||||
P2W: "שבועיים"
|
||||
P1M: "חודש"
|
||||
P3M: "שלושה חודשים"
|
||||
P6M: "שישה חודשים"
|
||||
P1Y: "שנה"
|
||||
fields:
|
||||
categories:
|
||||
label: מוגבל לקטגוריות
|
||||
tags:
|
||||
label: מוגבל לתגיות
|
||||
recurring:
|
||||
every: כל
|
||||
frequencies:
|
||||
minute: דקה
|
||||
hour: שעה
|
||||
day: יום
|
||||
weekday: יום חול
|
||||
week: שבוע
|
||||
month: חודש
|
||||
year: שנה
|
||||
fields:
|
||||
recurrence:
|
||||
label: חזרתיות
|
||||
start_date:
|
||||
label: תאריך התחלה
|
||||
stalled_wiki:
|
||||
durations:
|
||||
PT1H: "שעה"
|
||||
P1D: "יום"
|
||||
P1W: "שבוע"
|
||||
P2W: "שבועיים"
|
||||
P1M: "חודש"
|
||||
P3M: "שלושה חודשים"
|
||||
P6M: "שישה חודשים"
|
||||
P1Y: "שנה"
|
||||
fields:
|
||||
restricted_category:
|
||||
label: מוגבל לקטגוריה
|
||||
stalled_after:
|
||||
label: השהיית הזנקה
|
||||
description: הגדרת השהייה בין עריכת הוויקי האחרונה והזנקת האוטומציה
|
||||
retriggered_after:
|
||||
label: השהיית הזנקה חוזרת
|
||||
user_added_to_group:
|
||||
fields:
|
||||
joined_group:
|
||||
label: קבוצה במעקב
|
||||
user_removed_from_group:
|
||||
fields:
|
||||
left_group:
|
||||
label: קבוצה במעקב
|
||||
user_promoted:
|
||||
fields:
|
||||
restricted_group:
|
||||
label: הגבלה לקבוצה
|
||||
trust_level_transition:
|
||||
label: מעבר בין דרגות אמון
|
||||
trust_levels:
|
||||
ALL: "כל דרגות האמון"
|
||||
TL01: "דרגת אמון 0 ל־1"
|
||||
TL12: "דרגת אמון 1 ל־2"
|
||||
TL23: "דרגת אמון 2 ל־3"
|
||||
TL34: "דרגת אמון 3 ל־4"
|
||||
point_in_time:
|
||||
fields:
|
||||
execute_at:
|
||||
label: לבצע ב־
|
||||
topic:
|
||||
fields:
|
||||
restricted_topic:
|
||||
label: מזהה נושא
|
||||
post_created_edited:
|
||||
fields:
|
||||
action_type:
|
||||
label: סוג פעולה
|
||||
description: "כרשות, הגבלת ההקפצה לאירועים שנוצרו או נערכו בלבד"
|
||||
valid_trust_levels:
|
||||
label: דרגות אמון תקפות
|
||||
description: יוקפץ רק אם הפוסט נוצר על ידי משתמש בדרגות האמון האלו, ברירת המחדל היא כל דרגת אמון
|
||||
restricted_category:
|
||||
label: קטגוריה
|
||||
description: הגדרת רשות, להזניק רק אם נושא הפוסט בקטגוריה הזאת
|
||||
restricted_group:
|
||||
label: קבוצה
|
||||
description: הגדרת רשות, להזניק רק אם נושא הפוסט הוא הודעה פרטית בתיבת הדואר הנכנס של הקבוצה הזאת
|
||||
ignore_group_members:
|
||||
label: התעלמות מחברי הקבוצה
|
||||
description: לדלג על ההזנקה אם המוען הוא חבר בקבוצה שצוינה לעיל
|
||||
ignore_automated:
|
||||
label: התעלמות מאוטומטי
|
||||
description: לדלג על ההזנקה אם לשולח יש כתובת דוא״ל noreply (לא להגיב) או שהוא ממקור אוטומטי. חל רק על פוסטים שנוצרו דרך דוא״ל
|
||||
first_post_only:
|
||||
label: פוסט ראשון בלבד
|
||||
description: יוקפץ רק אם הפוסט הוא הפוסט הראשון שיצר המשתמש
|
||||
first_topic_only:
|
||||
label: נושא ראשון בלבד
|
||||
description: יוקפץ רק אם הנושא הוא הנושא הראשון שיצר המשתמש
|
||||
created: נוצרו
|
||||
edited: נערכו
|
||||
user_updated:
|
||||
fields:
|
||||
user_profile:
|
||||
label: שדות פרופיל המשתמש
|
||||
description: יוזנק רק אם המשתמש מילא את השדות האלו
|
||||
custom_fields:
|
||||
label: להשתמש בשדות מותאמים אישית
|
||||
description: יוזנק רק אם המשתמש מילא את השדות האלו
|
||||
once_per_user:
|
||||
label: פעם אחת לכל משתמש
|
||||
description: יוזנק רק פעם אחת לכל משתמש
|
||||
category_created_edited:
|
||||
fields:
|
||||
restricted_category:
|
||||
label: קטגוריית הורה
|
||||
description: רשות, מאפשר להגביל הפעלות בקטגוריה הזאת
|
||||
pm_created:
|
||||
fields:
|
||||
restricted_user:
|
||||
label: משתמשים
|
||||
description: יוזנק רק עבור הודעות פרטיות שנשלחות למשתמש הזה
|
||||
restricted_group:
|
||||
label: קבוצה
|
||||
description: יוזנק רק עבור הודעות פרטיות שנשלחות לקבוצה הזאת
|
||||
ignore_staff:
|
||||
label: התעלמות מהסגל
|
||||
description: לדלג על גורם ההפעלה אם זה משתמש מהסגל
|
||||
ignore_group_members:
|
||||
label: התעלמות מחברי הקבוצה
|
||||
description: לדלג על ההזנקה אם המוען הוא חבר קבוצה שצוינה לעיל
|
||||
ignore_automated:
|
||||
label: התעלמות מאוטומטי
|
||||
description: לדלג על ההזנקה אם לשולח יש כתובת דוא״ל noreply (לא להגיב) או שהוא ממקור אוטומטי. חל רק על הודעות פרטיות שנוצרו דרך דוא״ל
|
||||
valid_trust_levels:
|
||||
label: דרגות אמון תקפות
|
||||
description: יוקפץ רק אם הפוסט נוצר על ידי משתמש בדרגות האמון האלו, ברירת המחדל היא כל דרגת אמון
|
||||
after_post_cook:
|
||||
fields:
|
||||
valid_trust_levels:
|
||||
label: דרגות אמון תקפות
|
||||
description: יוקפץ רק אם הפוסט נוצר על ידי משתמש בדרגות האמון האלו, ברירת המחדל היא כל דרגת אמון
|
||||
restricted_category:
|
||||
label: קטגוריה
|
||||
description: הגדרת רשות, להזניק רק אם נושא הפוסט בקטגוריה הזאת
|
||||
restricted_tags:
|
||||
label: תגיות
|
||||
description: הגדרת רשות, להזניק רק אם לפוסט יש את אחת התגיות הבאות
|
||||
scriptables:
|
||||
not_found: לא ניתן למצוא את הסקריפט `%{script}` לאוטומציה `%{automation}`, יש לוודא שהתוסף המשויך מותקן
|
||||
zapier_webhook:
|
||||
fields:
|
||||
webhook_url:
|
||||
label: כתובת התליה
|
||||
description: "אמורה להיות כתובת התליה תקפה של Zapier, למשל: https://hooks.zapier.com/hooks/catch/xxx/yyy/"
|
||||
auto_responder:
|
||||
fields:
|
||||
once:
|
||||
label: פעם אחת
|
||||
description: מגיב פעם אחת בלבד בכל נושא
|
||||
word_answer_list:
|
||||
label: רשימה של צמדי מילה/תשובה
|
||||
description: "מגדיר רשימה של קבוצות מפתח/ערך כאשר `מפתח` הוא הערך אחריו מחפשים, ו`ערך` הוא טקסט התגובה. את ה`מפתח` אפשר להשאיר ריק כדי להגיב לכל ההזנקות ללא תלות בתוכן. נא לשים לב ש`ערך` מקבל `{{key}}` בתור ממלא מקום להחלפת הערך של ה`מפתח` בתגובה. נא לשים לב ש`מפתח` יפוענח כביטוי רגולרי, ותווים מיוחדים כגון `.` דורשים החרגה אם הכוונה שלך הייתה נקודה, למשל: `\\.`"
|
||||
answering_user:
|
||||
label: משתמש שענה
|
||||
description: ברירת המחדל היא משתמש המערכת
|
||||
auto_tag_topic:
|
||||
fields:
|
||||
tags:
|
||||
label: תגיות
|
||||
description: רשימת תגיות להוספה לנושא.
|
||||
post:
|
||||
fields:
|
||||
creator:
|
||||
label: יוצר
|
||||
post_creator_context: יוצר הפוסט
|
||||
updated_user_context: המשתמש שעודכן
|
||||
topic:
|
||||
label: מזהה נושא
|
||||
post:
|
||||
label: תוכן הפוסט
|
||||
group_category_notification_default:
|
||||
fields:
|
||||
group:
|
||||
label: קבוצה
|
||||
notification_level:
|
||||
label: רמת התראה
|
||||
update_existing_members:
|
||||
label: עדכון חברים קיימים
|
||||
description: מעדכן את רמת ההתראות עבור חברי קבוצה קיימים
|
||||
user_global_notice:
|
||||
fields:
|
||||
level:
|
||||
label: דרגה
|
||||
notice:
|
||||
label: הודעה
|
||||
description: מקבל HTML, לא למלא את זה בקלט מפוקפק!
|
||||
levels:
|
||||
warning: אזהרה
|
||||
info: מידע
|
||||
success: הצלחה
|
||||
error: שגיאה
|
||||
user_group_membership_through_badge:
|
||||
fields:
|
||||
badge_name:
|
||||
label: שם העיטור
|
||||
group:
|
||||
label: קבוצה
|
||||
description: קבוצת יעד. משתמשים עם העיטור שצוין יתווספו לקבוצה הזאת.
|
||||
update_user_title_and_flair:
|
||||
label: עדכון כותרת וסמלון המשתמש
|
||||
description: רשות, עדכון כותרת וסמלון המשתמש
|
||||
remove_members_without_badge:
|
||||
label: הסרת חברים קיימים ללא עיטור
|
||||
description: כרשות, להסיר חברי קבוצה קיימים בלי עיטורים מסוימים
|
||||
badge:
|
||||
label: עיטור
|
||||
description: בחירת עיטור
|
||||
suspend_user_by_email:
|
||||
fields:
|
||||
suspend_until:
|
||||
label: להשעות עד (ברירת מחדל)
|
||||
reason:
|
||||
label: סיבה (ברירת מחדל)
|
||||
actor:
|
||||
label: משתמש
|
||||
description: "המשתמש האחראי להשעיה (ברירת מחדל: מערכת)"
|
||||
pin_topic:
|
||||
fields:
|
||||
pinnable_topic:
|
||||
label: מזהה נושא
|
||||
pinned_globally:
|
||||
label: נעוץ גלובלית
|
||||
pinned_until:
|
||||
label: נעוץ עד
|
||||
banner_topic:
|
||||
fields:
|
||||
topic_id:
|
||||
label: מזהה נושא
|
||||
banner_until:
|
||||
label: להציב ככרזה עד
|
||||
user:
|
||||
label: משתמש
|
||||
description: "המשתמש יוצר את הכרזה (ברירת מחדל: מערכת)"
|
||||
flag_post_on_words:
|
||||
fields:
|
||||
words:
|
||||
label: מילים שנבדקות
|
||||
topic_required_words:
|
||||
fields:
|
||||
words:
|
||||
label: רשימת מילים נדרשות
|
||||
send_pms:
|
||||
add_a_pm_btn:
|
||||
label: הוספת הודעה פרטית
|
||||
close_topic:
|
||||
fields:
|
||||
topic:
|
||||
label: מזהה נושא
|
||||
message:
|
||||
label: הודעת סגירה
|
||||
description: "הודעת רשות שתופיע ברשומת סגירת הנושא"
|
||||
user:
|
||||
label: משתמש
|
||||
description: "המשתמש שסגר את הנושא (ברירת מחדל: מערכת)"
|
||||
add_user_to_group_through_custom_field:
|
||||
fields:
|
||||
custom_field_name:
|
||||
label: "שם שדה מותאם אישית של משתמש"
|
||||
models:
|
||||
script:
|
||||
name:
|
||||
label: סקריפט
|
||||
trigger:
|
||||
name:
|
||||
label: גורם מפעיל
|
||||
automation:
|
||||
name:
|
||||
label: שם
|
||||
trigger:
|
||||
label: גורם מפעיל
|
||||
script:
|
||||
label: סקריפט
|
||||
version:
|
||||
label: גירסה
|
||||
enabled:
|
||||
label: מופעל
|
||||
disabled:
|
||||
label: מושבת
|
||||
placeholders:
|
||||
label: ממלאי מקום
|
||||
last_updated_at:
|
||||
label: עדכון אחרון
|
||||
last_updated_by:
|
||||
label: עודכן על ידי
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue