├── .prettierrc
├── .eslintrc
├── .streerc
├── .rubocop.yml
├── .template-lintrc.js
├── assets
├── javascripts
│ ├── components
│ │ ├── canned-replies-form.js
│ │ └── canned-reply.js
│ ├── discourse
│ │ └── templates
│ │ │ ├── modal
│ │ │ ├── new-reply.hbs
│ │ │ ├── edit-reply.hbs
│ │ │ └── canned-replies.hbs
│ │ │ ├── components
│ │ │ ├── canned-replies-form.hbs
│ │ │ └── canned-reply.hbs
│ │ │ └── connectors
│ │ │ └── editor-preview
│ │ │ └── canned-replies.hbs
│ ├── initializers
│ │ └── add-canned-replies-ui-builder.js
│ ├── controllers
│ │ ├── new-reply.js
│ │ ├── edit-reply.js
│ │ └── canned-replies.js
│ ├── lib
│ │ └── apply-reply.js
│ └── connectors
│ │ └── editor-preview
│ │ └── canned-replies.js
└── stylesheets
│ └── canned-replies.scss
├── .gitignore
├── package.json
├── Gemfile
├── .editorconfig
├── config
├── locales
│ ├── server.be.yml
│ ├── server.bg.yml
│ ├── server.cs.yml
│ ├── server.da.yml
│ ├── server.el.yml
│ ├── server.et.yml
│ ├── server.gl.yml
│ ├── server.id.yml
│ ├── server.lt.yml
│ ├── server.lv.yml
│ ├── server.sq.yml
│ ├── server.sr.yml
│ ├── server.te.yml
│ ├── server.th.yml
│ ├── server.vi.yml
│ ├── client.en_GB.yml
│ ├── server.bs_BA.yml
│ ├── server.en_GB.yml
│ ├── server.fa_IR.yml
│ ├── server.nb_NO.yml
│ ├── server.zh_TW.yml
│ ├── client.id.yml
│ ├── client.sk.yml
│ ├── client.el.yml
│ ├── client.gl.yml
│ ├── client.sr.yml
│ ├── client.th.yml
│ ├── client.be.yml
│ ├── client.bg.yml
│ ├── client.lt.yml
│ ├── client.lv.yml
│ ├── client.te.yml
│ ├── server.hu.yml
│ ├── server.sk.yml
│ ├── client.zh_TW.yml
│ ├── client.pt.yml
│ ├── client.sq.yml
│ ├── client.nb_NO.yml
│ ├── client.cs.yml
│ ├── client.vi.yml
│ ├── server.ko.yml
│ ├── client.et.yml
│ ├── server.zh_CN.yml
│ ├── server.sw.yml
│ ├── server.ja.yml
│ ├── server.en.yml
│ ├── client.da.yml
│ ├── server.ur.yml
│ ├── client.en.yml
│ ├── server.uk.yml
│ ├── server.hr.yml
│ ├── client.zh_CN.yml
│ ├── client.he.yml
│ ├── client.ja.yml
│ ├── client.fa_IR.yml
│ ├── server.ar.yml
│ ├── server.ca.yml
│ ├── client.ko.yml
│ ├── server.he.yml
│ ├── server.hy.yml
│ ├── client.ur.yml
│ ├── client.sl.yml
│ ├── client.hu.yml
│ ├── client.ro.yml
│ ├── client.uk.yml
│ ├── client.pl_PL.yml
│ ├── client.hr.yml
│ ├── client.bs_BA.yml
│ ├── client.ca.yml
│ ├── client.sw.yml
│ ├── server.pl_PL.yml
│ ├── server.sv.yml
│ ├── server.tr_TR.yml
│ ├── client.hy.yml
│ ├── client.tr_TR.yml
│ ├── server.ru.yml
│ ├── client.ar.yml
│ ├── server.sl.yml
│ ├── client.sv.yml
│ ├── server.nl.yml
│ ├── server.pt.yml
│ ├── client.nl.yml
│ ├── client.de.yml
│ ├── client.fi.yml
│ ├── client.ru.yml
│ ├── server.de.yml
│ ├── server.es.yml
│ ├── server.fi.yml
│ ├── server.ro.yml
│ ├── client.it.yml
│ ├── client.es.yml
│ ├── server.pt_BR.yml
│ ├── client.fr.yml
│ ├── client.pt_BR.yml
│ ├── server.it.yml
│ └── server.fr.yml
├── settings.yml
└── routes.rb
├── lib
└── discourse_canned_replies
│ └── engine.rb
├── translator.yml
├── .github
└── workflows
│ └── discourse-plugin.yml
├── app
├── jobs
│ └── onceoff
│ │ └── rename_canned_replies.rb
├── controllers
│ └── discourse_canned_replies
│ │ └── canned_replies_controller.rb
└── models
│ └── discourse_canned_replies
│ └── reply.rb
├── README.md
├── spec
├── plugin_spec.rb
└── integration
│ └── canned_reply
│ └── canned_replies_spec.rb
├── Gemfile.lock
├── plugin.rb
├── test
└── javascripts
│ └── acceptance
│ └── canned-replies-test.js
└── LICENSE.txt
/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-discourse"
3 | }
4 |
--------------------------------------------------------------------------------
/.streerc:
--------------------------------------------------------------------------------
1 | --print-width=100
2 | --plugins=plugin/trailing_comma
3 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_gem:
2 | rubocop-discourse: stree-compat.yml
3 |
--------------------------------------------------------------------------------
/.template-lintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ["ember-template-lint-plugin-discourse"],
3 | extends: "discourse:recommended",
4 | };
5 |
--------------------------------------------------------------------------------
/assets/javascripts/components/canned-replies-form.js:
--------------------------------------------------------------------------------
1 | import Component from "@ember/component";
2 |
3 | export default Component.extend({});
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.iml
3 | .DS_Store
4 | node_modules/
5 | yarn-error.log
6 | node_modules
7 | .rubocop-https---raw-githubusercontent-com-discourse-*
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Discourse",
3 | "license": "GPL-2.0-only",
4 | "devDependencies": {
5 | "eslint-config-discourse": "^3.4.0"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | group :development do
6 | gem "rubocop-discourse"
7 | gem "syntax_tree"
8 | end
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*.*]
8 | indent_style = space
9 | indent_size = 2
--------------------------------------------------------------------------------
/config/locales/server.be.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | be:
8 |
--------------------------------------------------------------------------------
/config/locales/server.bg.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | bg:
8 |
--------------------------------------------------------------------------------
/config/locales/server.cs.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | cs:
8 |
--------------------------------------------------------------------------------
/config/locales/server.da.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | da:
8 |
--------------------------------------------------------------------------------
/config/locales/server.el.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | el:
8 |
--------------------------------------------------------------------------------
/config/locales/server.et.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | et:
8 |
--------------------------------------------------------------------------------
/config/locales/server.gl.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | gl:
8 |
--------------------------------------------------------------------------------
/config/locales/server.id.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | id:
8 |
--------------------------------------------------------------------------------
/config/locales/server.lt.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | lt:
8 |
--------------------------------------------------------------------------------
/config/locales/server.lv.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | lv:
8 |
--------------------------------------------------------------------------------
/config/locales/server.sq.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | sq:
8 |
--------------------------------------------------------------------------------
/config/locales/server.sr.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | sr:
8 |
--------------------------------------------------------------------------------
/config/locales/server.te.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | te:
8 |
--------------------------------------------------------------------------------
/config/locales/server.th.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | th:
8 |
--------------------------------------------------------------------------------
/config/locales/server.vi.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | vi:
8 |
--------------------------------------------------------------------------------
/lib/discourse_canned_replies/engine.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ::DiscourseCannedReplies
4 | class Engine < ::Rails::Engine
5 | engine_name PLUGIN_NAME
6 | isolate_namespace DiscourseCannedReplies
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/translator.yml:
--------------------------------------------------------------------------------
1 | # Configuration file for discourse-translator-bot
2 |
3 | files:
4 | - source_path: config/locales/client.en.yml
5 | destination_path: client.yml
6 | - source_path: config/locales/server.en.yml
7 | destination_path: server.yml
8 |
--------------------------------------------------------------------------------
/.github/workflows/discourse-plugin.yml:
--------------------------------------------------------------------------------
1 | name: Discourse Plugin
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 |
9 | jobs:
10 | ci:
11 | uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1
12 |
--------------------------------------------------------------------------------
/config/locales/client.en_GB.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | en_GB:
8 |
--------------------------------------------------------------------------------
/config/locales/server.bs_BA.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | bs_BA:
8 |
--------------------------------------------------------------------------------
/config/locales/server.en_GB.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | en_GB:
8 |
--------------------------------------------------------------------------------
/config/locales/server.fa_IR.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | fa_IR:
8 |
--------------------------------------------------------------------------------
/config/locales/server.nb_NO.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | nb_NO:
8 |
--------------------------------------------------------------------------------
/config/locales/server.zh_TW.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | zh_TW:
8 |
--------------------------------------------------------------------------------
/config/settings.yml:
--------------------------------------------------------------------------------
1 | plugins:
2 | canned_replies_enabled:
3 | default: true
4 | client: true
5 | canned_replies_groups:
6 | default: ""
7 | type: list
8 | canned_replies_everyone_enabled:
9 | default: false
10 | client: true
11 | canned_replies_everyone_can_edit:
12 | default: false
13 | client: true
14 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | DiscourseCannedReplies::Engine.routes.draw do
4 | resources :canned_replies, path: "/", only: %i[index create destroy update] do
5 | member do
6 | get "reply"
7 | patch "use"
8 | end
9 | end
10 | end
11 |
12 | Discourse::Application.routes.draw { mount ::DiscourseCannedReplies::Engine, at: "/canned_replies" }
13 |
--------------------------------------------------------------------------------
/config/locales/client.id.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | id:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Judul"
12 | insert:
13 | new_button: "Baru"
14 | edit_button: "Ubah"
15 |
--------------------------------------------------------------------------------
/config/locales/client.sk.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | sk:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Názov"
12 | insert:
13 | new_button: "Nový"
14 | edit_button: "Upraviť"
15 | back: "Späť"
16 |
--------------------------------------------------------------------------------
/config/locales/client.el.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | el:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Τίτλος"
12 | insert:
13 | new_button: "Νέο"
14 | edit_button: "Επεξεργασία"
15 | back: "Πίσω"
16 |
--------------------------------------------------------------------------------
/config/locales/client.gl.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | gl:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Título"
12 | insert:
13 | new_button: "Novo"
14 | edit_button: "Editar"
15 | back: "Volver"
16 |
--------------------------------------------------------------------------------
/config/locales/client.sr.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | sr:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Naslov"
12 | insert:
13 | new_button: "Novo"
14 | edit_button: "Izmeni"
15 | back: "Nazad"
16 |
--------------------------------------------------------------------------------
/config/locales/client.th.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | th:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "ชื่อเรื่อง"
12 | insert:
13 | new_button: "ใหม่"
14 | edit_button: "แก้ไข"
15 | back: "กลับ"
16 |
--------------------------------------------------------------------------------
/config/locales/client.be.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | be:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "тытульны"
12 | insert:
13 | new_button: "Новая"
14 | edit_button: "Рэдагаваць"
15 | back: "Назад"
16 |
--------------------------------------------------------------------------------
/config/locales/client.bg.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | bg:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Заглавие"
12 | insert:
13 | new_button: "Нов"
14 | edit_button: "Редактирай"
15 | back: "Назад"
16 |
--------------------------------------------------------------------------------
/config/locales/client.lt.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | lt:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Antraštė"
12 | insert:
13 | new_button: "Naujas"
14 | edit_button: "Redaguoti"
15 | back: "Atgal"
16 |
--------------------------------------------------------------------------------
/config/locales/client.lv.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | lv:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Virsraksts"
12 | insert:
13 | new_button: "Jauns"
14 | edit_button: "Labot"
15 | back: "Atpakaļ"
16 |
--------------------------------------------------------------------------------
/config/locales/client.te.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | te:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "శీర్షిక"
12 | insert:
13 | new_button: "కొత్త"
14 | edit_button: "సవరించు"
15 | back: "వెనుకకు"
16 |
--------------------------------------------------------------------------------
/config/locales/server.hu.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | hu:
8 | site_settings:
9 | canned_replies_enabled: 'Megjelenítse az eltett válaszok beillesztése gombot a szerkesztő ablakon?'
10 | replies:
11 | default_reply:
12 | title: "Első eltett válaszom"
13 |
--------------------------------------------------------------------------------
/config/locales/server.sk.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | sk:
8 | site_settings:
9 | canned_replies_enabled: 'Má sa v editore zobraziť tlačítko na vloženie pripravenej odpovede?'
10 | replies:
11 | default_reply:
12 | title: "Moja prvá pripravená odpoveď"
13 |
--------------------------------------------------------------------------------
/assets/javascripts/discourse/templates/modal/new-reply.hbs:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/config/locales/client.zh_TW.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | zh_TW:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "標題"
12 | content:
13 | name: "內容"
14 | insert:
15 | new_button: "新使用者"
16 | edit_button: "編輯"
17 | back: "上一步"
18 |
--------------------------------------------------------------------------------
/app/jobs/onceoff/rename_canned_replies.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Jobs
4 | class RenameCannedReplies < ::Jobs::Onceoff
5 | OLD_PLUGIN_NAME = "canned_replies"
6 | NEW_PLUGIN_NAME = "discourse-canned-replies"
7 |
8 | def execute_onceoff(args)
9 | PluginStoreRow.where(plugin_name: NEW_PLUGIN_NAME).delete_all
10 | PluginStoreRow.where(plugin_name: OLD_PLUGIN_NAME).update_all(plugin_name: NEW_PLUGIN_NAME)
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/config/locales/client.pt.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | pt:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Título"
12 | content:
13 | name: "Conteúdo"
14 | insert:
15 | new_button: "Novo"
16 | edit_button: "Editar"
17 | back: "Retroceder"
18 |
--------------------------------------------------------------------------------
/config/locales/client.sq.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | sq:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Titulli"
12 | content:
13 | name: "Content"
14 | insert:
15 | new_button: "I Ri"
16 | edit_button: "Redakto"
17 | back: "Kthehu mbrapa"
18 |
--------------------------------------------------------------------------------
/config/locales/client.nb_NO.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | nb_NO:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Tittel"
12 | content:
13 | name: "Innhold"
14 | insert:
15 | choose: "(velg et svar)"
16 | new_button: "Ny"
17 | edit_button: "Rediger"
18 | back: "Forrige"
19 |
--------------------------------------------------------------------------------
/assets/javascripts/discourse/templates/components/canned-replies-form.hbs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/config/locales/client.cs.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | cs:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Nadpis"
12 | invalid: Nadpis nemůže být prázdný.
13 | content:
14 | name: "Obsah"
15 | invalid: Obsah nemůže být prázdný.
16 | insert:
17 | insert_button: "Vložit odpověď"
18 | new_button: "Noví"
19 | edit_button: "Upravit"
20 | back: "Zpět"
21 |
--------------------------------------------------------------------------------
/config/locales/client.vi.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | vi:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Tiêu đề"
12 | invalid: Tiêu đề không được trống
13 | content:
14 | name: "Nội dung"
15 | invalid: Nội dung không được trống
16 | insert:
17 | choose: "(chọn một câu trả lời)"
18 | new_button: "Mới"
19 | edit_button: "Sửa"
20 | back: "Quay lại"
21 |
--------------------------------------------------------------------------------
/config/locales/server.ko.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ko:
8 | site_settings:
9 | canned_replies_enabled: '작성기 창에 미리 준비된 답장 삽입 버튼을 표시 하시겠습니까?'
10 | canned_replies_groups: '비 직원 그룹의 미리 준비된 답변에 그룹 이름별로 액세스 권한 추가'
11 | canned_replies_everyone_enabled: '모든 사람이 미리 준비된 답변을 사용할 수 있도록 허용'
12 | canned_replies_everyone_can_edit: '모든 사람이 미리 준비된 답장을 작성, 편집 및 삭제하도록 허용'
13 | replies:
14 | default_reply:
15 | title: "나의 첫 통조림 답변"
16 |
--------------------------------------------------------------------------------
/config/locales/client.et.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | et:
8 | js:
9 | canned_replies:
10 | title:
11 | name: "Pealkiri"
12 | invalid: Pealkiri ei saa olla tühi.
13 | content:
14 | name: "Sisu"
15 | invalid: Sisu ei saa olla tühi.
16 | insert:
17 | choose: "(vali vastus)"
18 | insert_button: "Sisesta vastus"
19 | new_button: "Uus"
20 | edit_button: "uuda"
21 | sort:
22 | alphabetically: "Sorteeri tähestiku järjekorras"
23 | back: "Tagasi"
24 |
--------------------------------------------------------------------------------
/config/locales/server.zh_CN.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | zh_CN:
8 | site_settings:
9 | canned_replies_enabled: '在编辑器窗口上显示“插入预设回复”按钮?'
10 | canned_replies_groups: '按群组名称为非管理群组添加预设回复的访问权限'
11 | canned_replies_everyone_enabled: '允许所有人使用预设回复'
12 | canned_replies_everyone_can_edit: '允许所有人创建、编辑和删除预设回复'
13 | replies:
14 | default_reply:
15 | title: "我的第一个预设回复"
16 | body: |
17 | 这是一个示例预设回复。
18 | 您可以使用 **markdown ** 设置回复样式。点击**新建**按钮可以创建新回复,点击**编辑**按钮可以编辑或移除现有的预设回复。
19 |
20 | * 当回复名单为空时,将添加此预设回复。*
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DEPRECATED
2 |
3 | This plugin is no longer supported. Please migrate to the [new discourse-templates plugin](https://meta.discourse.org/t/discourse-templates/229250).
4 |
5 | # Canned replies plugin
6 | This plugin adds support for inserting a canned reply into the composer window via a UI.
7 |
8 | Official Plugin Topic & Documentation: https://meta.discourse.org/t/discourse-canned-replies/48296/
9 |
10 | ## Installation
11 |
12 | Follow the directions at [Install a Plugin](https://meta.discourse.org/t/install-a-plugin/19157) using https://github.com/discourse/discourse-canned-replies.git as the repository URL.
13 |
14 | ## Authors
15 |
16 | Jay Pfaffman
17 |
18 | André Pereira
19 |
20 | ## License
21 |
22 | GNU GPL v2
23 |
--------------------------------------------------------------------------------
/assets/javascripts/discourse/templates/components/canned-reply.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{reply.title}}
4 |
5 |
6 |
11 |
12 | {{#if canEdit}}
13 |
18 | {{/if}}
19 |
20 |
21 |
22 |
23 | {{cook-text reply.content}}
24 |
25 |
--------------------------------------------------------------------------------
/config/locales/server.sw.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | sw:
8 | site_settings:
9 | canned_replies_enabled: 'Onyesha Kitufe cha kuweka majibu yaliyohifadhiwa kwenye sehemu ya kuandika? '
10 | replies:
11 | default_reply:
12 | title: "Jibu langu la kwanza lililohifadhiwa"
13 | body: |
14 | Huu ni mfano wa majibu yaliyohifadhiwa.
15 | Tumia **markdown** kuweka mtindo kwenye majibu yako. Bonyeza kitufe cha **mpya** kutengeneza majibu mapya au kitufe cha **hariri** kuhariri ay ondoa jibu ulilo hifadhi.
16 |
17 | *Jibu jipya litahifadhiwa kama orodha ya majibu iko tupu.*
18 |
--------------------------------------------------------------------------------
/config/locales/server.ja.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ja:
8 | site_settings:
9 | canned_replies_enabled: 'コンポーザーウィンドウに[返信定型文を挿入]ボタンを表示しますか?'
10 | canned_replies_groups: 'グループ名でスタッフ以外のグループに返信定型文へのアクセスを追加する'
11 | canned_replies_everyone_enabled: '返信定型文の使用を全員に許可する'
12 | canned_replies_everyone_can_edit: '返信定型文の作成、編集、および削除を全員に許可する'
13 | replies:
14 | default_reply:
15 | title: "私の最初の返信定型文"
16 | body: |
17 | これは返信定型文の例です。
18 | **マークダウン**を使用して、返信にスタイルを設定できます。**新規**ボタンをクリックして新しい返信を作成するか、**編集**ボタンをクリックして既存の返信定型文を編集または削除します。
19 |
20 | *この返信定型文は、返信リストが空である場合に追加されます。*
21 |
--------------------------------------------------------------------------------
/config/locales/server.en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | site_settings:
3 | canned_replies_enabled: 'Show insert canned reply button on composer window?'
4 | canned_replies_groups: 'Add access to canned replies for non-staff groups by group name'
5 | canned_replies_everyone_enabled: 'Allow everyone to use canned replies'
6 | canned_replies_everyone_can_edit: 'Allow everyone to create, edit, and delete canned replies'
7 | replies:
8 | default_reply:
9 | title: "My first canned reply"
10 | body: |
11 | This is an example canned reply.
12 | You can use **markdown** to style your replies. Click the **new** button to create new replies or the **edit** button to edit or remove an existing canned reply.
13 |
14 | *This canned reply will be added when the replies list is empty.*
15 |
--------------------------------------------------------------------------------
/assets/javascripts/discourse/templates/modal/edit-reply.hbs:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/config/locales/client.da.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | da:
8 | js:
9 | canned_replies:
10 | filter_hint: "Filtrer efter titel ..."
11 | title:
12 | name: "Titel"
13 | invalid: Titel kan ikke være tom.
14 | content:
15 | name: "Indhold"
16 | invalid: Indholdet kan ikke være tomt.
17 | insert:
18 | choose: "(vælg et svar)"
19 | insert_button: "Indsæt Svar"
20 | new_button: "Ny"
21 | edit_button: "Rediger"
22 | sort:
23 | alphabetically: "Sortér alfabetisk"
24 | usage: "Sorter efter anvendelsesfrekvens"
25 | back: "Tilbage"
26 |
--------------------------------------------------------------------------------
/config/locales/server.ur.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ur:
8 | site_settings:
9 | canned_replies_enabled: 'کومپوزر کی ونڈو پر طہ شدہ جواب کا بٹن دکھائیں؟'
10 | canned_replies_groups: 'گروپ نام کے حساب سے غیر سٹاف گروپس کیلئے طہ شدہ جوابات تک رسائی شامل کریں'
11 | replies:
12 | default_reply:
13 | title: "میرا پہلا طہ شدہ جواب"
14 | body: |
15 | یہ ایک مثال طہ شدہ جواب ہے۔
16 | آپ اپنے جوابات کو سٹائل کرنے کیلئے **مارکڈائون** کا استعمال کرسکتے ہیں۔ نیا جواب بنابے کیلئے **نیا** بٹن پر کلک کریں یا کسی موجودہ طہ شدہ جواب میں ترمیم یا حذف کرنے کیلئے **ترمیم** بٹن پر کلک کریں۔
17 |
18 | *یہ طہ شدہ جواب شامل کیا جائے گا جب جوابات کی فہرست خالی ہو گی۔*
19 |
--------------------------------------------------------------------------------
/config/locales/client.en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | js:
3 | canned_replies:
4 | filter_hint: "Filter by title..."
5 | composer_button_text: "Canned replies"
6 | title:
7 | name: "Title"
8 | invalid: Title can't be empty.
9 | content:
10 | name: "Content"
11 | invalid: Content can't be empty.
12 | insert:
13 | choose: "(choose a reply)"
14 | insert_button: "Insert Reply"
15 | new_button: "New"
16 | edit_button: "Edit"
17 | modal_title: "Insert Canned Reply"
18 | sort:
19 | alphabetically: "Sort alphabetically"
20 | usage: "Sort by frequency of use"
21 | add:
22 | modal_title: "Add Canned Reply"
23 | edit:
24 | modal_title: "Edit Canned Reply"
25 | remove_confirm: "Are you sure you want to delete this canned reply?"
26 | back: "Back"
27 |
--------------------------------------------------------------------------------
/config/locales/server.uk.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | uk:
8 | site_settings:
9 | canned_replies_enabled: 'Показати кнопку вставляння готової відповіді у вікно композитора?'
10 | canned_replies_groups: 'Додайте доступ до готових відповідей для неспівробітників за назвою групи'
11 | replies:
12 | default_reply:
13 | title: "Моя перша готова відповідь"
14 | body: |
15 | Це приклад готової відповіді.
16 | Ви можете використовувати **markdown** для стилювання відповідей. Натисніть кнопку **new**, щоб створити нові відповіді, або кнопку **edit**, щоб відредагувати або видалити наявну відповідь.
17 | *Ця готова відповідь буде додана, коли список відповідей порожній.*
18 |
--------------------------------------------------------------------------------
/config/locales/server.hr.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | hr:
8 | site_settings:
9 | canned_replies_enabled: 'Prikaži gumb za ubacivanje pripremljenog odgovora u prozoru composer-a'
10 | canned_replies_groups: 'Dodaj pristup pripremljenim odgovorima grupama po nazivu'
11 | replies:
12 | default_reply:
13 | title: "Moj prvi pripremljeni odgovor"
14 | body: |
15 | Ovo je primjer pripremljenog odgovora.
16 | Možeš koristiti **markdown** za stiliziranje svojih odgovora. Pritisni **novi** gumb za kreiranje ili **uredi** gumb za uređivanje ili brisanje već postojećeg pripremljenog odgovora.
17 |
18 | *Ovaj pripremljeni odgovor će biti dodan kada će lista odgovora biti prazna*
19 |
--------------------------------------------------------------------------------
/config/locales/client.zh_CN.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | zh_CN:
8 | js:
9 | canned_replies:
10 | filter_hint: "按标题筛选…"
11 | composer_button_text: "预设回复"
12 | title:
13 | name: "标题"
14 | invalid: 标题不能为空。
15 | content:
16 | name: "内容"
17 | invalid: 内容不能为空。
18 | insert:
19 | choose: "(选择回复)"
20 | insert_button: "插入回复"
21 | new_button: "新建"
22 | edit_button: "编辑"
23 | modal_title: "插入预设回复"
24 | sort:
25 | alphabetically: "按字母顺序排序"
26 | usage: "按使用频率排序"
27 | add:
28 | modal_title: "添加预设回复"
29 | edit:
30 | modal_title: "编辑预设回复"
31 | remove_confirm: "确定要删除此预设回复吗?"
32 | back: "返回"
33 |
--------------------------------------------------------------------------------
/config/locales/client.he.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | he:
8 | js:
9 | canned_replies:
10 | composer_button_text: "תגובות מוכנות"
11 | title:
12 | name: "כותרת"
13 | invalid: הכותרת לא יכולה להיות ריקה.
14 | content:
15 | name: "תוכן"
16 | invalid: התוכן לא יכול להיות ריק.
17 | insert:
18 | choose: "(נא לבחור תגובה)"
19 | insert_button: "הוספת תגובה"
20 | new_button: "חדש"
21 | edit_button: "עריכה"
22 | modal_title: "הוספת תגובה מוכנה"
23 | sort:
24 | alphabetically: "מיון לפי האלפבית"
25 | usage: "מיון לפי תדירות השימוש"
26 | add:
27 | modal_title: "הוספת תגובה מוכנה"
28 | edit:
29 | modal_title: "עריכת תגובה מוכנה"
30 | back: "חזרה"
31 |
--------------------------------------------------------------------------------
/config/locales/client.ja.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ja:
8 | js:
9 | canned_replies:
10 | filter_hint: "タイトルでフィルター..."
11 | composer_button_text: "返信定型文"
12 | title:
13 | name: "タイトル"
14 | invalid: タイトルを空白にできません。
15 | content:
16 | name: "コンテンツ"
17 | invalid: コンテンツを空白にできません。
18 | insert:
19 | choose: "(返信を選択)"
20 | insert_button: "返信を挿入"
21 | new_button: "新規"
22 | edit_button: "編集"
23 | modal_title: "返信定型文を挿入"
24 | sort:
25 | alphabetically: "アルファベット順に並べ替え"
26 | usage: "使用頻度で並べ替え"
27 | add:
28 | modal_title: "返信定型文を追加"
29 | edit:
30 | modal_title: "返信定型文を編集"
31 | remove_confirm: "この返信定型文を削除してもよろしいですか?"
32 | back: "戻る"
33 |
--------------------------------------------------------------------------------
/config/locales/client.fa_IR.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | fa_IR:
8 | js:
9 | canned_replies:
10 | composer_button_text: "پاسخ های آماده"
11 | title:
12 | name: "عنوان"
13 | invalid: عنوان نمیتواند خالی باشد
14 | content:
15 | name: "محتوا"
16 | invalid: محتوا نمیتوانتد خالی باشد.
17 | insert:
18 | choose: "(پاسخی انتخاب کن)"
19 | insert_button: "اضافه کن"
20 | new_button: "جدید"
21 | edit_button: "ویرایش"
22 | modal_title: "افزودن پاسخ آماده"
23 | sort:
24 | alphabetically: "به ترتیب حروف الفبا"
25 | usage: "به ترتیب استفاده"
26 | add:
27 | modal_title: "پاسخ آماده را اضافه کن"
28 | edit:
29 | modal_title: "پاسخ آماده را ویرایش کن"
30 | back: "بازگشت"
31 |
--------------------------------------------------------------------------------
/config/locales/server.ar.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ar:
8 | site_settings:
9 | canned_replies_enabled: 'هل تريد إظهار زر إدراج الرد المحدَّد مسبقًا في نافذة أداة الإنشاء؟'
10 | canned_replies_groups: 'إضافة الوصول إلى الردود المحدَّدة مسبقًا لمجموعات المستخدمين من غير الأعضاء في طاقم العمل باستخدام اسم المجموعة'
11 | canned_replies_everyone_enabled: 'السماح للجميع باستخدام الردود المحدَّدة مسبقًا'
12 | canned_replies_everyone_can_edit: 'السماح للجميع بإنشاء الردود المحدَّدة مسبقًا وتعديلها وحذفها'
13 | replies:
14 | default_reply:
15 | title: "أول رد محدَّد مسبقًا لي"
16 | body: |
17 | هذا مثال للرد المحدَّد مسبقًا.
18 | يمكنك استخدام **Markdown** لتغيير نمط ردودك. انقر على زر **جديد** لإنشاء ردود جديدة أو زر **تعديل** لتعديل أو إزالة رد محدَّد مسبقًا حالي.
19 |
--------------------------------------------------------------------------------
/config/locales/server.ca.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ca:
8 | site_settings:
9 | canned_replies_enabled: 'Mostra el botó d''inserció de resposta enllaunada en la finestra de redacció?'
10 | canned_replies_groups: 'Afegeix accés a les respostes enllaunades per a grups que no siguin de l''equip responsable, per nom de grup'
11 | replies:
12 | default_reply:
13 | title: "La meva primera resposta enllaunada"
14 | body: |
15 | Aquest és un exemple de resposta enllaunada.
16 | Podeu fer servir **markdown*** per a formatar les respostes. Feu clic en el botó **Nova** per a crear noves respostes o el botó **Edita** per a editar o eliminar una resposta enllaunada existent.
17 |
18 | * Aquesta resposta enllaunada s'afegirà quan la llista de respostes estigui buida.*
19 |
--------------------------------------------------------------------------------
/config/locales/client.ko.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ko:
8 | js:
9 | canned_replies:
10 | filter_hint: "제목으로 필터링..."
11 | composer_button_text: "미리 준비된 답변"
12 | title:
13 | name: "제목"
14 | invalid: 제목은 비워 둘 수 없습니다.
15 | content:
16 | name: "사이트 컨텐츠"
17 | invalid: 내용은 비워 둘 수 없습니다.
18 | insert:
19 | choose: "(답장 선택)"
20 | insert_button: "답장 삽입"
21 | new_button: "새값"
22 | edit_button: "수정"
23 | modal_title: "미리 준비된 답변 삽입"
24 | sort:
25 | alphabetically: "알파벳순으로 정렬"
26 | usage: "사용 빈도별로 정렬"
27 | add:
28 | modal_title: "미리 준비된 답변 추가"
29 | edit:
30 | modal_title: "미리 준비된 답변 수정"
31 | remove_confirm: "정말로 현재의 미리 준비된 답장을 삭제 하시겠습니까?"
32 | back: "뒤로"
33 |
--------------------------------------------------------------------------------
/config/locales/server.he.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | he:
8 | site_settings:
9 | canned_replies_enabled: 'להציג כפתור הוספת תגובה מוכנה בחלון כתיבת ההודעה?'
10 | canned_replies_groups: 'הוספת גישה לתגובות מוכנות לקבוצות שאינן לחברי סגל לפי שם קבוצה'
11 | canned_replies_everyone_enabled: 'לאפשר לכולם להשתמש בתגובות מוכנות'
12 | canned_replies_everyone_can_edit: 'לכולם יש את האפשרות ליצור, לערוך ולמחוק תגובות מוכנות מראש'
13 | replies:
14 | default_reply:
15 | title: "התגובה המוכנה הראשונה שלי"
16 | body: |
17 | זו הודעה מוכנה לדוגמה.
18 | ניתן להשתמש ב**סימון** כדי לסגנן את התגובות שלך. יש ללחוץ על הכפתור **חדש** כדי ליצור תגובות חדשות או על הכפתור **עריכה** כדי לערוך או להסיר תגובה מוכנה קיימת.
19 |
20 | *תגובה מוכנה זו תתווסף כאשר רשימת התגובות ריקה.*
21 |
--------------------------------------------------------------------------------
/config/locales/server.hy.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | hy:
8 | site_settings:
9 | canned_replies_enabled: 'Ցուցադրե՞լ Մուտքագրել պահեստավորված պատասխան կոճակը կոմպոզերի պատուհանում:'
10 | canned_replies_groups: 'Ավելացնել պահեստավորված պատասխանների հասանելիություն անձնակազմի անդամ չհանդիսացող խմբերի համար՝ ըստ խմբի անվանման'
11 | replies:
12 | default_reply:
13 | title: "Իմ առաջին պահեստավորված պատասխանը"
14 | body: |
15 | Սա պահեստավորված պատասխանի օրինակ է:
16 | Դուք կարող եք օգտագործել **markdown** Ձեր պատասխանների ոճը կառավարելու համար: Սեղմեք **նոր** կոճակընոր պատասխաններ ստեղծելու կամ **խմբագրել** կոճակը՝ գոյություն ունեցող պահեստավորված պատասխանը խմբագրելու կամ հեռացնելու համար:
17 |
18 | *Այս պահեստավորված պատասխանը կավելացվի, որբ պատասխանների ցանկը լինի դատարկ:*
19 |
--------------------------------------------------------------------------------
/config/locales/client.ur.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ur:
8 | js:
9 | canned_replies:
10 | composer_button_text: "طہ شدہ جوابات"
11 | title:
12 | name: "عنوان"
13 | invalid: عنوان خالی نہیں ھو سکتا
14 | content:
15 | name: "متن"
16 | invalid: متن خالی نہیں ھو سکتا
17 | insert:
18 | choose: "(ایک جواب کا انتخاب کیجیے)"
19 | insert_button: "جواب شامل کریں"
20 | new_button: "نیا"
21 | edit_button: "ترمیم کریں"
22 | modal_title: "طہ شدہ جواب شامل کریں"
23 | sort:
24 | alphabetically: "حروف تہجی ترتیب دیں"
25 | usage: "استعمال کی تعدد کے حساب سے ترتیب دیں"
26 | add:
27 | modal_title: "طہ شدہ جواب شامل کریں"
28 | edit:
29 | modal_title: "طہ شدہ جواب میں ترمیم کریں"
30 | back: "واپس"
31 |
--------------------------------------------------------------------------------
/config/locales/client.sl.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | sl:
8 | js:
9 | canned_replies:
10 | composer_button_text: "Pripravljeni odgovori"
11 | title:
12 | name: "Naslov"
13 | invalid: Naslov ne sme biti prazen.
14 | content:
15 | name: "Vsebina"
16 | invalid: Vsebina ne sme biti prazna.
17 | insert:
18 | choose: "(izberi odgovor)"
19 | insert_button: "Vstavi odgovor"
20 | new_button: "Nov"
21 | edit_button: "Uredi"
22 | modal_title: "Vstavi pripravljen odgovor"
23 | sort:
24 | alphabetically: "Uredi po abecednem redu"
25 | usage: "Uredi po pogostnosti uporabe"
26 | add:
27 | modal_title: "Dodaj pripravljen odgovor"
28 | edit:
29 | modal_title: "Uredi pripravljen odgovor"
30 | back: "Nazaj"
31 |
--------------------------------------------------------------------------------
/config/locales/client.hu.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | hu:
8 | js:
9 | canned_replies:
10 | composer_button_text: "Eltett válaszok"
11 | title:
12 | name: "Cím"
13 | invalid: Cím nem lehet üres.
14 | content:
15 | name: "Tartalom"
16 | invalid: Tartalom nem lehet üres.
17 | insert:
18 | choose: "(válasz választása)"
19 | insert_button: "Válasz Beillesztése"
20 | new_button: "Új"
21 | edit_button: "Szerkesztés"
22 | modal_title: "Eltett Válasz Beillesztése"
23 | sort:
24 | alphabetically: "Rendezés betűrendbe"
25 | usage: "Rendezés használat gyakorisága szerint"
26 | add:
27 | modal_title: "Eltett Válasz Hozzáadása"
28 | edit:
29 | modal_title: "Eltett Válasz Szerkesztése"
30 | back: "Vissza"
31 |
--------------------------------------------------------------------------------
/config/locales/client.ro.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ro:
8 | js:
9 | canned_replies:
10 | composer_button_text: "Răspunsuri predefinite"
11 | title:
12 | name: "Titlu"
13 | invalid: Titlul nu poate fi gol.
14 | content:
15 | name: "Conținut"
16 | invalid: Conținutul nu poate fi gol.
17 | insert:
18 | choose: "(alege un răspuns)"
19 | insert_button: "Inserează răspuns"
20 | new_button: "Nou"
21 | edit_button: "Editează"
22 | modal_title: "Inserează răspuns predefinit"
23 | sort:
24 | alphabetically: "Sortează alfabetic"
25 | usage: "Sortează după frecvența de utilizare"
26 | add:
27 | modal_title: "Adaugă răspuns predefinit"
28 | edit:
29 | modal_title: "Editează răspunsul predefinit"
30 | back: "Înapoi"
31 |
--------------------------------------------------------------------------------
/config/locales/client.uk.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | uk:
8 | js:
9 | canned_replies:
10 | composer_button_text: "Готові відповіді"
11 | title:
12 | name: "Назва"
13 | invalid: Назва не може бути порожньою.
14 | content:
15 | name: "Вміст"
16 | invalid: Вміст не може бути порожнім.
17 | insert:
18 | choose: "(виберіть відповідь)"
19 | insert_button: "Вставити відповідь"
20 | new_button: "Новий"
21 | edit_button: "Редагувати"
22 | modal_title: "Вставити готову відповідь"
23 | sort:
24 | alphabetically: "Сортування за алфавітом"
25 | usage: "Сортувати за частотою використання"
26 | add:
27 | modal_title: "Додати готову відповідь"
28 | edit:
29 | modal_title: "Редагувати готову відповідь"
30 | back: "Назад"
31 |
--------------------------------------------------------------------------------
/config/locales/client.pl_PL.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | pl_PL:
8 | js:
9 | canned_replies:
10 | composer_button_text: "Sugerowane odpowiedzi"
11 | title:
12 | name: "Tytuł"
13 | invalid: Tytuł nie może być pusty.
14 | content:
15 | name: "Teksty"
16 | invalid: Zawartość nie może być pusta.
17 | insert:
18 | choose: "(wybierz odpowiedź)"
19 | insert_button: "Wstaw odpowiedź"
20 | new_button: "Nowa"
21 | edit_button: "Edytuj"
22 | modal_title: "Wstaw sugerowaną odpowiedź"
23 | sort:
24 | alphabetically: "Sortuj alfabetycznie"
25 | usage: "Sortuj wg częstotliwości używania"
26 | add:
27 | modal_title: "Dodaj sugerowaną odpowiedź"
28 | edit:
29 | modal_title: "Edytuj sugerowaną odpowiedź"
30 | back: "Wstecz"
31 |
--------------------------------------------------------------------------------
/config/locales/client.hr.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | hr:
8 | js:
9 | canned_replies:
10 | composer_button_text: "Preddefinirani odgovori"
11 | title:
12 | name: "Naslov"
13 | invalid: Naslov ne može biti prazan.
14 | content:
15 | name: "Sadržaj"
16 | invalid: Sadržaj ne može biti prazan.
17 | insert:
18 | choose: "(izaberite odgovor)"
19 | insert_button: "Unesite odgovor"
20 | new_button: "Novo"
21 | edit_button: "Izmijeni"
22 | modal_title: "Umetnite predefinirani odgovor"
23 | sort:
24 | alphabetically: "Poredaj po abecedi"
25 | usage: "Poredaj prema učestalosti upotrebe"
26 | add:
27 | modal_title: "Dodaj predefinirani odgovor"
28 | edit:
29 | modal_title: "Uredi predefinirani odgovor"
30 | back: "Natrag"
31 |
--------------------------------------------------------------------------------
/config/locales/client.bs_BA.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | bs_BA:
8 | js:
9 | canned_replies:
10 | composer_button_text: "Konzervirani odgovori"
11 | title:
12 | name: "Naslov"
13 | invalid: Naslov ne može biti prazan.
14 | content:
15 | name: "Sadržaj"
16 | invalid: Sadržaj ne može biti prazan.
17 | insert:
18 | choose: "(odaberite odgovor)"
19 | insert_button: "Umetni odgovor"
20 | new_button: "New"
21 | edit_button: "Izmijeni"
22 | modal_title: "Umetni Kozervirani Odgovor"
23 | sort:
24 | alphabetically: "Sortiraj po abecednom redu"
25 | usage: "Sortiraj po frekvenciji upotrebe"
26 | add:
27 | modal_title: "Dodaj Konzervirani Odgovor"
28 | edit:
29 | modal_title: "Uredi Konzervirani Odgovor"
30 | back: "Prethodno"
31 |
--------------------------------------------------------------------------------
/config/locales/client.ca.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ca:
8 | js:
9 | canned_replies:
10 | composer_button_text: "Respostes enllaunades"
11 | title:
12 | name: "Títol"
13 | invalid: El títol no pot estar buit.
14 | content:
15 | name: "Contingut"
16 | invalid: El contingut no pot estar buit.
17 | insert:
18 | choose: "(trieu una resposta)"
19 | insert_button: "Insereix una resposta"
20 | new_button: "Nou"
21 | edit_button: "Edita"
22 | modal_title: "Introduïu una resposta enllaunada"
23 | sort:
24 | alphabetically: "Ordena alfabèticament"
25 | usage: "Ordena per freqüència d'ús"
26 | add:
27 | modal_title: "Afegeix una resposta enllaunada"
28 | edit:
29 | modal_title: "Edita la resposta enllaunada"
30 | back: "Enrere"
31 |
--------------------------------------------------------------------------------
/assets/javascripts/discourse/templates/connectors/editor-preview/canned-replies.hbs:
--------------------------------------------------------------------------------
1 | {{#if cannedVisible}}
2 |
3 |
4 | {{#if canEdit}}
5 |
6 |
12 |
17 |
18 |
23 |
24 | {{/if}}
25 |
26 | {{#each filteredReplies as |r|}}
27 |
28 | {{/each}}
29 |
30 |
31 | {{/if}}
--------------------------------------------------------------------------------
/config/locales/client.sw.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | sw:
8 | js:
9 | canned_replies:
10 | composer_button_text: "Majibu ambayo yameifadhiwa"
11 | title:
12 | name: "Kichwa cha Habari"
13 | invalid: Kichwa cha habari lazima kiwe na taarifa.
14 | content:
15 | name: "Maudhui"
16 | invalid: Sehemu ya maudhui lazima iwe na taarifa.
17 | insert:
18 | choose: "(chagua jibu)"
19 | insert_button: "Andika Jibu Lako"
20 | new_button: "Mpya"
21 | edit_button: "Hariri"
22 | modal_title: "Chomeka Jibu Lililohifadhiwa"
23 | sort:
24 | alphabetically: "Panga kwa alfabeti"
25 | usage: "Chuja kwa mara ya utumiaji"
26 | add:
27 | modal_title: "Ongeza jibu lililohifadhiwa"
28 | edit:
29 | modal_title: "Hariri Taarifa zilizohifadhiwa"
30 | back: "Iliyopita"
31 |
--------------------------------------------------------------------------------
/spec/plugin_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails_helper"
4 |
5 | describe DiscourseCannedReplies do
6 | let(:user) { Fabricate(:user) }
7 | let(:admin) { Fabricate(:admin) }
8 | let(:group) { Fabricate(:group) }
9 |
10 | it "works for staff and users in group" do
11 | SiteSetting.canned_replies_groups = group.name
12 | expect(admin.can_use_canned_replies?).to eq(true)
13 | expect(user.can_use_canned_replies?).to eq(false)
14 |
15 | group.add(user)
16 | expect(user.reload.can_use_canned_replies?).to eq(true)
17 | end
18 |
19 | it "works for everyone when enabled" do
20 | expect(user.can_use_canned_replies?).to eq(false)
21 |
22 | SiteSetting.canned_replies_everyone_enabled = true
23 | expect(user.reload.can_use_canned_replies?).to eq(true)
24 | end
25 |
26 | it "allows everyone to edit when enabled" do
27 | expect(user.can_edit_canned_replies?).to eq(false)
28 |
29 | SiteSetting.canned_replies_everyone_can_edit = true
30 | expect(user.reload.can_edit_canned_replies?).to eq(true)
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/config/locales/server.pl_PL.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | pl_PL:
8 | site_settings:
9 | canned_replies_enabled: 'Pokazuj przycisk wstawiania sugerowanej odpowiedzi w oknie edytora?'
10 | canned_replies_groups: 'Dodaj dostęp do sugerowanych odpowiedzi dla grup o nazwie'
11 | canned_replies_everyone_enabled: 'Pozwól wszystkim na używanie gotowych odpowiedzi'
12 | canned_replies_everyone_can_edit: 'Pozwól wszystkim tworzyć, edytować i usuwać gotowe odpowiedzi'
13 | replies:
14 | default_reply:
15 | title: "Moja pierwsza sugerowana odpowiedź"
16 | body: |
17 | To jest przykład sugerowanej odpowiedzi.
18 | Możesz użyć **markdown**, by stylizować swoje odpowiedzi. Naciśnij przycisk **nowa**, by stworzyć nową odpowiedź lub **edytuj*, by edytować lub usunąć sugerowaną odpowiedź.
19 |
20 | *Ta odpowiedź sugerowana będzie dodana, gdy lista odpowiedzi jest pusta.*
21 |
--------------------------------------------------------------------------------
/config/locales/server.sv.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | sv:
8 | site_settings:
9 | canned_replies_enabled: 'Vill du visa knappen Infoga förberett svar i redigeringsfönstret?'
10 | canned_replies_groups: 'Lägg till åtkomst till förberedda svar för icke-personalgrupper efter gruppnamn'
11 | canned_replies_everyone_enabled: 'Tillåt att alla använder förberedda svar'
12 | canned_replies_everyone_can_edit: 'Tillåt att alla skapar, ändrar eller raderar förberedda svar'
13 | replies:
14 | default_reply:
15 | title: "Mitt första förberedda svar"
16 | body: |
17 | Detta är ett exempel på ett förberett svar.
18 | Du kan använda **markdown** för att utforma dina svar. Klicka på knappen **new** för att skapa nya svar eller på knappen **edit** för att redigera eller ta bort ett befintligt förberett svar.
19 |
20 | *Detta förberedda svar läggs till när svarslistan är tom.*
21 |
--------------------------------------------------------------------------------
/config/locales/server.tr_TR.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | tr_TR:
8 | site_settings:
9 | canned_replies_enabled: 'Yazar penceresinde eklenen hazır yanıt düğmesini göster?'
10 | canned_replies_groups: 'Personel olmayan gruplar için hazır cevaplara grup adına göre erişim ekleyin'
11 | canned_replies_everyone_enabled: 'Herkesin hazır cevapları kullanmasına izin ver'
12 | canned_replies_everyone_can_edit: 'Herkesin hazır cevaplar oluşturmasına, düzenlemesine ve silmesine izin ver'
13 | replies:
14 | default_reply:
15 | title: "İlk hazır yanıtım"
16 | body: |
17 | Bu örnek bir hazır yanıttır.
18 | Cevaplarınızı şekillendirmek için ** markdown ** kullanabilirsiniz. Yeni yanıtlar yaratmak için ** yeni ** düğmesini veya mevcut bir hazır yanıtı düzenlemek veya kaldırmak için ** düzenle ** düğmesini tıklayın.
19 |
20 | * Bu hazır yanıt, yanıtlar listesi boşken eklenecektir. *
21 |
--------------------------------------------------------------------------------
/config/locales/client.hy.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | hy:
8 | js:
9 | canned_replies:
10 | composer_button_text: "Պահեստավորված պատասխաններ"
11 | title:
12 | name: "Վերնագիր"
13 | invalid: 'Վերնագիրը չի կարող դատարկ լինել:'
14 | content:
15 | name: "Բովանդակություն"
16 | invalid: 'Բովանդակությունը չի կարող դատարկ լինել:'
17 | insert:
18 | choose: "(ընտրեք պատասխան)"
19 | insert_button: "Մուտքագրել Պատասխան"
20 | new_button: "Նոր"
21 | edit_button: "Խմբագրել"
22 | modal_title: "Մուտքագրեք Պահեստավորված Պատասխանը"
23 | sort:
24 | alphabetically: "Դասավորել այբբենական կարգով"
25 | usage: "Դասավորել ըստ օգտագործման հաճախականության"
26 | add:
27 | modal_title: "Ավելացնել Պահեստավորված Պատասխան"
28 | edit:
29 | modal_title: "Խմբագրել Պահեստավորված Պատասխանը"
30 | back: "Ետ"
31 |
--------------------------------------------------------------------------------
/config/locales/client.tr_TR.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | tr_TR:
8 | js:
9 | canned_replies:
10 | filter_hint: "Başlığa göre filtrele"
11 | composer_button_text: "Hazır cevaplar"
12 | title:
13 | name: "Başlık"
14 | invalid: Başlık boş olamaz.
15 | content:
16 | name: "İçerik"
17 | invalid: İçerik boş olamaz.
18 | insert:
19 | choose: "(bir yanıt seçin)"
20 | insert_button: "Yanıt Ekle"
21 | new_button: "Yeni"
22 | edit_button: "Düzenle"
23 | modal_title: "Hazır Yanıt Ekle"
24 | sort:
25 | alphabetically: "Alfabetik olarak sırala"
26 | usage: "Kullanım sıklığına göre sırala"
27 | add:
28 | modal_title: "Hazır Yanıt Ekle"
29 | edit:
30 | modal_title: "Hazır Yanıtı Düzenle"
31 | remove_confirm: "Bu hazır yanıtı silmek istediğinizden emin misiniz?"
32 | back: "Geri"
33 |
--------------------------------------------------------------------------------
/config/locales/server.ru.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ru:
8 | site_settings:
9 | canned_replies_enabled: 'Отображать кнопку вставки готовых ответов в редакторе'
10 | canned_replies_groups: 'Добавить доступ к готовым ответам для групп, не связанных с персоналом (по имени группы)'
11 | canned_replies_everyone_enabled: 'Разрешать всем использовать готовые ответы'
12 | canned_replies_everyone_can_edit: 'Разрешать всем создавать, редактировать и удалять готовые ответы'
13 | replies:
14 | default_reply:
15 | title: "Мой первый готовый ответ"
16 | body: |
17 | Это пример готового ответа.
18 | Можно использовать разметку **markdown** в тексте готовых ответов. Нажмите на кнопку **Новый**, чтобы создать новый ответ, или на кнопку **Редактировать**, чтобы отредактировать или удалить существующий.
19 |
20 | *Этот готовый ответ добавится автоматически, пока список готовых ответов еще пуст.*
21 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | ast (2.4.2)
5 | json (2.6.2)
6 | parallel (1.22.1)
7 | parser (3.1.2.1)
8 | ast (~> 2.4.1)
9 | prettier_print (1.2.0)
10 | rainbow (3.1.1)
11 | regexp_parser (2.6.0)
12 | rexml (3.2.5)
13 | rubocop (1.36.0)
14 | json (~> 2.3)
15 | parallel (~> 1.10)
16 | parser (>= 3.1.2.1)
17 | rainbow (>= 2.2.2, < 4.0)
18 | regexp_parser (>= 1.8, < 3.0)
19 | rexml (>= 3.2.5, < 4.0)
20 | rubocop-ast (>= 1.20.1, < 2.0)
21 | ruby-progressbar (~> 1.7)
22 | unicode-display_width (>= 1.4.0, < 3.0)
23 | rubocop-ast (1.21.0)
24 | parser (>= 3.1.1.0)
25 | rubocop-discourse (3.0)
26 | rubocop (>= 1.1.0)
27 | rubocop-rspec (>= 2.0.0)
28 | rubocop-rspec (2.13.2)
29 | rubocop (~> 1.33)
30 | ruby-progressbar (1.11.0)
31 | syntax_tree (5.1.0)
32 | prettier_print (>= 1.2.0)
33 | unicode-display_width (2.3.0)
34 |
35 | PLATFORMS
36 | ruby
37 |
38 | DEPENDENCIES
39 | rubocop-discourse
40 | syntax_tree
41 |
42 | BUNDLED WITH
43 | 2.1.4
44 |
--------------------------------------------------------------------------------
/config/locales/client.ar.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ar:
8 | js:
9 | canned_replies:
10 | filter_hint: "التصفية حسب العنوان..."
11 | composer_button_text: "الردود الآلية"
12 | title:
13 | name: "العنوان"
14 | invalid: لا يمكن للعنوان أن يكون فارغًا.
15 | content:
16 | name: "المحتوى"
17 | invalid: لا يمكن للمحتوى أن يكون فارغًا.
18 | insert:
19 | choose: "(اختر ردًا)"
20 | insert_button: "أضف ردًا"
21 | new_button: "جديد"
22 | edit_button: "تعديل"
23 | modal_title: "إضافة رد محدَّد مسبقًا"
24 | sort:
25 | alphabetically: "الترتيب حسب الاسم"
26 | usage: "الترتيب حسب تكرار الاستخدام"
27 | add:
28 | modal_title: "إضافة رد محدَّد مسبقًا"
29 | edit:
30 | modal_title: "تعديل الرد المحدَّد مسبقًا"
31 | remove_confirm: "هل تريد بالتأكيد حذف هذا الرد المحدَّد مسبقًا؟"
32 | back: "الرجوع"
33 |
--------------------------------------------------------------------------------
/config/locales/server.sl.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | sl:
8 | site_settings:
9 | canned_replies_enabled: 'Prikaži gumb za vstavljanje pripravljenega odgovora v oknu urejevalnika?'
10 | canned_replies_groups: 'Omogoči uporabo pripravljenih odgovorov tudi drugim poimenskim skupinam uporabnikov, ne samo osebju'
11 | canned_replies_everyone_enabled: 'Dovoli vsem, da uporabljajo pripravljene odgovore'
12 | canned_replies_everyone_can_edit: 'Dovoli vsem ustvarjanje, urejanje in brisanje pripravljenih odgovorov'
13 | replies:
14 | default_reply:
15 | title: "Moj prvi pripravljen odgovor"
16 | body: |
17 | To je primer pripravljenega odgovora.
18 | Za oblikovanje odgovorov lahko uporabite **markdown**. Kliknite na gumb **nov**, da ustvarite nove odgovore ali na gumb **uredi**, da uredite ali odstranite obstoječe odgovore.
19 |
20 | *Ta pripravljen odgovor bo dodan, če je seznam pripravljenih odgovorov prazen.*
21 |
--------------------------------------------------------------------------------
/config/locales/client.sv.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | sv:
8 | js:
9 | canned_replies:
10 | filter_hint: "Filtrera efter titel..."
11 | composer_button_text: "Förberedda svar"
12 | title:
13 | name: "Rubrik"
14 | invalid: Rubriken kan inte vara tom.
15 | content:
16 | name: "Innehåll"
17 | invalid: Innehållet kan inte vara tomt.
18 | insert:
19 | choose: "(välj ett svar)"
20 | insert_button: "Infoga svar"
21 | new_button: "Ny"
22 | edit_button: "Redigera"
23 | modal_title: "Infoga förberett svar"
24 | sort:
25 | alphabetically: "Sortera alfabetiskt"
26 | usage: "Sortera efter användningsfrekvens"
27 | add:
28 | modal_title: "Lägg till förberett svar"
29 | edit:
30 | modal_title: "Redigera förberett svar"
31 | remove_confirm: "Är du säker på att du vill radera detta förberedda svar?"
32 | back: "Tillbaka"
33 |
--------------------------------------------------------------------------------
/config/locales/server.nl.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | nl:
8 | site_settings:
9 | canned_replies_enabled: 'Knop Sjabloonantwoord invoegen weergeven in editorvenster?'
10 | canned_replies_groups: 'Toegang tot sjabloonantwoorden voor niet-medewerkergroepen toevoegen op groepsnaam'
11 | canned_replies_everyone_enabled: 'Iedereen toestaan sjabloonantwoorden te gebruiken'
12 | canned_replies_everyone_can_edit: 'Iedereen toestaan sjabloonantwoorden te maken, bewerken en verwijderen'
13 | replies:
14 | default_reply:
15 | title: "Mijn eerste sjabloonantwoord"
16 | body: |
17 | Dit is een voorbeeld van een sjabloonantwoord.
18 | Je kunt **markdown** gebruiken om stijl aan je antwoorden te geven. Klik op de knop **Nieuw** om een nieuw antwoord te maken of op de knop **Bewerken** om een bestaand sjabloonantwoord te bewerken of te verwijderen.
19 |
20 | *Dit sjabloonantwoord wordt toegevoegd wanneer de antwoordenlijst leeg is.*
21 |
--------------------------------------------------------------------------------
/config/locales/server.pt.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | pt:
8 | site_settings:
9 | canned_replies_enabled: 'Mostrar o botão de inserção de resposta gravada na janela do compor?'
10 | canned_replies_groups: 'Adicionar acesso para as respostas gravadas para os grupos não pertencentes à equipa por nome do grupo'
11 | canned_replies_everyone_enabled: 'Permitir que todos utilizem as respostas gravadas'
12 | canned_replies_everyone_can_edit: 'Permitir que todos criem, editem e eliminem as respostas gravadas'
13 | replies:
14 | default_reply:
15 | title: "A minha primeira resposta gravada"
16 | body: |
17 | Este é um exemplo de resposta gravada.
18 | Pode utilizar a **marcação** para estilizar as suas respostas. Clique no botão **nova** para criar novas respostas ou o botão **editar** para editar ou remover uma resposta existente gravada.
19 |
20 | *Esta resposta gravada será adicionada quando a lista de respostas estiver vazia.*
21 |
--------------------------------------------------------------------------------
/config/locales/client.nl.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | nl:
8 | js:
9 | canned_replies:
10 | filter_hint: "Filteren op titel..."
11 | composer_button_text: "Sjabloonantwoorden"
12 | title:
13 | name: "Titel"
14 | invalid: Titel mag niet leeg zijn.
15 | content:
16 | name: "Inhoud"
17 | invalid: Inhoud mag niet leeg zijn.
18 | insert:
19 | choose: "(kies een antwoord)"
20 | insert_button: "Antwoord invoegen"
21 | new_button: "Nieuw"
22 | edit_button: "Bewerken"
23 | modal_title: "Sjabloonantwoord invoegen"
24 | sort:
25 | alphabetically: "Alfabetisch sorteren"
26 | usage: "Sorteren op gebruiksfrequentie"
27 | add:
28 | modal_title: "Sjabloonantwoord toevoegen"
29 | edit:
30 | modal_title: "Sjabloonantwoord bewerken"
31 | remove_confirm: "Weet je zeker dat je dit sjabloonantwoord wilt verwijderen?"
32 | back: "Terug"
33 |
--------------------------------------------------------------------------------
/config/locales/client.de.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | de:
8 | js:
9 | canned_replies:
10 | filter_hint: "Nach Titel filtern …"
11 | composer_button_text: "Antwortvorlagen"
12 | title:
13 | name: "Titel"
14 | invalid: Der Titel darf nicht leer sein.
15 | content:
16 | name: "Inhalt"
17 | invalid: Der Inhalt darf nicht leer sein.
18 | insert:
19 | choose: "(wähle eine Antwort)"
20 | insert_button: "Antwort einfügen"
21 | new_button: "Neu"
22 | edit_button: "Bearbeiten"
23 | modal_title: "Antwortvorlage einfügen"
24 | sort:
25 | alphabetically: "Alphabetisch sortieren"
26 | usage: "Sortieren nach Nutzungshäufigkeit"
27 | add:
28 | modal_title: "Antwortvorlage hinzufügen"
29 | edit:
30 | modal_title: "Antwortvorlage bearbeiten"
31 | remove_confirm: "Möchtest du wirklich diese Antwortvorlage löschen?"
32 | back: "Zurück"
33 |
--------------------------------------------------------------------------------
/config/locales/client.fi.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | fi:
8 | js:
9 | canned_replies:
10 | filter_hint: "Suodata otsikon mukaan..."
11 | composer_button_text: "Tallennetut vastaukset"
12 | title:
13 | name: "Otsikko"
14 | invalid: Otsikko ei voi olla tyhjä.
15 | content:
16 | name: "Sisältö"
17 | invalid: Sisältö ei voi olla tyhjä.
18 | insert:
19 | choose: "(valitse vastaus)"
20 | insert_button: "Lisää vastaus"
21 | new_button: "Uusi"
22 | edit_button: "Muokkaa"
23 | modal_title: "Lisää tallennettu vastaus"
24 | sort:
25 | alphabetically: "Järjestä aakkosjärjestykseen"
26 | usage: "Järjestä käytön mukaan"
27 | add:
28 | modal_title: "Lisää tallennettu vastaus"
29 | edit:
30 | modal_title: "Muokkaa tallennettua vastausta"
31 | remove_confirm: "Haluatko varmasti poistaa tämän tallennetun vastauksen?"
32 | back: "Takaisin"
33 |
--------------------------------------------------------------------------------
/config/locales/client.ru.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ru:
8 | js:
9 | canned_replies:
10 | filter_hint: "Фильтр по названию..."
11 | composer_button_text: "Готовые ответы"
12 | title:
13 | name: "Название"
14 | invalid: Название не может быть пустым.
15 | content:
16 | name: "Содержание"
17 | invalid: Содержание не может быть пустым.
18 | insert:
19 | choose: "(выберите ответ)"
20 | insert_button: "Вставить ответ"
21 | new_button: "Новый"
22 | edit_button: "Редактировать"
23 | modal_title: "Вставить готовый ответ"
24 | sort:
25 | alphabetically: "Сортировать в алфавитном порядке"
26 | usage: "Сортировать по частоте использования"
27 | add:
28 | modal_title: "Добавить готовый ответ"
29 | edit:
30 | modal_title: "Редактировать готовый ответ"
31 | remove_confirm: "Вы действительно хотите удалить этот готовый ответ?"
32 | back: "Назад"
33 |
--------------------------------------------------------------------------------
/config/locales/server.de.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | de:
8 | site_settings:
9 | canned_replies_enabled: 'Schaltfläche zum Einfügen von Antwortvorlagen im Editor-Fenster anzeigen?'
10 | canned_replies_groups: 'Zugriff auf Antwortvorlagen für Nicht-Team-Gruppen auf Basis des Gruppennamens gewähren'
11 | canned_replies_everyone_enabled: 'Jedem erlauben, Antwortvorlagen zu verwenden'
12 | canned_replies_everyone_can_edit: 'Jedem erlauben, Antwortvorlagen zu erstellen, zu bearbeiten und zu löschen'
13 | replies:
14 | default_reply:
15 | title: "Meine erste Antwortvorlage"
16 | body: |
17 | Das ist ein Beispiel für eine Antwortvorlage.
18 | Du kannst **Markdown** verwenden, um deine Antworten zu formatieren. Klicke die **Neu**-Schaltfläche an, um neue Antworten zu erstellen, bzw. die **Bearbeiten**-Schaltfläche, um bestehende Vorlagen zu bearbeiten oder zu entfernen.
19 |
20 | *Diese Antwortvorlage wird hinzugefügt, wenn die Antwortliste leer ist.*
21 |
--------------------------------------------------------------------------------
/config/locales/server.es.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | es:
8 | site_settings:
9 | canned_replies_enabled: '¿Mostrar el botón de insertar respuestas predefinidas en el editor?'
10 | canned_replies_groups: 'Añadir acceso a las respuestas predefinidas para grupos que no sean parte del staff por nombre de grupo'
11 | canned_replies_everyone_enabled: 'Permitir a todos usar respuestas predefinidas'
12 | canned_replies_everyone_can_edit: 'Permitir a todos crear, editar, y eliminar respuestas predefinidas'
13 | replies:
14 | default_reply:
15 | title: "Mi primera respuesta predefinida"
16 | body: |
17 | Este es un ejemplo de una respuesta predefinida.
18 | Puedes usar **marcado** para dar formato a tus respuestas. Haz clic en el botón **nueva** para crear nuevas respuestas o en el botón de **editar** para editar o eliminar alguna respuesta predefinida ya existente.
19 |
20 | *Esta respuesta predefinida se añadirá cuando la lista de respuestas esté vacía.*
21 |
--------------------------------------------------------------------------------
/config/locales/server.fi.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | fi:
8 | site_settings:
9 | canned_replies_enabled: 'Näytä lisää tallennettu vastaus -painike viesti-ikkunassa?'
10 | canned_replies_groups: 'Lisää pääsy tallennettuihin vastauksiin muille kuin henkilökunnan ryhmille ryhmän nimen perusteella'
11 | canned_replies_everyone_enabled: 'Salli kaikkien käyttää tallennettuja vastauksia'
12 | canned_replies_everyone_can_edit: 'Salli kaikkien luoda, muokata ja poistaa tallennettuja vastauksia'
13 | replies:
14 | default_reply:
15 | title: "Ensimmäinen tallennettu vastaukseni"
16 | body: |
17 | Tämä on esimerkki tallennetusta vastauksesta.
18 | Vastausten muotoilussa voi käyttää **markdownia**. Voit aloittaa uuden vastauksen työstämisen klikkaamalla **Uusi**-painiketta. Voit muokata olemassa olevaa tallennettua vastausta tai poistaa sellaisen **Muokkaa**-painikkeen avulla.
19 |
20 | *Tämä tallennettu vastaus lisätään, kun muita tallennettuja vastauksia ei ole.*
21 |
--------------------------------------------------------------------------------
/config/locales/server.ro.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | ro:
8 | site_settings:
9 | canned_replies_enabled: 'Afișezi butonul de inserare răspuns predefinit în fereastra editorului?'
10 | canned_replies_groups: 'Adaugă acces la răspunsurile predefinite pentru grupurile care nu fac parte din personal după numele grupului'
11 | canned_replies_everyone_enabled: 'Permite oricui să folosească răspunsuri predefinite'
12 | canned_replies_everyone_can_edit: 'Permite oricui să creeze, să editeze și să șteargă răspunsuri predefinite'
13 | replies:
14 | default_reply:
15 | title: "Primul meu răspuns predefinit"
16 | body: |
17 | Acesta este un exemplu de răspuns predefinit.
18 | Poţi utiliza **markdown** pentru a stiliza răspunsurile. Apasă pe butonul **nou** pentru a crea răspunsuri noi sau pe butonul **editează** pentru a edita sau a șterge un răspuns predefinit deja existent.
19 |
20 | *Acest răspuns predefinit va fi adăugat când lista de răspunsuri este goală.*
21 |
--------------------------------------------------------------------------------
/config/locales/client.it.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | it:
8 | js:
9 | canned_replies:
10 | filter_hint: "Filtra per titolo..."
11 | composer_button_text: "Risposte predefinite"
12 | title:
13 | name: "Titolo"
14 | invalid: Il titolo non può essere vuoto.
15 | content:
16 | name: "Contenuto"
17 | invalid: Il contenuto non può essere vuoto.
18 | insert:
19 | choose: "(scegli una risposta)"
20 | insert_button: "Inserisci Risposta"
21 | new_button: "Nuovo"
22 | edit_button: "Modifica"
23 | modal_title: "Inserisci Risposta Predefinita"
24 | sort:
25 | alphabetically: "Ordina alfabeticamente"
26 | usage: "Ordina per frequenza di utilizzo"
27 | add:
28 | modal_title: "Aggiungi Risposta Predefinita"
29 | edit:
30 | modal_title: "Modifica Risposta Predefinita"
31 | remove_confirm: "Vuoi cancellare questa risposta predefinita?"
32 | back: "Indietro"
33 |
--------------------------------------------------------------------------------
/config/locales/client.es.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | es:
8 | js:
9 | canned_replies:
10 | filter_hint: "Filtrar por título ..."
11 | composer_button_text: "Respuestas predefinidas"
12 | title:
13 | name: "Título"
14 | invalid: El título no puede estar vacío.
15 | content:
16 | name: "Contenido"
17 | invalid: El contenido no puede estar vacío.
18 | insert:
19 | choose: "(elige una respuesta)"
20 | insert_button: "Insertar respuesta"
21 | new_button: "Nuevo"
22 | edit_button: "Editar"
23 | modal_title: "Insertar respuesta predefinida"
24 | sort:
25 | alphabetically: "Ordenar alfabéticamente"
26 | usage: "Ordenar por frecuencia de uso"
27 | add:
28 | modal_title: "Añadir respuesta predefinida"
29 | edit:
30 | modal_title: "Editar respuesta predefinida"
31 | remove_confirm: "¿Seguro que quieres eliminar esta respuesta predefinida?"
32 | back: "Atrás"
33 |
--------------------------------------------------------------------------------
/config/locales/server.pt_BR.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | pt_BR:
8 | site_settings:
9 | canned_replies_enabled: 'Mostrar botão de inserir resposta predefinida na janela do compositor?'
10 | canned_replies_groups: 'Adicionar acesso às respostas predefinidas para grupos que são sejam de membros da equipe por nome do grupo'
11 | canned_replies_everyone_enabled: 'Permitir que todas as pessoas usem respostas predefinidas'
12 | canned_replies_everyone_can_edit: 'Permitir que todas as pessoas criem, editem e excluam respostas predefinidas'
13 | replies:
14 | default_reply:
15 | title: "Minha primeira resposta predefinida"
16 | body: |
17 | Este é um exemplo de resposta predefinida.
18 | Você pode usar **markdown** para estilizar suas respostas. Clique no botão **novo** para criar novas respostas ou no botão **editar** para editar ou remover uma resposta predefinida existente.
19 |
20 | *Esta resposta predefinida será adicionada quando a lista de respostas estiver vazia.*
21 |
--------------------------------------------------------------------------------
/config/locales/client.fr.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | fr:
8 | js:
9 | canned_replies:
10 | filter_hint: "Filtrer par titre…"
11 | composer_button_text: "Réponses prédéfinies"
12 | title:
13 | name: "Titre"
14 | invalid: Le titre ne peut pas être vide.
15 | content:
16 | name: "Contenu"
17 | invalid: Le contenu ne peut pas être vide.
18 | insert:
19 | choose: "(choisir une réponse)"
20 | insert_button: "Insérer la réponse"
21 | new_button: "Nouveau"
22 | edit_button: "Modifier"
23 | modal_title: "Insérer la réponse prédéfinie"
24 | sort:
25 | alphabetically: "Trier par ordre alphabétique"
26 | usage: "Trier par fréquence d'utilisation"
27 | add:
28 | modal_title: "Ajouter une réponse prédéfinie"
29 | edit:
30 | modal_title: "Modifier une réponse prédéfinie"
31 | remove_confirm: "Voulez-vous vraiment supprimer cette réponse prédéfinie ?"
32 | back: "Retour"
33 |
--------------------------------------------------------------------------------
/config/locales/client.pt_BR.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | pt_BR:
8 | js:
9 | canned_replies:
10 | filter_hint: "Filtrar por título..."
11 | composer_button_text: "Respostas predefinidas"
12 | title:
13 | name: "Título"
14 | invalid: O título não pode ficar vazio.
15 | content:
16 | name: "Conteúdo"
17 | invalid: O conteúdo não pode ficar vazio.
18 | insert:
19 | choose: "(escolha uma resposta)"
20 | insert_button: "Inserir resposta"
21 | new_button: "Novo"
22 | edit_button: "Editar"
23 | modal_title: "Inserir resposta predefinida"
24 | sort:
25 | alphabetically: "Ordenar alfabeticamente"
26 | usage: "Ordenar por frequência de uso"
27 | add:
28 | modal_title: "Adicionar resposta predefinida"
29 | edit:
30 | modal_title: "Editar resposta predefinida"
31 | remove_confirm: "Você tem certeza de que deseja excluir esta resposta predefinida?"
32 | back: "Voltar"
33 |
--------------------------------------------------------------------------------
/config/locales/server.it.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | it:
8 | site_settings:
9 | canned_replies_enabled: 'Mostrare il pulsante per inserire le risposte predefinite nella finestra di composizione?'
10 | canned_replies_groups: 'Aggiungi l''accesso alle risposte predefinite per i gruppi non appartenenti allo staff in base al nome del gruppo'
11 | canned_replies_everyone_enabled: 'Consenti a tutti di utilizzare le risposte predefinite'
12 | canned_replies_everyone_can_edit: 'Consenti a tutti di creare, modificare ed eliminare le risposte predefinite'
13 | replies:
14 | default_reply:
15 | title: "La mia prima risposta predefinita"
16 | body: |
17 | Questo è un esempio di risposta predefinita.
18 | Puoi usare **markdown** per formattare le tue risposte. Clicca il pulsante **nuovo** per creare nuove risposte o il pulsante **modifica** per modificare o rimuovere una risposta predefinita esistente.
19 |
20 | *Questa risposta predefinita verrà aggiunta quando la lista delle risposte è vuota.*
21 |
--------------------------------------------------------------------------------
/config/locales/server.fr.yml:
--------------------------------------------------------------------------------
1 | # WARNING: Never edit this file.
2 | # It will be overwritten when translations are pulled from Crowdin.
3 | #
4 | # To work with us on translations, join this project:
5 | # https://translate.discourse.org/
6 |
7 | fr:
8 | site_settings:
9 | canned_replies_enabled: 'Afficher le bouton pour insérer une réponse prédéfinie dans la fenêtre de rédaction ?'
10 | canned_replies_groups: 'Ajouter un accès aux réponses prédéfinies pour les groupes ne faisant pas partie des responsables par nom de groupe'
11 | canned_replies_everyone_enabled: 'Autoriser tout le monde à utiliser les réponses prédéfinies'
12 | canned_replies_everyone_can_edit: 'Autoriser tout le monde à créer, modifier et supprimer des réponses prédéfinies'
13 | replies:
14 | default_reply:
15 | title: "Ma première réponse prédéfinie"
16 | body: |
17 | Ceci est un exemple de réponse prédéfinie.
18 | Vous pouvez utiliser le **marquage** dans vos réponses. Cliquez sur le bouton **nouveau** pour créer de nouvelles réponses ou sur le bouton **modifier** pour modifier ou supprimer une réponse prédéfinie existante.
19 |
20 | *Cette réponse prédéfinie sera ajoutée quand la liste de réponses sera vide.*
21 |
--------------------------------------------------------------------------------
/assets/javascripts/initializers/add-canned-replies-ui-builder.js:
--------------------------------------------------------------------------------
1 | import { withPluginApi } from "discourse/lib/plugin-api";
2 | import showModal from "discourse/lib/show-modal";
3 |
4 | function initializeCannedRepliesUIBuilder(api) {
5 | api.modifyClass("controller:composer", {
6 | pluginId: "discourse-canned-replies",
7 | actions: {
8 | showCannedRepliesButton() {
9 | if (this.site.mobileView) {
10 | showModal("canned-replies").set("composerModel", this.model);
11 | } else {
12 | this.appEvents.trigger("composer:show-preview");
13 | this.appEvents.trigger("canned-replies:show");
14 | }
15 | },
16 | },
17 | });
18 |
19 | api.addToolbarPopupMenuOptionsCallback(() => {
20 | return {
21 | id: "canned_replies_button",
22 | icon: "far-clipboard",
23 | action: "showCannedRepliesButton",
24 | label: "canned_replies.composer_button_text",
25 | };
26 | });
27 | }
28 |
29 | export default {
30 | name: "add-canned-replies-ui-builder",
31 |
32 | initialize(container) {
33 | const siteSettings = container.lookup("site-settings:main");
34 | const currentUser = container.lookup("current-user:main");
35 | if (
36 | siteSettings.canned_replies_enabled &&
37 | currentUser &&
38 | currentUser.can_use_canned_replies
39 | ) {
40 | withPluginApi("0.5", initializeCannedRepliesUIBuilder);
41 | }
42 | },
43 | };
44 |
--------------------------------------------------------------------------------
/assets/javascripts/controllers/new-reply.js:
--------------------------------------------------------------------------------
1 | import Controller from "@ember/controller";
2 | import ModalFunctionality from "discourse/mixins/modal-functionality";
3 | import showModal from "discourse/lib/show-modal";
4 | import { ajax } from "discourse/lib/ajax";
5 | import discourseComputed from "discourse-common/utils/decorators";
6 | import { popupAjaxError } from "discourse/lib/ajax-error";
7 |
8 | export default Controller.extend(ModalFunctionality, {
9 | newTitle: "",
10 | newContent: "",
11 |
12 | onShow() {
13 | this.setProperties({
14 | newTitle: "",
15 | newContent: "",
16 | });
17 | },
18 |
19 | @discourseComputed("newTitle", "newContent")
20 | disableSaveButton(newTitle, newContent) {
21 | return newTitle === "" || newContent === "";
22 | },
23 |
24 | actions: {
25 | save() {
26 | ajax("/canned_replies", {
27 | type: "POST",
28 | data: { title: this.newTitle, content: this.newContent },
29 | })
30 | .then(() => {
31 | this.send("closeModal");
32 | if (this.site.mobileView) {
33 | showModal("canned-replies");
34 | } else {
35 | this.appEvents.trigger("canned-replies:show");
36 | }
37 | })
38 | .catch(popupAjaxError);
39 | },
40 |
41 | cancel() {
42 | this.send("closeModal");
43 | if (this.site.mobileView) {
44 | showModal("canned-replies");
45 | } else {
46 | this.appEvents.trigger("canned-replies:show");
47 | }
48 | },
49 | },
50 | });
51 |
--------------------------------------------------------------------------------
/assets/javascripts/discourse/templates/modal/canned-replies.hbs:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
20 |
21 | {{#if selectedReply}}
22 |
23 |
{{cook-text selectedReply.content}}
24 |
25 | {{/if}}
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/assets/javascripts/components/canned-reply.js:
--------------------------------------------------------------------------------
1 | import Component from "@ember/component";
2 | import showModal from "discourse/lib/show-modal";
3 | import applyReply from "discourse/plugins/discourse-canned-replies/lib/apply-reply";
4 | import { getOwner } from "discourse-common/lib/get-owner";
5 |
6 | export default Component.extend({
7 | canEdit: false,
8 |
9 | init() {
10 | this._super(...arguments);
11 | const currentUser = this.get("currentUser");
12 | const everyoneCanEdit =
13 | this.get("siteSettings.canned_replies_everyone_enabled") &&
14 | this.get("siteSettings.canned_replies_everyone_can_edit");
15 | const currentUserCanEdit =
16 | this.get("siteSettings.canned_replies_enabled") &&
17 | currentUser &&
18 | currentUser.can_edit_canned_replies;
19 | const canEdit = currentUserCanEdit ? currentUserCanEdit : everyoneCanEdit;
20 | this.set("canEdit", canEdit);
21 | },
22 |
23 | actions: {
24 | apply() {
25 | const composer = getOwner(this).lookup("controller:composer");
26 |
27 | applyReply(
28 | this.get("reply.id"),
29 | this.get("reply.title"),
30 | this.get("reply.content"),
31 | composer.model
32 | );
33 |
34 | this.appEvents.trigger("canned-replies:hide");
35 | },
36 |
37 | editReply() {
38 | const composer = getOwner(this).lookup("controller:composer");
39 |
40 | getOwner(this).lookup("service:modal").close();
41 | showModal("edit-reply").setProperties({
42 | composerModel: composer.composerModel,
43 | replyId: this.get("reply.id"),
44 | replyTitle: this.get("reply.title"),
45 | replyContent: this.get("reply.content"),
46 | });
47 | },
48 | },
49 | });
50 |
--------------------------------------------------------------------------------
/assets/javascripts/lib/apply-reply.js:
--------------------------------------------------------------------------------
1 | import { ajax } from "discourse/lib/ajax";
2 | import { popupAjaxError } from "discourse/lib/ajax-error";
3 |
4 | export default function (replyId, replyTitle, replyContent, model) {
5 | // Replace variables with values.
6 | if (model) {
7 | const vars = {
8 | my_username: model.get("user.username"),
9 | my_name: model.get("user.name"),
10 | original_poster_username: model.get("topic.details.created_by.username"),
11 | original_poster_name: model.get("topic.details.created_by.name"),
12 | reply_to_username: model.get("post.username"),
13 | reply_to_name: model.get("post.name"),
14 | last_poster_username: model.get("topic.last_poster_username"),
15 | reply_to_or_last_poster_username:
16 | model.get("post.username") || model.get("topic.last_poster_username"),
17 | };
18 |
19 | for (let key in vars) {
20 | if (vars[key]) {
21 | replyTitle = replyTitle.replace(
22 | new RegExp(`%{${key}(,fallback:.[^}]*)?}`, "g"),
23 | vars[key]
24 | );
25 | replyContent = replyContent.replace(
26 | new RegExp(`%{${key}(,fallback:.[^}]*)?}`, "g"),
27 | vars[key]
28 | );
29 | } else {
30 | replyTitle = replyTitle.replace(
31 | new RegExp(`%{${key},fallback:(.[^}]*)}`, "g"),
32 | "$1"
33 | );
34 | replyTitle = replyTitle.replace(new RegExp(`%{${key}}`, "g"), "");
35 | replyContent = replyContent.replace(
36 | new RegExp(`%{${key},fallback:(.[^}]*)}`, "g"),
37 | "$1"
38 | );
39 | replyContent = replyContent.replace(new RegExp(`%{${key}}`, "g"), "");
40 | }
41 | }
42 | }
43 |
44 | // Finally insert canned reply.
45 | model.appEvents.trigger("composer:insert-block", replyContent);
46 | if (model && !model.title) {
47 | model.set("title", replyTitle);
48 | }
49 |
50 | ajax(`/canned_replies/${replyId}/use`, {
51 | type: "PATCH",
52 | }).catch(popupAjaxError);
53 | }
54 |
--------------------------------------------------------------------------------
/plugin.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # name: discourse-canned-replies
4 | # about: Add canned replies through the composer
5 | # version: 1.2
6 | # authors: Jay Pfaffman and André Pereira
7 | # url: https://github.com/discourse/discourse-canned-replies
8 | # transpile_js: true
9 |
10 | enabled_site_setting :canned_replies_enabled
11 |
12 | register_asset "stylesheets/canned-replies.scss"
13 |
14 | register_svg_icon "far-clipboard" if respond_to?(:register_svg_icon)
15 |
16 | module ::DiscourseCannedReplies
17 | PLUGIN_NAME = "discourse-canned-replies"
18 | STORE_NAME = "replies"
19 | end
20 |
21 | require_relative "lib/discourse_canned_replies/engine"
22 |
23 | after_initialize do
24 | AdminDashboardData.add_problem_check do
25 | "The discourse-canned-replies plugin is no longer supported. Please migrate to the new discourse-templates plugin and uninstall discourse-canned-replies."
26 | end
27 |
28 | require_relative "app/jobs/onceoff/rename_canned_replies.rb"
29 |
30 | add_to_class(:user, :can_edit_canned_replies?) do
31 | return true if staff?
32 | return true if SiteSetting.canned_replies_everyone_can_edit
33 | group_list = SiteSetting.canned_replies_groups.split("|").map(&:downcase)
34 | groups.any? { |group| group_list.include?(group.name.downcase) }
35 | end
36 |
37 | add_to_class(:user, :can_use_canned_replies?) do
38 | return true if staff?
39 | return true if SiteSetting.canned_replies_everyone_enabled
40 | group_list = SiteSetting.canned_replies_groups.split("|").map(&:downcase)
41 | groups.any? { |group| group_list.include?(group.name.downcase) }
42 | end
43 |
44 | add_to_class(:guardian, :can_edit_canned_replies?) { user && user.can_edit_canned_replies? }
45 |
46 | add_to_class(:guardian, :can_use_canned_replies?) { user && user.can_use_canned_replies? }
47 |
48 | add_to_serializer(:current_user, :can_use_canned_replies) { object.can_use_canned_replies? }
49 |
50 | add_to_serializer(:current_user, :can_edit_canned_replies) { object.can_edit_canned_replies? }
51 | end
52 |
--------------------------------------------------------------------------------
/app/controllers/discourse_canned_replies/canned_replies_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ::DiscourseCannedReplies
4 | class CannedRepliesController < ::ApplicationController
5 | requires_plugin DiscourseCannedReplies::PLUGIN_NAME
6 | before_action :ensure_logged_in
7 | before_action :ensure_canned_replies_enabled
8 | skip_before_action :check_xhr
9 |
10 | def index
11 | user_id = current_user.id
12 | replies = DiscourseCannedReplies::Reply.all(user_id)
13 |
14 | render json: { replies: replies }
15 | end
16 |
17 | def create
18 | guardian.ensure_can_edit_canned_replies!
19 |
20 | title = params.require(:title)
21 | content = params.require(:content)
22 | user_id = current_user.id
23 |
24 | record = DiscourseCannedReplies::Reply.add(user_id, title, content)
25 | render json: record
26 | end
27 |
28 | def update
29 | guardian.ensure_can_edit_canned_replies!
30 |
31 | reply_id = params.require(:id)
32 | title = params.require(:title)
33 | content = params.require(:content)
34 | user_id = current_user.id
35 |
36 | record = DiscourseCannedReplies::Reply.edit(user_id, reply_id, title, content)
37 | render json: record
38 | end
39 |
40 | def destroy
41 | guardian.ensure_can_edit_canned_replies!
42 |
43 | reply_id = params.require(:id)
44 | user_id = current_user.id
45 | record = DiscourseCannedReplies::Reply.remove(user_id, reply_id)
46 |
47 | render json: record
48 | end
49 |
50 | def reply
51 | reply_id = params.require(:id)
52 | user_id = current_user.id
53 |
54 | record = DiscourseCannedReplies::Reply.get_reply(user_id, reply_id)
55 | render json: record
56 | end
57 |
58 | def use
59 | reply_id = params.require(:id)
60 | user_id = current_user.id
61 | record = DiscourseCannedReplies::Reply.use(user_id, reply_id)
62 |
63 | render json: record
64 | end
65 |
66 | private
67 |
68 | def ensure_canned_replies_enabled
69 | guardian.ensure_can_use_canned_replies!
70 | end
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/assets/javascripts/controllers/edit-reply.js:
--------------------------------------------------------------------------------
1 | import Controller from "@ember/controller";
2 | import ModalFunctionality from "discourse/mixins/modal-functionality";
3 | import showModal from "discourse/lib/show-modal";
4 | import { ajax } from "discourse/lib/ajax";
5 | import discourseComputed from "discourse-common/utils/decorators";
6 | import { popupAjaxError } from "discourse/lib/ajax-error";
7 | import I18n from "I18n";
8 | import { inject as service } from "@ember/service";
9 |
10 | export default Controller.extend(ModalFunctionality, {
11 | dialog: service(),
12 | replyTitle: "",
13 | replyContent: "",
14 | replyId: "",
15 | saving: null,
16 |
17 | onShow() {
18 | this.set("saving", null);
19 | },
20 |
21 | @discourseComputed("saving")
22 | savingLabel(saving) {
23 | return saving === null ? "save" : saving ? "saving" : "saved";
24 | },
25 |
26 | @discourseComputed("replyTitle", "replyContent", "saving")
27 | disableSaveButton(replyTitle, replyContent, saving) {
28 | return saving || replyTitle === "" || replyContent === "";
29 | },
30 |
31 | actions: {
32 | save() {
33 | this.set("saving", true);
34 |
35 | ajax(`/canned_replies/${this.replyId}`, {
36 | type: "PATCH",
37 | data: {
38 | title: this.replyTitle,
39 | content: this.replyContent,
40 | },
41 | })
42 | .catch(popupAjaxError)
43 | .finally(() => {
44 | this.set("saving", false);
45 | this.appEvents.trigger("canned-replies:show");
46 | });
47 | },
48 |
49 | remove() {
50 | this.dialog.deleteConfirm({
51 | message: I18n.t("canned_replies.edit.remove_confirm"),
52 | didConfirm: () => {
53 | return ajax(`/canned_replies/${this.replyId}`, {
54 | type: "DELETE",
55 | })
56 | .then(() => {
57 | this.send("closeModal");
58 | if (this.site.mobileView) {
59 | showModal("canned-replies");
60 | } else {
61 | this.appEvents.trigger("canned-replies:show");
62 | }
63 | })
64 | .catch(popupAjaxError);
65 | },
66 | });
67 | },
68 |
69 | cancel() {
70 | this.send("closeModal");
71 | if (this.site.mobileView) {
72 | showModal("canned-replies");
73 | } else {
74 | this.appEvents.trigger("canned-replies:show");
75 | }
76 | },
77 | },
78 | });
79 |
--------------------------------------------------------------------------------
/assets/stylesheets/canned-replies.scss:
--------------------------------------------------------------------------------
1 | .canned-replies-modal {
2 | max-width: 1080px;
3 | .canned-replies-sort-usage,
4 | .canned-replies-sort-alpha {
5 | display: inline-block;
6 |
7 | .btn {
8 | float: none;
9 | }
10 | }
11 |
12 | .content {
13 | margin-top: 10px;
14 | }
15 |
16 | .btn.link,
17 | .btn.local-dates {
18 | display: none;
19 | }
20 | }
21 |
22 | .canned-replies-edit-bar {
23 | display: flex;
24 | border-bottom: 1px solid var(--primary-low);
25 | margin-bottom: 1em;
26 | padding-bottom: 1em;
27 | input {
28 | margin: 0 0 0 1em;
29 | }
30 | .close {
31 | margin-left: auto;
32 | }
33 | }
34 |
35 | .canned-replies-footer {
36 | align-items: stretch;
37 | margin-top: 3px;
38 | }
39 |
40 | .canned-reply-title {
41 | display: flex;
42 | align-items: center;
43 | button {
44 | margin-left: 0.5em;
45 | }
46 |
47 | &:hover {
48 | background-color: var(--highlight-medium);
49 | cursor: pointer;
50 | }
51 | }
52 |
53 | .canned-reply-title-text {
54 | font-weight: bold;
55 | margin-left: 0.5em;
56 | @include ellipsis;
57 | }
58 |
59 | #reply-control {
60 | .canned-replies {
61 | height: 100%;
62 | width: 100%;
63 | }
64 |
65 | .canned-reply {
66 | padding-bottom: 1em;
67 |
68 | .canned-reply-title {
69 | display: flex;
70 | align-items: center;
71 |
72 | .canned-reply-title-text {
73 | max-width: 75%;
74 | }
75 |
76 | .actions {
77 | margin-left: auto;
78 | }
79 | }
80 | }
81 | }
82 |
83 | .canned-replies-form-title-input {
84 | width: 100%;
85 | }
86 |
87 | .canned-replies-content {
88 | background: var(--primary-very-low);
89 | padding: 0.75em;
90 | margin-top: 0.5em;
91 | box-sizing: border-box;
92 | p:first-of-type {
93 | margin-top: 0;
94 | }
95 | p:last-of-type {
96 | margin-bottom: 0;
97 | }
98 | }
99 |
100 | .desktop-view {
101 | .canned-replies-edit-back {
102 | // This button is only needed on mobile because it's a modal within a modal
103 | display: none;
104 | }
105 | }
106 |
107 | .mobile-view .canned-replies-modal {
108 | min-height: 50vh;
109 | box-sizing: border-box;
110 | width: 100vw;
111 | overflow-x: hidden;
112 | display: flex;
113 | flex-direction: column;
114 |
115 | > div,
116 | form,
117 | .canned-replies-form-content-wrapper {
118 | // for making the edit input full-height
119 | flex: 1 1 100%;
120 | display: flex;
121 | flex-direction: column;
122 | }
123 |
124 | .content {
125 | max-height: 50vh;
126 | }
127 |
128 | .select-kit {
129 | width: 100%;
130 | }
131 | .d-editor-preview-wrapper {
132 | display: none;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/assets/javascripts/controllers/canned-replies.js:
--------------------------------------------------------------------------------
1 | import Controller from "@ember/controller";
2 | import ModalFunctionality from "discourse/mixins/modal-functionality";
3 | import showModal from "discourse/lib/show-modal";
4 | import { ajax } from "discourse/lib/ajax";
5 | import { observes } from "discourse-common/utils/decorators";
6 | import { popupAjaxError } from "discourse/lib/ajax-error";
7 | import applyReply from "discourse/plugins/discourse-canned-replies/lib/apply-reply";
8 |
9 | export default Controller.extend(ModalFunctionality, {
10 | selectedReply: null,
11 | selectedReplyId: "",
12 | loadingReplies: true,
13 | canEdit: false,
14 |
15 | init() {
16 | this._super(...arguments);
17 |
18 | const currentUser = this.get("currentUser");
19 | const everyoneCanEdit =
20 | this.siteSettings.canned_replies_everyone_enabled &&
21 | this.siteSettings.canned_replies_everyone_can_edit;
22 | const currentUserCanEdit =
23 | this.siteSettings.canned_replies_enabled &&
24 | currentUser &&
25 | currentUser.can_edit_canned_replies;
26 | const canEdit = currentUserCanEdit ? currentUserCanEdit : everyoneCanEdit;
27 | this.set("canEdit", canEdit);
28 |
29 | this.replies = [];
30 | },
31 |
32 | @observes("selectedReplyId")
33 | _updateSelection() {
34 | this.selectionChange();
35 | },
36 |
37 | onShow() {
38 | ajax("/canned_replies")
39 | .then((results) => {
40 | this.set("replies", results.replies);
41 | // trigger update of the selected reply
42 | this.selectionChange();
43 | })
44 | .catch(popupAjaxError)
45 | .finally(() => this.set("loadingReplies", false));
46 | },
47 |
48 | selectionChange() {
49 | const localSelectedReplyId = this.get("selectedReplyId");
50 |
51 | let localSelectedReply = "";
52 | this.get("replies").forEach((entry) => {
53 | if (entry.id === localSelectedReplyId) {
54 | localSelectedReply = entry;
55 | return;
56 | }
57 | });
58 |
59 | this.set("selectedReply", localSelectedReply);
60 | },
61 |
62 | actions: {
63 | apply() {
64 | applyReply(
65 | this.get("selectedReplyId"),
66 | this.selectedReply.title,
67 | this.selectedReply.content,
68 | this.composerModel
69 | );
70 |
71 | this.send("closeModal");
72 | },
73 |
74 | newReply() {
75 | this.send("closeModal");
76 |
77 | showModal("new-reply").set("newContent", this.composerModel.reply);
78 | },
79 |
80 | editReply() {
81 | this.send("closeModal");
82 |
83 | showModal("edit-reply").setProperties({
84 | replyId: this.selectedReplyId,
85 | replyTitle: this.get("selectedReply.title"),
86 | replyContent: this.get("selectedReply.content"),
87 | });
88 | },
89 | },
90 | });
91 |
--------------------------------------------------------------------------------
/app/models/discourse_canned_replies/reply.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ::DiscourseCannedReplies
4 | class Reply
5 | def self.add(user_id, title, content)
6 | id = SecureRandom.hex(16)
7 | record = { id: id, title: title, content: content }
8 |
9 | replies =
10 | PluginStore.get(DiscourseCannedReplies::PLUGIN_NAME, DiscourseCannedReplies::STORE_NAME) ||
11 | {}
12 |
13 | replies[id] = record
14 | PluginStore.set(
15 | DiscourseCannedReplies::PLUGIN_NAME,
16 | DiscourseCannedReplies::STORE_NAME,
17 | replies,
18 | )
19 |
20 | record
21 | end
22 |
23 | def self.edit(user_id, reply_id, title, content)
24 | record = { id: reply_id, title: title, content: content }
25 | remove(user_id, reply_id)
26 |
27 | replies =
28 | PluginStore.get(DiscourseCannedReplies::PLUGIN_NAME, DiscourseCannedReplies::STORE_NAME) ||
29 | {}
30 |
31 | replies[reply_id] = record
32 | PluginStore.set(
33 | DiscourseCannedReplies::PLUGIN_NAME,
34 | DiscourseCannedReplies::STORE_NAME,
35 | replies,
36 | )
37 |
38 | record
39 | end
40 |
41 | def self.all(user_id)
42 | replies =
43 | PluginStore.get(DiscourseCannedReplies::PLUGIN_NAME, DiscourseCannedReplies::STORE_NAME)
44 |
45 | if replies.blank?
46 | add_default_reply
47 | replies =
48 | PluginStore.get(DiscourseCannedReplies::PLUGIN_NAME, DiscourseCannedReplies::STORE_NAME)
49 | end
50 |
51 | return [] if replies.blank?
52 | replies.values.sort_by { |reply| reply["title"] || "" }
53 | end
54 |
55 | def self.get_reply(user_id, reply_id)
56 | replies = all(user_id)
57 |
58 | replies.detect { |reply| reply["id"] == reply_id }
59 | end
60 |
61 | def self.remove(user_id, reply_id)
62 | replies =
63 | PluginStore.get(DiscourseCannedReplies::PLUGIN_NAME, DiscourseCannedReplies::STORE_NAME)
64 | replies.delete(reply_id)
65 | PluginStore.set(
66 | DiscourseCannedReplies::PLUGIN_NAME,
67 | DiscourseCannedReplies::STORE_NAME,
68 | replies,
69 | )
70 | end
71 |
72 | def self.use(user_id, reply_id)
73 | replies =
74 | PluginStore.get(DiscourseCannedReplies::PLUGIN_NAME, DiscourseCannedReplies::STORE_NAME)
75 | reply = replies[reply_id]
76 | reply["usages"] ||= 0
77 | reply["usages"] += 1
78 | replies[reply_id] = reply
79 | PluginStore.set(
80 | DiscourseCannedReplies::PLUGIN_NAME,
81 | DiscourseCannedReplies::STORE_NAME,
82 | replies,
83 | )
84 | end
85 |
86 | def self.add_default_reply()
87 | add(1, I18n.t("replies.default_reply.title"), I18n.t("replies.default_reply.body"))
88 | end
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/assets/javascripts/connectors/editor-preview/canned-replies.js:
--------------------------------------------------------------------------------
1 | import showModal from "discourse/lib/show-modal";
2 | import { ajax } from "discourse/lib/ajax";
3 | import { popupAjaxError } from "discourse/lib/ajax-error";
4 | import { getOwner } from "discourse-common/lib/get-owner";
5 | import { schedule } from "@ember/runloop";
6 |
7 | export default {
8 | setupComponent(args, component) {
9 | const currentUser = this.get("currentUser");
10 | const everyoneCanEdit =
11 | this.get("siteSettings.canned_replies_everyone_enabled") &&
12 | this.get("siteSettings.canned_replies_everyone_can_edit");
13 | const currentUserCanEdit =
14 | this.get("siteSettings.canned_replies_enabled") &&
15 | currentUser &&
16 | currentUser.can_edit_canned_replies;
17 | const canEdit = currentUserCanEdit ? currentUserCanEdit : everyoneCanEdit;
18 | this.set("canEdit", canEdit);
19 |
20 | component.setProperties({
21 | cannedVisible: false,
22 | loadingReplies: false,
23 | replies: [],
24 | filteredReplies: [],
25 | });
26 |
27 | if (!component.appEvents.has("canned-replies:show")) {
28 | this.showCanned = () => component.send("show");
29 | component.appEvents.on("canned-replies:show", this, this.showCanned);
30 | }
31 |
32 | if (!component.appEvents.has("canned-replies:hide")) {
33 | this.hideCanned = () => component.send("hide");
34 | component.appEvents.on("canned-replies:hide", this, this.hideCanned);
35 | }
36 |
37 | component.addObserver("listFilter", function () {
38 | const filterTitle = component.listFilter.toLowerCase();
39 | const filtered = component.replies
40 | .map((reply) => {
41 | /* Give a relevant score to each reply. */
42 | reply.score = 0;
43 | if (reply.title.toLowerCase().indexOf(filterTitle) !== -1) {
44 | reply.score += 2;
45 | } else if (reply.content.toLowerCase().indexOf(filterTitle) !== -1) {
46 | reply.score += 1;
47 | }
48 | return reply;
49 | })
50 | .filter((reply) => reply.score !== 0) // Filter irrelevant replies.
51 | .sort((a, b) => {
52 | /* Sort replies by relevance and title. */
53 | if (a.score !== b.score) {
54 | return a.score > b.score ? -1 : 1; /* descending */
55 | } else if (a.title !== b.title) {
56 | return a.title < b.title ? -1 : 1; /* ascending */
57 | }
58 | return 0;
59 | });
60 | component.set("filteredReplies", filtered);
61 | });
62 | },
63 |
64 | teardownComponent(component) {
65 | if (component.appEvents.has("canned-replies:show") && this.showCanned) {
66 | component.appEvents.off("canned-replies:show", this, this.showCanned);
67 | component.appEvents.off("canned-replies:hide", this, this.hideCanned);
68 | }
69 | },
70 |
71 | actions: {
72 | show() {
73 | $("#reply-control .d-editor-preview-wrapper > .d-editor-preview").hide();
74 | this.setProperties({ cannedVisible: true, loadingReplies: true });
75 |
76 | ajax("/canned_replies")
77 | .then((results) => {
78 | this.setProperties({
79 | replies: results.replies,
80 | filteredReplies: results.replies,
81 | });
82 | })
83 | .catch(popupAjaxError)
84 | .finally(() => {
85 | this.set("loadingReplies", false);
86 |
87 | if (this.canEdit) {
88 | schedule("afterRender", () =>
89 | document.querySelector(".canned-replies-filter").focus()
90 | );
91 | }
92 | });
93 | },
94 |
95 | hide() {
96 | $(".d-editor-preview-wrapper > .d-editor-preview").show();
97 | this.set("cannedVisible", false);
98 | },
99 |
100 | newReply() {
101 | const composer = getOwner(this).lookup("controller:composer");
102 | getOwner(this).lookup("service:modal").close();
103 |
104 | showModal("new-reply").set("newContent", composer.model.reply);
105 | },
106 | },
107 | };
108 |
--------------------------------------------------------------------------------
/test/javascripts/acceptance/canned-replies-test.js:
--------------------------------------------------------------------------------
1 | import I18n from "I18n";
2 | import selectKit from "discourse/tests/helpers/select-kit-helper";
3 | import { acceptance } from "discourse/tests/helpers/qunit-helpers";
4 | import { click, fillIn, visit } from "@ember/test-helpers";
5 | import { test } from "qunit";
6 | import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
7 |
8 | acceptance("Canned Replies", function (needs) {
9 | needs.user({
10 | can_use_canned_replies: true,
11 | can_edit_canned_replies: true,
12 | });
13 | needs.settings({
14 | canned_replies_enabled: true,
15 | });
16 | needs.pretender((server, helper) => {
17 | server.patch("/canned_replies/cd6680d7a04caaac1274e6f37429458c/use", () => {
18 | return helper.response({});
19 | });
20 | server.patch("/canned_replies/cd6680d7a04caaac1274e6f37429458c", () => {
21 | return helper.response({});
22 | });
23 | server.patch("/canned_replies/ce5fc200ab90dd0d5ac597ca9bb4708b", () => {
24 | return helper.response({});
25 | });
26 | server.patch("/canned_replies/ce5fc200ab90dd0d5ac597ca9bb4708b/use", () => {
27 | return helper.response({});
28 | });
29 | server.patch("/canned_replies/04697870e02acfef3c2130dab92fe6d8", () => {
30 | return helper.response({});
31 | });
32 | server.patch("/canned_replies/04697870e02acfef3c2130dab92fe6d8/use", () => {
33 | return helper.response({});
34 | });
35 | server.post("/canned_replies", () => {
36 | return helper.response({});
37 | });
38 | server.get("/canned_replies", () => {
39 | return helper.response({
40 | replies: [
41 | {
42 | id: "ce5fc200ab90dd0d5ac597ca9bb4708b",
43 | title: "Small markdown example",
44 | excerpt: "markdown",
45 | content: "**markdown**",
46 | },
47 | {
48 | id: "cd6680d7a04caaac1274e6f37429458c",
49 | title: "My first canned reply",
50 | excerpt: "This is an example canned reply",
51 | content:
52 | "This is an example canned reply.\nYou can user **markdown** to style your replies. Click the **new** button to create new replies or the **edit** button to edit or remove an existing canned reply.\n\n*This canned reply will be added when the replies list is empty.*",
53 | },
54 | {
55 | id: "1a1987620aa49135344abe65eb43302d",
56 | title: "Testing",
57 | excerpt: "",
58 | content: "",
59 | usages: 1,
60 | },
61 | {
62 | id: "5317b7fd066d3d4c15acde92d70f0377",
63 | title: "This is a test",
64 | excerpt: "Testing",
65 | content: "Testing testin **123**",
66 | usages: 1,
67 | },
68 | {
69 | id: "04697870e02acfef3c2130dab92fe6d8",
70 | title: "Using variables",
71 | excerpt:
72 | "Hi %{reply_to_username,fallback:there}, regards %{my_username}.",
73 | content:
74 | "Hi %{reply_to_username,fallback:there}, regards %{my_username}.",
75 | },
76 | ],
77 | });
78 | });
79 | });
80 | needs.hooks.beforeEach(() => clearPopupMenuOptionsCallback());
81 |
82 | test("Inserting canned replies", async (assert) => {
83 | await visit("/");
84 |
85 | await click("#create-topic");
86 | const categoryChooser = selectKit(".category-chooser");
87 | await categoryChooser.expand();
88 | await categoryChooser.selectRowByValue(2);
89 | await fillIn(".d-editor-input", "beforeafter");
90 |
91 | const editorInput = $(".d-editor-input")[0];
92 | editorInput.selectionStart = editorInput.selectionEnd = "before".length;
93 |
94 | const popUpMenu = await selectKit(".toolbar-popup-menu-options");
95 | await popUpMenu.expand();
96 | await popUpMenu.selectRowByValue("showCannedRepliesButton");
97 | await click(".canned-reply-title");
98 |
99 | assert.ok(
100 | find(".canned-replies-content")
101 | .html()
102 | .indexOf("markdown") !== -1,
103 | "it should display the right cooked content"
104 | );
105 |
106 | await click(
107 | "#canned-reply-ce5fc200ab90dd0d5ac597ca9bb4708b .canned-replies-apply"
108 | );
109 |
110 | assert.equal(
111 | find(".d-editor-input").val(),
112 | "before\n\n**markdown**\n\nafter",
113 | "it should contain the right selected output"
114 | );
115 | });
116 |
117 | test("Editing a canned reply", async (assert) => {
118 | const popUpMenu = await selectKit(".toolbar-popup-menu-options");
119 |
120 | await visit("/");
121 |
122 | await click("#create-topic");
123 | await popUpMenu.expand();
124 | await popUpMenu.selectRowByValue("showCannedRepliesButton");
125 |
126 | await click(".canned-replies-edit");
127 |
128 | await fillIn(".canned-replies-form-title-input", "Some title");
129 | await fillIn(".canned-replies-form-content-input textarea", "Some content");
130 |
131 | await click(".edit-reply-save-btn");
132 |
133 | assert.equal(
134 | find(".canned-replies-footer .edit-reply-save-btn").text().trim(),
135 | I18n.t("saved")
136 | );
137 | });
138 |
139 | test("Creating a new canned reply", async (assert) => {
140 | const popUpMenu = await selectKit(".toolbar-popup-menu-options");
141 |
142 | await visit("/");
143 |
144 | await click("#create-topic");
145 | await popUpMenu.expand();
146 | await popUpMenu.selectRowByValue("showCannedRepliesButton");
147 |
148 | await click(".canned-replies-new");
149 |
150 | await fillIn(".canned-replies-form-title-input", "");
151 | await fillIn(".canned-replies-form-content-input textarea", "");
152 |
153 | assert.equal(
154 | find(".btn.new-reply-save-btn[disabled]").length,
155 | 1,
156 | "save button should be disabled by default"
157 | );
158 |
159 | await fillIn(".canned-replies-form-title-input", "Some title");
160 |
161 | assert.equal(
162 | find(".btn.new-reply-save-btn[disabled]").length,
163 | 1,
164 | "save button should be disabled when content is blank"
165 | );
166 |
167 | await fillIn(".canned-replies-form-content-input textarea", "Some content");
168 | await click(".new-reply-save-btn");
169 | });
170 |
171 | test("Replacing variables", async (assert) => {
172 | const popUpMenu = await selectKit(".toolbar-popup-menu-options");
173 |
174 | await visit("/");
175 |
176 | await click("#create-topic");
177 | const categoryChooser = selectKit(".category-chooser");
178 | await categoryChooser.expand();
179 | await categoryChooser.selectRowByValue(2);
180 | await popUpMenu.expand();
181 | await popUpMenu.selectRowByValue("showCannedRepliesButton");
182 |
183 | await click(
184 | "#canned-reply-04697870e02acfef3c2130dab92fe6d8 .canned-replies-apply"
185 | );
186 |
187 | assert.equal(
188 | find(".d-editor-input").val().trim(),
189 | "Hi there, regards eviltrout.",
190 | "it should replace variables"
191 | );
192 | });
193 |
194 | test("Reset modal content", async (assert) => {
195 | const popUpMenu = await selectKit(".toolbar-popup-menu-options");
196 |
197 | await visit("/");
198 |
199 | await click("#create-topic");
200 | await popUpMenu.expand();
201 | await popUpMenu.selectRowByValue("showCannedRepliesButton");
202 |
203 | await click(".canned-replies-new");
204 |
205 | await fillIn(".canned-replies-form-title-input", "Some title");
206 | await fillIn(".canned-replies-form-content-input textarea", "Some content");
207 |
208 | await click(".modal-close");
209 |
210 | await click(".canned-replies-new");
211 |
212 | assert.equal(
213 | find(".canned-replies-form-title-input").val(),
214 | "",
215 | "it should clear title"
216 | );
217 | assert.equal(
218 | find(".canned-replies-form-content-input textarea").val(),
219 | "",
220 | "it should clear content"
221 | );
222 | });
223 | });
224 |
--------------------------------------------------------------------------------
/spec/integration/canned_reply/canned_replies_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails_helper"
4 |
5 | RSpec.describe DiscourseCannedReplies::CannedRepliesController do
6 | let(:moderator) do
7 | user = Fabricate(:moderator)
8 | sign_in(user)
9 | user
10 | end
11 |
12 | let(:privileged_group) do
13 | group = Fabricate(:group, users: [privileged_user])
14 | group.add(privileged_user)
15 | group.save
16 | group
17 | end
18 |
19 | let(:privileged_user) do
20 | user = Fabricate(:user)
21 | sign_in(user)
22 | user
23 | end
24 |
25 | let(:user) do
26 | user = Fabricate(:user)
27 | sign_in(user)
28 | user
29 | end
30 |
31 | let(:canned_reply) { DiscourseCannedReplies::Reply.add(moderator, "some title", "some content") }
32 |
33 | describe "listing canned replies" do
34 | context "as a normal user" do
35 | it "should raise the right error" do
36 | user
37 |
38 | get "/canned_replies"
39 | expect(response.status).to eq(403)
40 | end
41 | end
42 |
43 | context "as a normal user with everyone enabled" do
44 | it "should not raise an error" do
45 | SiteSetting.canned_replies_everyone_enabled = true
46 | user
47 |
48 | get "/canned_replies"
49 | expect(response.status).to eq(200)
50 | end
51 | end
52 |
53 | let(:list_canned_replies) do
54 | post "/canned_replies", params: { title: "Reply test title", content: "Reply test content" }
55 |
56 | expect(response).to be_successful
57 |
58 | get "/canned_replies"
59 |
60 | expect(response).to be_successful
61 |
62 | replies = JSON.parse(response.body)["replies"]
63 | reply = replies.first
64 |
65 | expect(replies.length).to eq(1)
66 | expect(reply["title"]).to eq "Reply test title"
67 | expect(reply["content"]).to eq "Reply test content"
68 | end
69 |
70 | context "as a staff" do
71 | it "should list all replies correctly" do
72 | moderator
73 |
74 | list_canned_replies
75 | end
76 | end
77 |
78 | context "as a privileged user" do
79 | before do
80 | privileged_user
81 | privileged_group
82 | SiteSetting.canned_replies_groups = privileged_group.name
83 | end
84 |
85 | it "should list all replies correctly" do
86 | list_canned_replies
87 | end
88 | end
89 | end
90 |
91 | describe "removing canned replies" do
92 | context "as a normal user" do
93 | it "should raise the right error" do
94 | user
95 |
96 | delete "/canned_replies/someid"
97 | expect(response.status).to eq(403)
98 | end
99 |
100 | it "should raise the right error with everyone enabled" do
101 | SiteSetting.canned_replies_everyone_enabled = true
102 | user
103 |
104 | delete "/canned_replies/someid"
105 | expect(response.status).to eq(403)
106 | end
107 | end
108 |
109 | let(:remove_canned_replies) do
110 | post "/canned_replies", params: { title: "Reply test title", content: "Reply test content" }
111 |
112 | expect(response).to be_successful
113 |
114 | id, _new_reply =
115 | PluginStore.get(
116 | DiscourseCannedReplies::PLUGIN_NAME,
117 | DiscourseCannedReplies::STORE_NAME,
118 | ).first
119 |
120 | delete "/canned_replies/#{id}"
121 |
122 | expect(response).to be_successful
123 | expect(
124 | PluginStore.get(DiscourseCannedReplies::PLUGIN_NAME, DiscourseCannedReplies::STORE_NAME),
125 | ).to eq({})
126 | end
127 |
128 | context "as a staff" do
129 | it "should be able to remove reply" do
130 | moderator
131 |
132 | remove_canned_replies
133 | end
134 | end
135 | context "as a privileged user" do
136 | before do
137 | privileged_user
138 | privileged_group
139 | SiteSetting.canned_replies_groups = privileged_group.name
140 | end
141 |
142 | it "should be able to remove reply" do
143 | remove_canned_replies
144 | end
145 | end
146 |
147 | context "as a regular user with everyone can edit enabled" do
148 | it "should be able to remove reply" do
149 | SiteSetting.canned_replies_everyone_enabled = true
150 | SiteSetting.canned_replies_everyone_can_edit = true
151 | user
152 |
153 | remove_canned_replies
154 | end
155 | end
156 | end
157 |
158 | describe "editing a canned reply" do
159 | context "as a normal user" do
160 | it "should raise the right error" do
161 | user
162 |
163 | put "/canned_replies/someid"
164 | expect(response.status).to eq(403)
165 | end
166 | it "should raise the right error with everyone enabled" do
167 | SiteSetting.canned_replies_everyone_enabled = true
168 | user
169 |
170 | put "/canned_replies/someid"
171 | expect(response.status).to eq(403)
172 | end
173 | end
174 |
175 | let(:edit_canned_reply) do
176 | post "/canned_replies", params: { title: "Reply test title", content: "Reply test content" }
177 |
178 | expect(response).to be_successful
179 |
180 | id, _new_reply =
181 | PluginStore.get(
182 | DiscourseCannedReplies::PLUGIN_NAME,
183 | DiscourseCannedReplies::STORE_NAME,
184 | ).first
185 |
186 | patch "/canned_replies/#{id}", params: { title: "new title", content: "new content" }
187 |
188 | expect(response).to be_successful
189 |
190 | id, reply =
191 | PluginStore.get(
192 | DiscourseCannedReplies::PLUGIN_NAME,
193 | DiscourseCannedReplies::STORE_NAME,
194 | ).first
195 |
196 | expect(reply["title"]).to eq("new title")
197 | expect(reply["content"]).to eq("new content")
198 | end
199 |
200 | context "as a normal user" do
201 | before do
202 | SiteSetting.canned_replies_everyone_enabled = true
203 |
204 | canned_reply
205 | user
206 | end
207 |
208 | it "should not be able to edit a reply" do
209 | patch "/canned_replies/#{canned_reply[:id]}",
210 | params: {
211 | title: "new title",
212 | content: "new content",
213 | }
214 |
215 | expect(response.status).to eq(403)
216 | end
217 |
218 | it "should be able to edit a reply when SiteSetting is enabled" do
219 | SiteSetting.canned_replies_everyone_can_edit = true
220 |
221 | patch "/canned_replies/#{canned_reply[:id]}",
222 | params: {
223 | title: "new title",
224 | content: "new content",
225 | }
226 |
227 | expect(response).to be_successful
228 |
229 | id, reply =
230 | PluginStore.get(
231 | DiscourseCannedReplies::PLUGIN_NAME,
232 | DiscourseCannedReplies::STORE_NAME,
233 | ).first
234 |
235 | expect(reply["title"]).to eq("new title")
236 | expect(reply["content"]).to eq("new content")
237 | end
238 | end
239 |
240 | context "as a staff" do
241 | it "should be able to edit a reply" do
242 | moderator
243 |
244 | edit_canned_reply
245 | end
246 | end
247 | context "as a privileged user" do
248 | before do
249 | privileged_user
250 | privileged_group
251 | SiteSetting.canned_replies_groups = privileged_group.name
252 | end
253 |
254 | it "should be able to edit a reply" do
255 | edit_canned_reply
256 | end
257 | end
258 | context "as a regular user with everyone can edit enabled" do
259 | it "should be able to edit a reply" do
260 | SiteSetting.canned_replies_everyone_enabled = true
261 | SiteSetting.canned_replies_everyone_can_edit = true
262 | user
263 |
264 | edit_canned_reply
265 | end
266 | end
267 | end
268 |
269 | describe "recording canned replies usages" do
270 | context "as a normal user" do
271 | it "should raise the right error" do
272 | canned_reply
273 | user
274 |
275 | patch "/canned_replies/#{canned_reply[:id]}/use"
276 | expect(response.status).to eq(403)
277 | end
278 |
279 | it "should be able to record a user with everyone enabled" do
280 | SiteSetting.canned_replies_everyone_enabled = true
281 | canned_reply
282 | user
283 |
284 | patch "/canned_replies/#{canned_reply[:id]}/use"
285 | expect(response).to be_successful
286 | _id, reply =
287 | PluginStore.get(
288 | DiscourseCannedReplies::PLUGIN_NAME,
289 | DiscourseCannedReplies::STORE_NAME,
290 | ).first
291 |
292 | expect(reply["usages"]).to eq(1)
293 | end
294 | end
295 |
296 | context "as a staff" do
297 | it "should be able to record a usage" do
298 | patch "/canned_replies/#{canned_reply[:id]}/use"
299 |
300 | expect(response).to be_successful
301 |
302 | _id, reply =
303 | PluginStore.get(
304 | DiscourseCannedReplies::PLUGIN_NAME,
305 | DiscourseCannedReplies::STORE_NAME,
306 | ).first
307 |
308 | expect(reply["usages"]).to eq(1)
309 | end
310 | end
311 | end
312 |
313 | describe "retrieving a canned reply" do
314 | context "as a normal user" do
315 | it "should raise the right error" do
316 | canned_reply
317 | user
318 |
319 | get "/canned_replies/#{canned_reply[:id]}/reply"
320 | expect(response.status).to eq(403)
321 | end
322 | it "should succeed with everyone enabled" do
323 | SiteSetting.canned_replies_everyone_enabled = true
324 | canned_reply
325 | user
326 |
327 | get "/canned_replies/#{canned_reply[:id]}/reply"
328 | expect(response).to be_successful
329 | end
330 | end
331 |
332 | context "as a staff" do
333 | it "should fetch the right canned reply" do
334 | get "/canned_replies/#{canned_reply[:id]}/reply"
335 |
336 | expect(response).to be_successful
337 |
338 | reply = JSON.parse(response.body)
339 |
340 | expect(reply["title"]).to eq(canned_reply[:title])
341 | expect(reply["content"]).to eq(canned_reply[:content])
342 | end
343 | end
344 | end
345 | end
346 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
--------------------------------------------------------------------------------