DEV: Chat service object initial implementation (#19814)

This is a combined work of Martin Brennan, Loïc Guitaut, and Joffrey Jaffeux.

---

This commit implements a base service object when working in chat. The documentation is available at https://discourse.github.io/discourse/chat/backend/Chat/Service.html

Generating documentation has been made as part of this commit with a bigger goal in mind of generally making it easier to dive into the chat project.

Working with services generally involves 3 parts:

- The service object itself, which is a series of steps where few of them are specialized (model, transaction, policy)

```ruby
class UpdateAge
  include Chat::Service::Base

  model :user, :fetch_user
  policy :can_see_user
  contract
  step :update_age

  class Contract
    attribute :age, :integer
  end

  def fetch_user(user_id:, **)
    User.find_by(id: user_id)
  end

  def can_see_user(guardian:, **)
    guardian.can_see_user(user)
  end

  def update_age(age:, **)
    user.update!(age: age)
  end
end
```

- The `with_service` controller helper, handling success and failure of the service within a service and making easy to return proper response to it from the controller

```ruby
def update
  with_service(UpdateAge) do
    on_success { render_serialized(result.user, BasicUserSerializer, root: "user") }
  end
end
```

- Rspec matchers and steps inspector, improving the dev experience while creating specs for a service

```ruby
RSpec.describe(UpdateAge) do
  subject(:result) do
    described_class.call(guardian: guardian, user_id: user.id, age: age)
  end

  fab!(:user) { Fabricate(:user) }
  fab!(:current_user) { Fabricate(:admin) }

  let(:guardian) { Guardian.new(current_user) }
  let(:age) { 1 }

   it { expect(user.reload.age).to eq(age) }
end
```

Note in case of unexpected failure in your spec, the output will give all the relevant information:

```
  1) UpdateAge when no channel_id is given is expected to fail to find a model named 'user'
     Failure/Error: it { is_expected.to fail_to_find_a_model(:user) }

       Expected model 'foo' (key: 'result.model.user') was not found in the result object.

       [1/4] [model] 'user' 
       [2/4] [policy] 'can_see_user'
       [3/4] [contract] 'default'
       [4/4] [step] 'update_age'

       /Users/joffreyjaffeux/Code/pr-discourse/plugins/chat/app/services/update_age.rb:32:in `fetch_user': missing keyword: :user_id (ArgumentError)
       	from /Users/joffreyjaffeux/Code/pr-discourse/plugins/chat/app/services/base.rb:202:in `instance_exec'
       	from /Users/joffreyjaffeux/Code/pr-discourse/plugins/chat/app/services/base.rb:202:in `call'
       	from /Users/joffreyjaffeux/Code/pr-discourse/plugins/chat/app/services/base.rb:219:in `call'
       	from /Users/joffreyjaffeux/Code/pr-discourse/plugins/chat/app/services/base.rb:417:in `block in run!'
       	from /Users/joffreyjaffeux/Code/pr-discourse/plugins/chat/app/services/base.rb:417:in `each'
       	from /Users/joffreyjaffeux/Code/pr-discourse/plugins/chat/app/services/base.rb:417:in `run!'
       	from /Users/joffreyjaffeux/Code/pr-discourse/plugins/chat/app/services/base.rb:411:in `run'
       	from <internal:kernel>:90:in `tap'
       	from /Users/joffreyjaffeux/Code/pr-discourse/plugins/chat/app/services/base.rb:302:in `call'
       	from /Users/joffreyjaffeux/Code/pr-discourse/plugins/chat/spec/services/update_age_spec.rb:15:in `block (3 levels) in <main>'
```
This commit is contained in:
Martin Brennan 2023-02-13 22:09:57 +10:00 committed by GitHub
parent 81a4d75f06
commit 60ad836313
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 14567 additions and 932 deletions

View File

@ -12,3 +12,4 @@ node_modules/
spec/
dist/
tmp/
documentation/

View File

@ -7,7 +7,7 @@ on:
- main
permissions:
contents: read
contents: write
jobs:
build:
@ -40,7 +40,6 @@ jobs:
gem install bundler --conservative -v $(awk '/BUNDLED WITH/ { getline; gsub(/ /,""); print $0 }' Gemfile.lock)
bundle config --local path vendor/bundle
bundle config --local deployment true
bundle config --local without development
bundle install --jobs 4
bundle clean
@ -60,17 +59,23 @@ jobs:
- name: Yarn install
run: yarn install
- name: Check Chat documentation
- name: Check documentation
run: |
LOAD_PLUGINS=1 bin/rake chat:doc
LOAD_PLUGINS=1 bin/rake documentation
if [ ! -z "$(git status --porcelain plugins/chat/docs/)" ]; then
echo "Chat documentation is not up to date. To resolve, run:"
echo " LOAD_PLUGINS=1 bin/rake chat:doc"
if [ ! -z "$(git status --porcelain documentation/)" ]; then
echo "Documentation is not up to date. To resolve, run:"
echo " LOAD_PLUGINS=1 bin/rake documentation"
echo
echo "Or manually apply the diff printed below:"
echo "---------------------------------------------"
git -c color.ui=always diff plugins/chat/docs/
git -c color.ui=always diff documentation/
exit 1
fi
timeout-minutes: 30
- name: Deploy documentation to github pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./documentation

14
.jsdoc
View File

@ -3,5 +3,19 @@
{
"source": {
"excludePattern": ""
},
"templates": {
"default": {
"includeDate": false
}
},
"opts": {
"template": "./node_modules/tidy-jsdoc",
"prism-theme": "prism-custom",
"encoding": "utf8",
"recurse": true
},
"metadata": {
"title": "Discourse"
}
}

View File

@ -3,6 +3,7 @@ plugins/**/assets/stylesheets/vendor/
plugins/**/assets/javascripts/vendor/
plugins/**/config/locales/**/*.yml
plugins/**/config/*.yml
documentation/
package.json
config/locales/**/*.yml
!config/locales/**/*.en*.yml

View File

@ -180,6 +180,7 @@ group :development do
gem "better_errors", platform: :mri, require: !!ENV["BETTER_ERRORS"]
gem "binding_of_caller"
gem "yaml-lint"
gem "yard"
end
if ENV["ALLOW_DEV_POPULATE"] == "1"

View File

@ -516,6 +516,8 @@ GEM
xpath (3.2.0)
nokogiri (~> 1.8)
yaml-lint (0.1.2)
yard (0.9.28)
webrick (~> 1.7.0)
zeitwerk (2.6.7)
PLATFORMS
@ -663,6 +665,7 @@ DEPENDENCIES
webrick
xorcist
yaml-lint
yard
BUNDLED WITH
2.4.4

View File

@ -0,0 +1,3 @@
<svg width="960" height="375" viewBox="0 0 960 375" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M960 163.361C960 76.4166 861.5 2.37168e-05 574 0C286.5 -2.37168e-05 0 76.4166 0 163.361C0 250.306 124 385 415.5 373.5C707 362 960 250.306 960 163.361Z" fill="#FBF5AF"/>
</svg>

After

Width:  |  Height:  |  Size: 325 B

View File

@ -0,0 +1,6 @@
<svg width="1867" height="475" viewBox="0 0 1867 475" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1865.6 220.428C1856.87 332.048 1771.2 390.61 1665.99 404.379C1563.03 417.858 1471.85 319.945 1480.58 208.325C1489.3 96.7051 1591.56 4.34115 1697.59 0.168844C1828.53 -4.98699 1874.33 108.807 1865.6 220.428Z" fill="#FBF5AF"/>
<path d="M1464.63 416.562C1464.23 416.562 1463.83 416.594 1463.42 416.659C1458.84 417.393 1454.7 421.929 1455.71 428.253C1456.72 434.575 1462.62 438.145 1467.25 437.407C1469.45 437.053 1471.1 435.907 1472.15 433.993C1473.3 431.918 1473.65 429.03 1473.14 425.867C1472.64 422.761 1471.34 420.134 1469.46 418.463C1468.05 417.209 1466.4 416.562 1464.63 416.562ZM1465.91 444.071C1457.94 444.071 1450.59 437.774 1449.23 429.29C1447.75 419.993 1453.52 411.598 1462.38 410.179C1466.57 409.514 1470.62 410.714 1473.82 413.561C1476.83 416.233 1478.89 420.237 1479.62 424.827C1480.37 429.495 1479.77 433.758 1477.9 437.154C1475.88 440.829 1472.47 443.217 1468.29 443.883C1467.49 444.011 1466.7 444.071 1465.91 444.071" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M344 323.966C344 353.644 324.906 366.211 293.64 366.211C262.374 366.211 223 321.874 223 292.194C223 262.514 272.175 254 303.441 254C334.707 254 344 294.286 344 323.966Z" fill="#0CA64E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M396.325 444.694C396.653 458.473 386.913 467.649 370.18 473.648C356.257 478.638 342.267 465.392 336.512 453.65C330.052 440.465 344.903 425.749 369.376 421.378C383.936 418.778 395.973 429.909 396.325 444.694Z" fill="#E84A51"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -0,0 +1,118 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Module: Chat
&mdash; Documentation by YARD 0.9.28
</title>
<link rel="stylesheet" href="css/style.css" type="text/css" />
<link rel="stylesheet" href="css/common.css" type="text/css" />
<script type="text/javascript">
pathId = "Chat";
relpath = '';
</script>
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="js/app.js"></script>
</head>
<body>
<div class="nav_wrap">
<iframe id="nav" src="class_list.html?1"></iframe>
<div id="resizer"></div>
</div>
<div id="main" tabindex="-1">
<div id="header">
<div id="menu">
<a href="_index.html">Index (C)</a> &raquo;
<span class="title">Chat</span>
</div>
<div id="search">
<a class="full_list_link" id="class_list_link"
href="class_list.html">
<svg width="24" height="24">
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
</svg>
</a>
</div>
<div class="clear"></div>
</div>
<div id="content"><h1>Module: Chat
</h1>
<div class="box_info">
<dl>
<dt>Defined in:</dt>
<dd>plugins/chat/app/services/base.rb<span class="defines">,<br />
plugins/chat/app/services/update_user_last_read.rb,<br /> plugins/chat/app/services/trash_channel.rb,<br /> plugins/chat/app/services/update_channel.rb,<br /> plugins/chat/app/services/update_channel_status.rb</span>
</dd>
</dl>
</div>
<h2>Defined Under Namespace</h2>
<p class="children">
<strong class="modules">Modules:</strong> <span class='object_link'><a href="Chat/Service.html" title="Chat::Service (module)">Service</a></span>
</p>
</div>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
0.9.28.
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,120 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Module: Chat::Service
&mdash; Documentation by YARD 0.9.28
</title>
<link rel="stylesheet" href="../css/style.css" type="text/css" />
<link rel="stylesheet" href="../css/common.css" type="text/css" />
<script type="text/javascript">
pathId = "Chat::Service";
relpath = '../';
</script>
<script type="text/javascript" charset="utf-8" src="../js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="../js/app.js"></script>
</head>
<body>
<div class="nav_wrap">
<iframe id="nav" src="../class_list.html?1"></iframe>
<div id="resizer"></div>
</div>
<div id="main" tabindex="-1">
<div id="header">
<div id="menu">
<a href="../_index.html">Index (S)</a> &raquo;
<span class='title'><span class='object_link'><a href="../Chat.html" title="Chat (module)">Chat</a></span></span>
&raquo;
<span class="title">Service</span>
</div>
<div id="search">
<a class="full_list_link" id="class_list_link"
href="../class_list.html">
<svg width="24" height="24">
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
</svg>
</a>
</div>
<div class="clear"></div>
</div>
<div id="content"><h1>Module: Chat::Service
</h1>
<div class="box_info">
<dl>
<dt>Defined in:</dt>
<dd>plugins/chat/app/services/base.rb<span class="defines">,<br />
plugins/chat/app/services/update_user_last_read.rb,<br /> plugins/chat/app/services/trash_channel.rb,<br /> plugins/chat/app/services/update_channel.rb,<br /> plugins/chat/app/services/update_channel_status.rb</span>
</dd>
</dl>
</div>
<h2>Defined Under Namespace</h2>
<p class="children">
<strong class="modules">Modules:</strong> <span class='object_link'><a href="Service/Base.html" title="Chat::Service::Base (module)">Base</a></span>
<strong class="classes">Classes:</strong> <span class='object_link'><a href="Service/TrashChannel.html" title="Chat::Service::TrashChannel (class)">TrashChannel</a></span>, <span class='object_link'><a href="Service/UpdateChannel.html" title="Chat::Service::UpdateChannel (class)">UpdateChannel</a></span>, <span class='object_link'><a href="Service/UpdateChannelStatus.html" title="Chat::Service::UpdateChannelStatus (class)">UpdateChannelStatus</a></span>, <span class='object_link'><a href="Service/UpdateUserLastRead.html" title="Chat::Service::UpdateUserLastRead (class)">UpdateUserLastRead</a></span>
</p>
</div>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
0.9.28.
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,723 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Module: Chat::Service::Base
&mdash; Documentation by YARD 0.9.28
</title>
<link rel="stylesheet" href="../../css/style.css" type="text/css" />
<link rel="stylesheet" href="../../css/common.css" type="text/css" />
<script type="text/javascript">
pathId = "Chat::Service::Base";
relpath = '../../';
</script>
<script type="text/javascript" charset="utf-8" src="../../js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="../../js/app.js"></script>
</head>
<body>
<div class="nav_wrap">
<iframe id="nav" src="../../class_list.html?1"></iframe>
<div id="resizer"></div>
</div>
<div id="main" tabindex="-1">
<div id="header">
<div id="menu">
<a href="../../_index.html">Index (B)</a> &raquo;
<span class='title'><span class='object_link'><a href="../../Chat.html" title="Chat (module)">Chat</a></span></span> &raquo; <span class='title'><span class='object_link'><a href="../Service.html" title="Chat::Service (module)">Service</a></span></span>
&raquo;
<span class="title">Base</span>
</div>
<div id="search">
<a class="full_list_link" id="class_list_link"
href="../../class_list.html">
<svg width="24" height="24">
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
</svg>
</a>
</div>
<div class="clear"></div>
</div>
<div id="content"><h1>Module: Chat::Service::Base
</h1>
<div class="box_info">
<dl>
<dt>Extended by:</dt>
<dd>ActiveSupport::Concern</dd>
</dl>
<dl>
<dt>Included in:</dt>
<dd><span class='object_link'><a href="TrashChannel.html" title="Chat::Service::TrashChannel (class)">TrashChannel</a></span>, <span class='object_link'><a href="UpdateChannel.html" title="Chat::Service::UpdateChannel (class)">UpdateChannel</a></span>, <span class='object_link'><a href="UpdateChannelStatus.html" title="Chat::Service::UpdateChannelStatus (class)">UpdateChannelStatus</a></span>, <span class='object_link'><a href="UpdateUserLastRead.html" title="Chat::Service::UpdateUserLastRead (class)">UpdateUserLastRead</a></span></dd>
</dl>
<dl>
<dt>Defined in:</dt>
<dd>plugins/chat/app/services/base.rb</dd>
</dl>
</div>
<h2>Overview</h2><div class="docstring">
<div class="discussion">
<p>Module to be included to provide steps DSL to any class. This allows to create easy to understand services as the whole service cycle is visible simply by reading the beginning of its class.</p>
<p>Steps are executed in the order theyre defined. They will use their name to execute the corresponding method defined in the service class.</p>
<p>Currently, there are 5 types of steps:</p>
<ul><li>
<p><tt>model(name = :model)</tt>: used to instantiate a model (either by building it or fetching it from the DB). If a falsy value is returned, then the step will fail. Otherwise the resulting object will be assigned in <code>context[name]</code> (<code>context[:model]</code> by default).</p>
</li><li>
<p><tt>policy(name = :default)</tt>: used to perform a check on the state of the system. Typically used to run guardians. If a falsy value is returned, the step will fail.</p>
</li><li>
<p><tt>contract(name = :default)</tt>: used to validate the input parameters, typically provided by a user calling an endpoint. A special embedded <code>Contract</code> class has to be defined to holds the validations. If the validations fail, the step will fail. Otherwise, the resulting contract will be available in <tt><a href=":"contract.default"">context</a></tt>.</p>
</li><li>
<p><tt>step(name)</tt>: used to run small snippets of arbitrary code. The step doesnt care about its return value, so to mark the service as failed, #fail! has to be called explicitly.</p>
</li><li>
<p><code>transaction</code>: used to wrap other steps inside a DB transaction.</p>
</li></ul>
<p>The methods defined on the service are automatically provided with the whole context passed as keyword arguments. This allows to define in a very explicit way what dependencies are used by the method. If for whatever reason a key isnt found in the current context, then Ruby will raise an exception when the method is called.</p>
<p>Regarding contract classes, they have automatically ActiveModel modules included so all the ActiveModel API is available.</p>
</div>
</div>
<div class="tags">
<div class="examples">
<p class="tag_title">Examples:</p>
<p class="example_title"><div class='inline'>
<p>An example from the <span class='object_link'><a href="TrashChannel.html" title="Chat::Service::TrashChannel (class)">TrashChannel</a></span> service</p>
</div></p>
<pre class="example code"><code><span class='kw'>class</span> <span class='const'><span class='object_link'><a href="TrashChannel.html" title="Chat::Service::TrashChannel (class)">TrashChannel</a></span></span>
<span class='id identifier rubyid_include'>include</span> <span class='const'>Base</span>
<span class='id identifier rubyid_model'>model</span> <span class='symbol'>:channel</span><span class='comma'>,</span> <span class='symbol'>:fetch_channel</span>
<span class='id identifier rubyid_policy'>policy</span> <span class='symbol'>:invalid_access</span>
<span class='id identifier rubyid_transaction'>transaction</span> <span class='kw'>do</span>
<span class='id identifier rubyid_step'>step</span> <span class='symbol'>:prevents_slug_collision</span>
<span class='id identifier rubyid_step'>step</span> <span class='symbol'>:soft_delete_channel</span>
<span class='id identifier rubyid_step'>step</span> <span class='symbol'>:log_channel_deletion</span>
<span class='kw'>end</span>
<span class='id identifier rubyid_step'>step</span> <span class='symbol'>:enqueue_delete_channel_relations_job</span>
<span class='id identifier rubyid_private'>private</span>
<span class='kw'>def</span> <span class='id identifier rubyid_fetch_channel'>fetch_channel</span><span class='lparen'>(</span><span class='label'>channel_id:</span><span class='comma'>,</span> <span class='op'>**</span><span class='rparen'>)</span>
<span class='const'>ChatChannel</span><span class='period'>.</span><span class='id identifier rubyid_find_by'>find_by</span><span class='lparen'>(</span><span class='label'>id:</span> <span class='id identifier rubyid_channel_id'>channel_id</span><span class='rparen'>)</span>
<span class='kw'>end</span>
<span class='kw'>def</span> <span class='id identifier rubyid_invalid_access'>invalid_access</span><span class='lparen'>(</span><span class='label'>guardian:</span><span class='comma'>,</span> <span class='label'>channel:</span><span class='comma'>,</span> <span class='op'>**</span><span class='rparen'>)</span>
<span class='id identifier rubyid_guardian'>guardian</span><span class='period'>.</span><span class='id identifier rubyid_can_preview_chat_channel?'>can_preview_chat_channel?</span><span class='lparen'>(</span><span class='id identifier rubyid_channel'>channel</span><span class='rparen'>)</span> <span class='op'>&amp;&amp;</span> <span class='id identifier rubyid_guardian'>guardian</span><span class='period'>.</span><span class='id identifier rubyid_can_delete_chat_channel?'>can_delete_chat_channel?</span>
<span class='kw'>end</span>
<span class='kw'>def</span> <span class='id identifier rubyid_prevents_slug_collision'>prevents_slug_collision</span><span class='lparen'>(</span><span class='label'>channel:</span><span class='comma'>,</span> <span class='op'>**</span><span class='rparen'>)</span>
<span class='id identifier rubyid_…'></span>
<span class='kw'>end</span>
<span class='kw'>def</span> <span class='id identifier rubyid_soft_delete_channel'>soft_delete_channel</span><span class='lparen'>(</span><span class='label'>guardian:</span><span class='comma'>,</span> <span class='label'>channel:</span><span class='comma'>,</span> <span class='op'>**</span><span class='rparen'>)</span>
<span class='id identifier rubyid_…'></span>
<span class='kw'>end</span>
<span class='kw'>def</span> <span class='id identifier rubyid_log_channel_deletion'>log_channel_deletion</span><span class='lparen'>(</span><span class='label'>guardian:</span><span class='comma'>,</span> <span class='label'>channel:</span><span class='comma'>,</span> <span class='op'>**</span><span class='rparen'>)</span>
<span class='id identifier rubyid_…'></span>
<span class='kw'>end</span>
<span class='kw'>def</span> <span class='id identifier rubyid_enqueue_delete_channel_relations_job'>enqueue_delete_channel_relations_job</span><span class='lparen'>(</span><span class='label'>channel:</span><span class='comma'>,</span> <span class='op'>**</span><span class='rparen'>)</span>
<span class='id identifier rubyid_…'></span>
<span class='kw'>end</span>
<span class='kw'>end</span></code></pre>
<p class="example_title"><div class='inline'>
<p>An example from the <span class='object_link'><a href="UpdateChannelStatus.html" title="Chat::Service::UpdateChannelStatus (class)">UpdateChannelStatus</a></span> service which uses a contract</p>
</div></p>
<pre class="example code"><code><span class='kw'>class</span> <span class='const'><span class='object_link'><a href="UpdateChannelStatus.html" title="Chat::Service::UpdateChannelStatus (class)">UpdateChannelStatus</a></span></span>
<span class='id identifier rubyid_include'>include</span> <span class='const'>Base</span>
<span class='id identifier rubyid_model'>model</span> <span class='symbol'>:channel</span><span class='comma'>,</span> <span class='symbol'>:fetch_channel</span>
<span class='id identifier rubyid_contract'>contract</span>
<span class='id identifier rubyid_policy'>policy</span> <span class='symbol'>:check_channel_permission</span>
<span class='id identifier rubyid_step'>step</span> <span class='symbol'>:change_status</span>
<span class='kw'>class</span> <span class='const'>Contract</span>
<span class='id identifier rubyid_attribute'>attribute</span> <span class='symbol'>:status</span>
<span class='id identifier rubyid_validates'>validates</span> <span class='symbol'>:status</span><span class='comma'>,</span> <span class='label'>inclusion:</span> <span class='lbrace'>{</span> <span class='label'>in:</span> <span class='const'>ChatChannel</span><span class='period'>.</span><span class='id identifier rubyid_editable_statuses'>editable_statuses</span><span class='period'>.</span><span class='id identifier rubyid_keys'>keys</span> <span class='rbrace'>}</span>
<span class='kw'>end</span>
<span class='id identifier rubyid_…'></span>
<span class='kw'>end</span></code></pre>
</div>
</div><h2>Defined Under Namespace</h2>
<p class="children">
<strong class="classes">Classes:</strong> <span class='object_link'><a href="Base/Context.html" title="Chat::Service::Base::Context (class)">Context</a></span>, <span class='object_link'><a href="Base/Failure.html" title="Chat::Service::Base::Failure (class)">Failure</a></span>
</p>
<h2>
Class Method Summary
<small><a href="#" class="summary_toggle">collapse</a></small>
</h2>
<ul class="summary">
<li class="public ">
<span class="summary_signature">
<a href="#contract-class_method" title="contract (class method)">.<strong>contract</strong>(name = :default, class_name: self::Contract, use_default_values_from: nil) &#x21d2; Object </a>
</span>
<span class="summary_desc"><div class='inline'>
<p>Checks the validity of the input parameters.</p>
</div></span>
</li>
<li class="public ">
<span class="summary_signature">
<a href="#model-class_method" title="model (class method)">.<strong>model</strong>(name = :model, step_name = :&quot;fetch_#{name}&quot;) &#x21d2; Object </a>
</span>
<span class="summary_desc"><div class='inline'>
<p>Evaluates arbitrary code to build or fetch a model (typically from the DB).</p>
</div></span>
</li>
<li class="public ">
<span class="summary_signature">
<a href="#policy-class_method" title="policy (class method)">.<strong>policy</strong>(name = :default) &#x21d2; Object </a>
</span>
<span class="summary_desc"><div class='inline'>
<p>Performs checks related to the state of the system.</p>
</div></span>
</li>
<li class="public ">
<span class="summary_signature">
<a href="#step-class_method" title="step (class method)">.<strong>step</strong>(name) &#x21d2; Object </a>
</span>
<span class="summary_desc"><div class='inline'>
<p>Runs arbitrary code.</p>
</div></span>
</li>
<li class="public ">
<span class="summary_signature">
<a href="#transaction-class_method" title="transaction (class method)">.<strong>transaction</strong>(&amp;block) &#x21d2; Object </a>
</span>
<span class="summary_desc"><div class='inline'>
<p>Runs steps inside a DB transaction.</p>
</div></span>
</li>
</ul>
<div id="class_method_details" class="method_details_list">
<h2>Class Method Details</h2>
<div class="method_details first">
<h3 class="signature first" id="contract-class_method">
.<strong>contract</strong>(name = :default, class_name: self::Contract, use_default_values_from: nil) &#x21d2; <tt>Object</tt>
</h3><div class="docstring">
<div class="discussion">
<p>Checks the validity of the input parameters. Implements ActiveModel::Validations and ActiveModel::Attributes.</p>
<p>It stores the resulting contract in <tt>context [“contract.default”]</tt> by default (can be customized by providing the <code>name</code> argument).</p>
</div>
</div>
<div class="tags">
<div class="examples">
<p class="tag_title">Examples:</p>
<pre class="example code"><code><span class='id identifier rubyid_contract'>contract</span>
<span class='kw'>class</span> <span class='const'>Contract</span>
<span class='id identifier rubyid_attribute'>attribute</span> <span class='symbol'>:name</span>
<span class='id identifier rubyid_validates'>validates</span> <span class='symbol'>:name</span><span class='comma'>,</span> <span class='label'>presence:</span> <span class='kw'>true</span>
<span class='kw'>end</span></code></pre>
</div>
<p class="tag_title">Parameters:</p>
<ul class="param">
<li>
<span class='name'>name</span>
<span class='type'>(<tt>Symbol</tt>)</span>
<em class="default">(defaults to: <tt>:default</tt>)</em>
&mdash;
<div class='inline'>
<p>name for this contract</p>
</div>
</li>
<li>
<span class='name'>class_name</span>
<span class='type'>(<tt>Class</tt>)</span>
<em class="default">(defaults to: <tt>self::Contract</tt>)</em>
&mdash;
<div class='inline'>
<p>a class defining the contract</p>
</div>
</li>
<li>
<span class='name'>use_default_values_from</span>
<span class='type'>(<tt>Symbol</tt>)</span>
<em class="default">(defaults to: <tt>nil</tt>)</em>
&mdash;
<div class='inline'>
<p>name of the model to get default values from</p>
</div>
</li>
</ul>
</div>
</div>
<div class="method_details ">
<h3 class="signature " id="model-class_method">
.<strong>model</strong>(name = :model, step_name = :&quot;fetch_#{name}&quot;) &#x21d2; <tt>Object</tt>
</h3><div class="docstring">
<div class="discussion">
<p>Evaluates arbitrary code to build or fetch a model (typically from the DB). If the step returns a falsy value, then the step will fail.</p>
<p>It stores the resulting model in <code>context[:model]</code> by default (can be customized by providing the <code>name</code> argument).</p>
</div>
</div>
<div class="tags">
<div class="examples">
<p class="tag_title">Examples:</p>
<pre class="example code"><code><span class='id identifier rubyid_model'>model</span> <span class='symbol'>:channel</span><span class='comma'>,</span> <span class='symbol'>:fetch_channel</span>
<span class='id identifier rubyid_private'>private</span>
<span class='kw'>def</span> <span class='id identifier rubyid_fetch_channel'>fetch_channel</span><span class='lparen'>(</span><span class='label'>channel_id:</span><span class='comma'>,</span> <span class='op'>**</span><span class='rparen'>)</span>
<span class='const'>ChatChannel</span><span class='period'>.</span><span class='id identifier rubyid_find_by'>find_by</span><span class='lparen'>(</span><span class='label'>id:</span> <span class='id identifier rubyid_channel_id'>channel_id</span><span class='rparen'>)</span>
<span class='kw'>end</span></code></pre>
</div>
<p class="tag_title">Parameters:</p>
<ul class="param">
<li>
<span class='name'>name</span>
<span class='type'>(<tt>Symbol</tt>)</span>
<em class="default">(defaults to: <tt>:model</tt>)</em>
&mdash;
<div class='inline'>
<p>name of the model</p>
</div>
</li>
<li>
<span class='name'>step_name</span>
<span class='type'>(<tt>Symbol</tt>)</span>
<em class="default">(defaults to: <tt>:&quot;fetch_#{name}&quot;</tt>)</em>
&mdash;
<div class='inline'>
<p>name of the method to call for this step</p>
</div>
</li>
</ul>
</div>
</div>
<div class="method_details ">
<h3 class="signature " id="policy-class_method">
.<strong>policy</strong>(name = :default) &#x21d2; <tt>Object</tt>
</h3><div class="docstring">
<div class="discussion">
<p>Performs checks related to the state of the system. If the step doesnt return a truthy value, then the policy will fail.</p>
</div>
</div>
<div class="tags">
<div class="examples">
<p class="tag_title">Examples:</p>
<pre class="example code"><code><span class='id identifier rubyid_policy'>policy</span> <span class='symbol'>:no_direct_message_channel</span>
<span class='id identifier rubyid_private'>private</span>
<span class='kw'>def</span> <span class='id identifier rubyid_no_direct_message_channel'>no_direct_message_channel</span><span class='lparen'>(</span><span class='label'>channel:</span><span class='comma'>,</span> <span class='op'>**</span><span class='rparen'>)</span>
<span class='op'>!</span><span class='id identifier rubyid_channel'>channel</span><span class='period'>.</span><span class='id identifier rubyid_direct_message_channel?'>direct_message_channel?</span>
<span class='kw'>end</span></code></pre>
</div>
<p class="tag_title">Parameters:</p>
<ul class="param">
<li>
<span class='name'>name</span>
<span class='type'>(<tt>Symbol</tt>)</span>
<em class="default">(defaults to: <tt>:default</tt>)</em>
&mdash;
<div class='inline'>
<p>name for this policy</p>
</div>
</li>
</ul>
</div>
</div>
<div class="method_details ">
<h3 class="signature " id="step-class_method">
.<strong>step</strong>(name) &#x21d2; <tt>Object</tt>
</h3><div class="docstring">
<div class="discussion">
<p>Runs arbitrary code. To mark a step as failed, a call to #fail! needs to be made explicitly.</p>
</div>
</div>
<div class="tags">
<div class="examples">
<p class="tag_title">Examples:</p>
<pre class="example code"><code><span class='id identifier rubyid_step'>step</span> <span class='symbol'>:update_channel</span>
<span class='id identifier rubyid_private'>private</span>
<span class='kw'>def</span> <span class='id identifier rubyid_update_channel'>update_channel</span><span class='lparen'>(</span><span class='label'>channel:</span><span class='comma'>,</span> <span class='label'>params_to_edit:</span><span class='comma'>,</span> <span class='op'>**</span><span class='rparen'>)</span>
<span class='id identifier rubyid_channel'>channel</span><span class='period'>.</span><span class='id identifier rubyid_update!'>update!</span><span class='lparen'>(</span><span class='id identifier rubyid_params_to_edit'>params_to_edit</span><span class='rparen'>)</span>
<span class='kw'>end</span></code></pre>
<p class="example_title"><div class='inline'>
<p>using #fail! in a step</p>
</div></p>
<pre class="example code"><code><span class='id identifier rubyid_step'>step</span> <span class='symbol'>:save_channel</span>
<span class='id identifier rubyid_private'>private</span>
<span class='kw'>def</span> <span class='id identifier rubyid_save_channel'>save_channel</span><span class='lparen'>(</span><span class='label'>channel:</span><span class='comma'>,</span> <span class='op'>**</span><span class='rparen'>)</span>
<span class='id identifier rubyid_fail!'>fail!</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>something went wrong</span><span class='tstring_end'>&quot;</span></span><span class='rparen'>)</span> <span class='kw'>unless</span> <span class='id identifier rubyid_channel'>channel</span><span class='period'>.</span><span class='id identifier rubyid_save'>save</span>
<span class='kw'>end</span></code></pre>
</div>
<p class="tag_title">Parameters:</p>
<ul class="param">
<li>
<span class='name'>name</span>
<span class='type'>(<tt>Symbol</tt>)</span>
&mdash;
<div class='inline'>
<p>the name of this step</p>
</div>
</li>
</ul>
</div>
</div>
<div class="method_details ">
<h3 class="signature " id="transaction-class_method">
.<strong>transaction</strong>(&amp;block) &#x21d2; <tt>Object</tt>
</h3><div class="docstring">
<div class="discussion">
<p>Runs steps inside a DB transaction.</p>
</div>
</div>
<div class="tags">
<div class="examples">
<p class="tag_title">Examples:</p>
<pre class="example code"><code><span class='id identifier rubyid_transaction'>transaction</span> <span class='kw'>do</span>
<span class='id identifier rubyid_step'>step</span> <span class='symbol'>:prevents_slug_collision</span>
<span class='id identifier rubyid_step'>step</span> <span class='symbol'>:soft_delete_channel</span>
<span class='id identifier rubyid_step'>step</span> <span class='symbol'>:log_channel_deletion</span>
<span class='kw'>end</span></code></pre>
</div>
<p class="tag_title">Parameters:</p>
<ul class="param">
<li>
<span class='name'>block</span>
<span class='type'>(<tt>Proc</tt>)</span>
&mdash;
<div class='inline'>
<p>a block containing steps to be run inside a transaction</p>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
0.9.28.
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,485 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Class: Chat::Service::Base::Context
&mdash; Documentation by YARD 0.9.28
</title>
<link rel="stylesheet" href="../../../css/style.css" type="text/css" />
<link rel="stylesheet" href="../../../css/common.css" type="text/css" />
<script type="text/javascript">
pathId = "Chat::Service::Base::Context";
relpath = '../../../';
</script>
<script type="text/javascript" charset="utf-8" src="../../../js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="../../../js/app.js"></script>
</head>
<body>
<div class="nav_wrap">
<iframe id="nav" src="../../../class_list.html?1"></iframe>
<div id="resizer"></div>
</div>
<div id="main" tabindex="-1">
<div id="header">
<div id="menu">
<a href="../../../_index.html">Index (C)</a> &raquo;
<span class='title'><span class='object_link'><a href="../../../Chat.html" title="Chat (module)">Chat</a></span></span> &raquo; <span class='title'><span class='object_link'><a href="../../Service.html" title="Chat::Service (module)">Service</a></span></span> &raquo; <span class='title'><span class='object_link'><a href="../Base.html" title="Chat::Service::Base (module)">Base</a></span></span>
&raquo;
<span class="title">Context</span>
</div>
<div id="search">
<a class="full_list_link" id="class_list_link"
href="../../../class_list.html">
<svg width="24" height="24">
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
</svg>
</a>
</div>
<div class="clear"></div>
</div>
<div id="content"><h1>Class: Chat::Service::Base::Context
</h1>
<div class="box_info">
<dl>
<dt>Inherits:</dt>
<dd>
<span class="inheritName">OpenStruct</span>
<ul class="fullTree">
<li>Object</li>
<li class="next">OpenStruct</li>
<li class="next">Chat::Service::Base::Context</li>
</ul>
<a href="#" class="inheritanceTree">show all</a>
</dd>
</dl>
<dl>
<dt>Defined in:</dt>
<dd>plugins/chat/app/services/base.rb</dd>
</dl>
</div>
<h2>Overview</h2><div class="docstring">
<div class="discussion">
<p>Simple structure to hold the context of the service during its whole lifecycle.</p>
</div>
</div>
<div class="tags">
</div>
<h2>
Instance Method Summary
<small><a href="#" class="summary_toggle">collapse</a></small>
</h2>
<ul class="summary">
<li class="public ">
<span class="summary_signature">
<a href="#fail-instance_method" title="#fail (instance method)">#<strong>fail</strong>(context = {}) &#x21d2; Context </a>
</span>
<span class="summary_desc"><div class='inline'>
<p>Marks the context as failed without raising an exception.</p>
</div></span>
</li>
<li class="public ">
<span class="summary_signature">
<a href="#fail!-instance_method" title="#fail! (instance method)">#<strong>fail!</strong>(context = {}) &#x21d2; Context </a>
</span>
<span class="summary_desc"><div class='inline'>
<p>Marks the context as failed.</p>
</div></span>
</li>
<li class="public ">
<span class="summary_signature">
<a href="#failure%3F-instance_method" title="#failure? (instance method)">#<strong>failure?</strong> &#x21d2; Boolean </a>
</span>
<span class="summary_desc"><div class='inline'>
<p>Returns <code>true</code> if the context is set as failed.</p>
</div></span>
</li>
<li class="public ">
<span class="summary_signature">
<a href="#success%3F-instance_method" title="#success? (instance method)">#<strong>success?</strong> &#x21d2; Boolean </a>
</span>
<span class="summary_desc"><div class='inline'>
<p>Returns <code>true</code> if the conext is set as successful (default).</p>
</div></span>
</li>
</ul>
<div id="instance_method_details" class="method_details_list">
<h2>Instance Method Details</h2>
<div class="method_details first">
<h3 class="signature first" id="fail-instance_method">
#<strong>fail</strong>(context = {}) &#x21d2; <tt><span class='object_link'><a href="" title="Chat::Service::Base::Context (class)">Context</a></span></tt>
</h3><div class="docstring">
<div class="discussion">
<p>Marks the context as failed without raising an exception.</p>
</div>
</div>
<div class="tags">
<div class="examples">
<p class="tag_title">Examples:</p>
<pre class="example code"><code><span class='id identifier rubyid_context'>context</span><span class='period'>.</span><span class='id identifier rubyid_fail'>fail</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>failure</span><span class='label_end'>&quot;:</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>something went wrong</span><span class='tstring_end'>&quot;</span></span><span class='rparen'>)</span></code></pre>
</div>
<p class="tag_title">Parameters:</p>
<ul class="param">
<li>
<span class='name'>context</span>
<span class='type'>(<tt>Hash</tt>, <tt><span class='object_link'><a href="" title="Chat::Service::Base::Context (class)">Context</a></span></tt>)</span>
<em class="default">(defaults to: <tt>{}</tt>)</em>
&mdash;
<div class='inline'>
<p>the context to merge into the current one</p>
</div>
</li>
</ul>
<p class="tag_title">Returns:</p>
<ul class="return">
<li>
<span class='type'>(<tt><span class='object_link'><a href="" title="Chat::Service::Base::Context (class)">Context</a></span></tt>)</span>
</li>
</ul>
</div>
</div>
<div class="method_details ">
<h3 class="signature " id="fail!-instance_method">
#<strong>fail!</strong>(context = {}) &#x21d2; <tt><span class='object_link'><a href="" title="Chat::Service::Base::Context (class)">Context</a></span></tt>
</h3><div class="docstring">
<div class="discussion">
<p>Marks the context as failed.</p>
</div>
</div>
<div class="tags">
<div class="examples">
<p class="tag_title">Examples:</p>
<pre class="example code"><code><span class='id identifier rubyid_context'>context</span><span class='period'>.</span><span class='id identifier rubyid_fail!'>fail!</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>failure</span><span class='label_end'>&quot;:</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>something went wrong</span><span class='tstring_end'>&quot;</span></span><span class='rparen'>)</span></code></pre>
</div>
<p class="tag_title">Parameters:</p>
<ul class="param">
<li>
<span class='name'>context</span>
<span class='type'>(<tt>Hash</tt>, <tt><span class='object_link'><a href="" title="Chat::Service::Base::Context (class)">Context</a></span></tt>)</span>
<em class="default">(defaults to: <tt>{}</tt>)</em>
&mdash;
<div class='inline'>
<p>the context to merge into the current one</p>
</div>
</li>
</ul>
<p class="tag_title">Returns:</p>
<ul class="return">
<li>
<span class='type'>(<tt><span class='object_link'><a href="" title="Chat::Service::Base::Context (class)">Context</a></span></tt>)</span>
</li>
</ul>
<p class="tag_title">Raises:</p>
<ul class="raise">
<li>
<span class='type'>(<tt><span class='object_link'><a href="Failure.html" title="Chat::Service::Base::Failure (class)">Failure</a></span></tt>)</span>
</li>
</ul>
</div>
</div>
<div class="method_details ">
<h3 class="signature " id="failure?-instance_method">
#<strong>failure?</strong> &#x21d2; <tt>Boolean</tt>
</h3><div class="docstring">
<div class="discussion">
<p>Returns <code>true</code> if the context is set as failed</p>
</div>
</div>
<div class="tags">
<p class="tag_title">Returns:</p>
<ul class="return">
<li>
<span class='type'>(<tt>Boolean</tt>)</span>
&mdash;
<div class='inline'>
<p>returns <code>true</code> if the context is set as failed</p>
</div>
</li>
</ul>
<p class="tag_title">See Also:</p>
<ul class="see">
<li><span class='object_link'><a href="#fail!-instance_method" title="Chat::Service::Base::Context#fail! (method)">#fail!</a></span></li>
<li><span class='object_link'><a href="#fail-instance_method" title="Chat::Service::Base::Context#fail (method)">#fail</a></span></li>
</ul>
</div>
</div>
<div class="method_details ">
<h3 class="signature " id="success?-instance_method">
#<strong>success?</strong> &#x21d2; <tt>Boolean</tt>
</h3><div class="docstring">
<div class="discussion">
<p>Returns <code>true</code> if the conext is set as successful (default)</p>
</div>
</div>
<div class="tags">
<p class="tag_title">Returns:</p>
<ul class="return">
<li>
<span class='type'>(<tt>Boolean</tt>)</span>
&mdash;
<div class='inline'>
<p>returns <code>true</code> if the conext is set as successful (default)</p>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
0.9.28.
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,209 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Exception: Chat::Service::Base::Failure
&mdash; Documentation by YARD 0.9.28
</title>
<link rel="stylesheet" href="../../../css/style.css" type="text/css" />
<link rel="stylesheet" href="../../../css/common.css" type="text/css" />
<script type="text/javascript">
pathId = "Chat::Service::Base::Failure";
relpath = '../../../';
</script>
<script type="text/javascript" charset="utf-8" src="../../../js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="../../../js/app.js"></script>
</head>
<body>
<div class="nav_wrap">
<iframe id="nav" src="../../../class_list.html?1"></iframe>
<div id="resizer"></div>
</div>
<div id="main" tabindex="-1">
<div id="header">
<div id="menu">
<a href="../../../_index.html">Index (F)</a> &raquo;
<span class='title'><span class='object_link'><a href="../../../Chat.html" title="Chat (module)">Chat</a></span></span> &raquo; <span class='title'><span class='object_link'><a href="../../Service.html" title="Chat::Service (module)">Service</a></span></span> &raquo; <span class='title'><span class='object_link'><a href="../Base.html" title="Chat::Service::Base (module)">Base</a></span></span>
&raquo;
<span class="title">Failure</span>
</div>
<div id="search">
<a class="full_list_link" id="class_list_link"
href="../../../class_list.html">
<svg width="24" height="24">
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
</svg>
</a>
</div>
<div class="clear"></div>
</div>
<div id="content"><h1>Exception: Chat::Service::Base::Failure
</h1>
<div class="box_info">
<dl>
<dt>Inherits:</dt>
<dd>
<span class="inheritName">StandardError</span>
<ul class="fullTree">
<li>Object</li>
<li class="next">StandardError</li>
<li class="next">Chat::Service::Base::Failure</li>
</ul>
<a href="#" class="inheritanceTree">show all</a>
</dd>
</dl>
<dl>
<dt>Defined in:</dt>
<dd>plugins/chat/app/services/base.rb</dd>
</dl>
</div>
<h2>Overview</h2><div class="docstring">
<div class="discussion">
<p>The only exception that can be raised by a service.</p>
</div>
</div>
<div class="tags">
</div>
<h2>Instance Attribute Summary <small><a href="#" class="summary_toggle">collapse</a></small></h2>
<ul class="summary">
<li class="public ">
<span class="summary_signature">
<a href="#context-instance_method" title="#context (instance method)">#<strong>context</strong> &#x21d2; Context </a>
</span>
<span class="note title readonly">readonly</span>
<span class="summary_desc"><div class='inline'></div></span>
</li>
</ul>
<div id="instance_attr_details" class="attr_details">
<h2>Instance Attribute Details</h2>
<span id=""></span>
<div class="method_details first">
<h3 class="signature first" id="context-instance_method">
#<strong>context</strong> &#x21d2; <tt><span class='object_link'><a href="Context.html" title="Chat::Service::Base::Context (class)">Context</a></span></tt> <span class="extras">(readonly)</span>
</h3><div class="docstring">
<div class="discussion">
</div>
</div>
<div class="tags">
<p class="tag_title">Returns:</p>
<ul class="return">
<li>
<span class='type'>(<tt><span class='object_link'><a href="Context.html" title="Chat::Service::Base::Context (class)">Context</a></span></tt>)</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
0.9.28.
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,277 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Class: Chat::Service::TrashChannel
&mdash; Documentation by YARD 0.9.28
</title>
<link rel="stylesheet" href="../../css/style.css" type="text/css" />
<link rel="stylesheet" href="../../css/common.css" type="text/css" />
<script type="text/javascript">
pathId = "Chat::Service::TrashChannel";
relpath = '../../';
</script>
<script type="text/javascript" charset="utf-8" src="../../js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="../../js/app.js"></script>
</head>
<body>
<div class="nav_wrap">
<iframe id="nav" src="../../class_list.html?1"></iframe>
<div id="resizer"></div>
</div>
<div id="main" tabindex="-1">
<div id="header">
<div id="menu">
<a href="../../_index.html">Index (T)</a> &raquo;
<span class='title'><span class='object_link'><a href="../../Chat.html" title="Chat (module)">Chat</a></span></span> &raquo; <span class='title'><span class='object_link'><a href="../Service.html" title="Chat::Service (module)">Service</a></span></span>
&raquo;
<span class="title">TrashChannel</span>
</div>
<div id="search">
<a class="full_list_link" id="class_list_link"
href="../../class_list.html">
<svg width="24" height="24">
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
</svg>
</a>
</div>
<div class="clear"></div>
</div>
<div id="content"><h1>Class: Chat::Service::TrashChannel
</h1>
<div class="box_info">
<dl>
<dt>Inherits:</dt>
<dd>
<span class="inheritName">Object</span>
<ul class="fullTree">
<li>Object</li>
<li class="next">Chat::Service::TrashChannel</li>
</ul>
<a href="#" class="inheritanceTree">show all</a>
</dd>
</dl>
<dl>
<dt>Includes:</dt>
<dd><span class='object_link'><a href="Base.html" title="Chat::Service::Base (module)">Base</a></span></dd>
</dl>
<dl>
<dt>Defined in:</dt>
<dd>plugins/chat/app/services/trash_channel.rb</dd>
</dl>
</div>
<h2>Overview</h2><div class="docstring">
<div class="discussion">
<p>Service responsible for trashing a chat channel. Note the slug is modified to prevent collisions.</p>
</div>
</div>
<div class="tags">
<div class="examples">
<p class="tag_title">Examples:</p>
<pre class="example code"><code><span class='const'><span class='object_link'><a href="../../Chat.html" title="Chat (module)">Chat</a></span></span><span class='op'>::</span><span class='const'><span class='object_link'><a href="../Service.html" title="Chat::Service (module)">Service</a></span></span><span class='op'>::</span><span class='const'>TrashChannel</span><span class='period'>.</span><span class='id identifier rubyid_call'><span class='object_link'><a href="#call-instance_method" title="Chat::Service::TrashChannel#call (method)">call</a></span></span><span class='lparen'>(</span><span class='label'>channel_id:</span> <span class='int'>2</span><span class='comma'>,</span> <span class='label'>guardian:</span> <span class='id identifier rubyid_guardian'>guardian</span><span class='rparen'>)</span></code></pre>
</div>
</div>
<h2>
Constant Summary
<small><a href="#" class="constants_summary_toggle">collapse</a></small>
</h2>
<dl class="constants">
<dt id="DELETE_CHANNEL_LOG_KEY-constant" class="">DELETE_CHANNEL_LOG_KEY =
</dt>
<dd><pre class="code"><span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>chat_channel_delete</span><span class='tstring_end'>&quot;</span></span></pre></dd>
</dl>
<h2>
Instance Method Summary
<small><a href="#" class="summary_toggle">collapse</a></small>
</h2>
<ul class="summary">
<li class="public ">
<span class="summary_signature">
<a href="#call-instance_method" title="#call (instance method)">#<strong>call</strong>(channel_id: , guardian: ) &#x21d2; Chat::Service::Base::Context </a>
</span>
<span class="summary_desc"><div class='inline'></div></span>
</li>
</ul>
<h3 class="inherited">Methods included from <span class='object_link'><a href="Base.html" title="Chat::Service::Base (module)">Base</a></span></h3>
<p class="inherited"><span class='object_link'><a href="Base.html#contract-class_method" title="Chat::Service::Base.contract (method)">contract</a></span>, <span class='object_link'><a href="Base.html#model-class_method" title="Chat::Service::Base.model (method)">model</a></span>, <span class='object_link'><a href="Base.html#policy-class_method" title="Chat::Service::Base.policy (method)">policy</a></span>, <span class='object_link'><a href="Base.html#step-class_method" title="Chat::Service::Base.step (method)">step</a></span>, <span class='object_link'><a href="Base.html#transaction-class_method" title="Chat::Service::Base.transaction (method)">transaction</a></span></p>
<div id="instance_method_details" class="method_details_list">
<h2>Instance Method Details</h2>
<div class="method_details first">
<h3 class="signature first" id="call-instance_method">
#<strong>call</strong>(channel_id: , guardian: ) &#x21d2; <tt><span class='object_link'><a href="Base/Context.html" title="Chat::Service::Base::Context (class)">Chat::Service::Base::Context</a></span></tt>
</h3><div class="docstring">
<div class="discussion">
</div>
</div>
<div class="tags">
<p class="tag_title">Parameters:</p>
<ul class="param">
<li>
<span class='name'>channel_id</span>
<span class='type'>(<tt>Integer</tt>)</span>
<em class="default">(defaults to: <tt></tt>)</em>
</li>
<li>
<span class='name'>guardian</span>
<span class='type'>(<tt>Guardian</tt>)</span>
<em class="default">(defaults to: <tt></tt>)</em>
</li>
</ul>
<p class="tag_title">Returns:</p>
<ul class="return">
<li>
<span class='type'>(<tt><span class='object_link'><a href="Base/Context.html" title="Chat::Service::Base::Context (class)">Chat::Service::Base::Context</a></span></tt>)</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
0.9.28.
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,346 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Class: Chat::Service::UpdateChannel
&mdash; Documentation by YARD 0.9.28
</title>
<link rel="stylesheet" href="../../css/style.css" type="text/css" />
<link rel="stylesheet" href="../../css/common.css" type="text/css" />
<script type="text/javascript">
pathId = "Chat::Service::UpdateChannel";
relpath = '../../';
</script>
<script type="text/javascript" charset="utf-8" src="../../js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="../../js/app.js"></script>
</head>
<body>
<div class="nav_wrap">
<iframe id="nav" src="../../class_list.html?1"></iframe>
<div id="resizer"></div>
</div>
<div id="main" tabindex="-1">
<div id="header">
<div id="menu">
<a href="../../_index.html">Index (U)</a> &raquo;
<span class='title'><span class='object_link'><a href="../../Chat.html" title="Chat (module)">Chat</a></span></span> &raquo; <span class='title'><span class='object_link'><a href="../Service.html" title="Chat::Service (module)">Service</a></span></span>
&raquo;
<span class="title">UpdateChannel</span>
</div>
<div id="search">
<a class="full_list_link" id="class_list_link"
href="../../class_list.html">
<svg width="24" height="24">
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
</svg>
</a>
</div>
<div class="clear"></div>
</div>
<div id="content"><h1>Class: Chat::Service::UpdateChannel
</h1>
<div class="box_info">
<dl>
<dt>Inherits:</dt>
<dd>
<span class="inheritName">Object</span>
<ul class="fullTree">
<li>Object</li>
<li class="next">Chat::Service::UpdateChannel</li>
</ul>
<a href="#" class="inheritanceTree">show all</a>
</dd>
</dl>
<dl>
<dt>Includes:</dt>
<dd><span class='object_link'><a href="Base.html" title="Chat::Service::Base (module)">Base</a></span></dd>
</dl>
<dl>
<dt>Defined in:</dt>
<dd>plugins/chat/app/services/update_channel.rb</dd>
</dl>
</div>
<h2>Overview</h2><div class="docstring">
<div class="discussion">
<p>Service responsible for updating a chat channels name, slug, and description.</p>
<p>For a CategoryChannel, the settings for auto_join_users and allow_channel_wide_mentions are also editable.</p>
</div>
</div>
<div class="tags">
<div class="examples">
<p class="tag_title">Examples:</p>
<pre class="example code"><code><span class='const'><span class='object_link'><a href="../../Chat.html" title="Chat (module)">Chat</a></span></span><span class='op'>::</span><span class='const'><span class='object_link'><a href="../Service.html" title="Chat::Service (module)">Service</a></span></span><span class='op'>::</span><span class='const'>UpdateChannel</span><span class='period'>.</span><span class='id identifier rubyid_call'><span class='object_link'><a href="#call-instance_method" title="Chat::Service::UpdateChannel#call (method)">call</a></span></span><span class='lparen'>(</span>
<span class='label'>channel_id:</span> <span class='int'>2</span><span class='comma'>,</span>
<span class='label'>guardian:</span> <span class='id identifier rubyid_guardian'>guardian</span><span class='comma'>,</span>
<span class='label'>name:</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>SuperChannel</span><span class='tstring_end'>&quot;</span></span><span class='comma'>,</span>
<span class='label'>description:</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>This is the best channel</span><span class='tstring_end'>&quot;</span></span><span class='comma'>,</span>
<span class='label'>slug:</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>super-channel</span><span class='tstring_end'>&quot;</span></span><span class='comma'>,</span>
<span class='rparen'>)</span></code></pre>
</div>
</div>
<h2>
Instance Method Summary
<small><a href="#" class="summary_toggle">collapse</a></small>
</h2>
<ul class="summary">
<li class="public ">
<span class="summary_signature">
<a href="#call-instance_method" title="#call (instance method)">#<strong>call</strong>(channel_id: , guardian: , **params_to_edit) &#x21d2; Chat::Service::Base::Context </a>
</span>
<span class="summary_desc"><div class='inline'></div></span>
</li>
</ul>
<h3 class="inherited">Methods included from <span class='object_link'><a href="Base.html" title="Chat::Service::Base (module)">Base</a></span></h3>
<p class="inherited"><span class='object_link'><a href="Base.html#contract-class_method" title="Chat::Service::Base.contract (method)">contract</a></span>, <span class='object_link'><a href="Base.html#model-class_method" title="Chat::Service::Base.model (method)">model</a></span>, <span class='object_link'><a href="Base.html#policy-class_method" title="Chat::Service::Base.policy (method)">policy</a></span>, <span class='object_link'><a href="Base.html#step-class_method" title="Chat::Service::Base.step (method)">step</a></span>, <span class='object_link'><a href="Base.html#transaction-class_method" title="Chat::Service::Base.transaction (method)">transaction</a></span></p>
<div id="instance_method_details" class="method_details_list">
<h2>Instance Method Details</h2>
<div class="method_details first">
<h3 class="signature first" id="call-instance_method">
#<strong>call</strong>(channel_id: , guardian: , **params_to_edit) &#x21d2; <tt><span class='object_link'><a href="Base/Context.html" title="Chat::Service::Base::Context (class)">Chat::Service::Base::Context</a></span></tt>
</h3><div class="docstring">
<div class="discussion">
</div>
</div>
<div class="tags">
<p class="tag_title">Parameters:</p>
<ul class="param">
<li>
<span class='name'>channel_id</span>
<span class='type'>(<tt>Integer</tt>)</span>
<em class="default">(defaults to: <tt></tt>)</em>
</li>
<li>
<span class='name'>guardian</span>
<span class='type'>(<tt>Guardian</tt>)</span>
<em class="default">(defaults to: <tt></tt>)</em>
</li>
<li>
<span class='name'>params_to_edit</span>
<span class='type'>(<tt>Hash</tt>)</span>
</li>
</ul>
<p class="tag_title">Options Hash (<tt>**params_to_edit</tt>):</p>
<ul class="option">
<li>
<span class="name">name</span>
<span class="type">(<tt>String</tt>, <tt>nil</tt>)</span>
<span class="default">
</span>
</li>
<li>
<span class="name">description</span>
<span class="type">(<tt>String</tt>, <tt>nil</tt>)</span>
<span class="default">
</span>
</li>
<li>
<span class="name">slug</span>
<span class="type">(<tt>String</tt>, <tt>nil</tt>)</span>
<span class="default">
</span>
</li>
<li>
<span class="name">auto_join_users</span>
<span class="type">(<tt>Boolean</tt>)</span>
<span class="default">
</span>
&mdash; <div class='inline'>
<p>Only valid for CategoryChannel. Whether active users with permission to see the category should automatically join the channel.</p>
</div>
</li>
<li>
<span class="name">allow_channel_wide_mentions</span>
<span class="type">(<tt>Boolean</tt>)</span>
<span class="default">
</span>
&mdash; <div class='inline'>
<p>Allow the use of @here and @all in the channel.</p>
</div>
</li>
</ul>
<p class="tag_title">Returns:</p>
<ul class="return">
<li>
<span class='type'>(<tt><span class='object_link'><a href="Base/Context.html" title="Chat::Service::Base::Context (class)">Chat::Service::Base::Context</a></span></tt>)</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
0.9.28.
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,274 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Class: Chat::Service::UpdateChannelStatus
&mdash; Documentation by YARD 0.9.28
</title>
<link rel="stylesheet" href="../../css/style.css" type="text/css" />
<link rel="stylesheet" href="../../css/common.css" type="text/css" />
<script type="text/javascript">
pathId = "Chat::Service::UpdateChannelStatus";
relpath = '../../';
</script>
<script type="text/javascript" charset="utf-8" src="../../js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="../../js/app.js"></script>
</head>
<body>
<div class="nav_wrap">
<iframe id="nav" src="../../class_list.html?1"></iframe>
<div id="resizer"></div>
</div>
<div id="main" tabindex="-1">
<div id="header">
<div id="menu">
<a href="../../_index.html">Index (U)</a> &raquo;
<span class='title'><span class='object_link'><a href="../../Chat.html" title="Chat (module)">Chat</a></span></span> &raquo; <span class='title'><span class='object_link'><a href="../Service.html" title="Chat::Service (module)">Service</a></span></span>
&raquo;
<span class="title">UpdateChannelStatus</span>
</div>
<div id="search">
<a class="full_list_link" id="class_list_link"
href="../../class_list.html">
<svg width="24" height="24">
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
</svg>
</a>
</div>
<div class="clear"></div>
</div>
<div id="content"><h1>Class: Chat::Service::UpdateChannelStatus
</h1>
<div class="box_info">
<dl>
<dt>Inherits:</dt>
<dd>
<span class="inheritName">Object</span>
<ul class="fullTree">
<li>Object</li>
<li class="next">Chat::Service::UpdateChannelStatus</li>
</ul>
<a href="#" class="inheritanceTree">show all</a>
</dd>
</dl>
<dl>
<dt>Includes:</dt>
<dd><span class='object_link'><a href="Base.html" title="Chat::Service::Base (module)">Base</a></span></dd>
</dl>
<dl>
<dt>Defined in:</dt>
<dd>plugins/chat/app/services/update_channel_status.rb</dd>
</dl>
</div>
<h2>Overview</h2><div class="docstring">
<div class="discussion">
<p>Service responsible for updating a chat channel status.</p>
</div>
</div>
<div class="tags">
<div class="examples">
<p class="tag_title">Examples:</p>
<pre class="example code"><code><span class='const'><span class='object_link'><a href="../../Chat.html" title="Chat (module)">Chat</a></span></span><span class='op'>::</span><span class='const'><span class='object_link'><a href="../Service.html" title="Chat::Service (module)">Service</a></span></span><span class='op'>::</span><span class='const'>UpdateChannelStatus</span><span class='period'>.</span><span class='id identifier rubyid_call'><span class='object_link'><a href="#call-instance_method" title="Chat::Service::UpdateChannelStatus#call (method)">call</a></span></span><span class='lparen'>(</span><span class='label'>channel_id:</span> <span class='int'>2</span><span class='comma'>,</span> <span class='label'>guardian:</span> <span class='id identifier rubyid_guardian'>guardian</span><span class='comma'>,</span> <span class='label'>status:</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>open</span><span class='tstring_end'>&quot;</span></span><span class='rparen'>)</span></code></pre>
</div>
</div>
<h2>
Instance Method Summary
<small><a href="#" class="summary_toggle">collapse</a></small>
</h2>
<ul class="summary">
<li class="public ">
<span class="summary_signature">
<a href="#call-instance_method" title="#call (instance method)">#<strong>call</strong>(channel_id: , guardian: , status: ) &#x21d2; Chat::Service::Base::Context </a>
</span>
<span class="summary_desc"><div class='inline'></div></span>
</li>
</ul>
<h3 class="inherited">Methods included from <span class='object_link'><a href="Base.html" title="Chat::Service::Base (module)">Base</a></span></h3>
<p class="inherited"><span class='object_link'><a href="Base.html#contract-class_method" title="Chat::Service::Base.contract (method)">contract</a></span>, <span class='object_link'><a href="Base.html#model-class_method" title="Chat::Service::Base.model (method)">model</a></span>, <span class='object_link'><a href="Base.html#policy-class_method" title="Chat::Service::Base.policy (method)">policy</a></span>, <span class='object_link'><a href="Base.html#step-class_method" title="Chat::Service::Base.step (method)">step</a></span>, <span class='object_link'><a href="Base.html#transaction-class_method" title="Chat::Service::Base.transaction (method)">transaction</a></span></p>
<div id="instance_method_details" class="method_details_list">
<h2>Instance Method Details</h2>
<div class="method_details first">
<h3 class="signature first" id="call-instance_method">
#<strong>call</strong>(channel_id: , guardian: , status: ) &#x21d2; <tt><span class='object_link'><a href="Base/Context.html" title="Chat::Service::Base::Context (class)">Chat::Service::Base::Context</a></span></tt>
</h3><div class="docstring">
<div class="discussion">
</div>
</div>
<div class="tags">
<p class="tag_title">Parameters:</p>
<ul class="param">
<li>
<span class='name'>channel_id</span>
<span class='type'>(<tt>Integer</tt>)</span>
<em class="default">(defaults to: <tt></tt>)</em>
</li>
<li>
<span class='name'>guardian</span>
<span class='type'>(<tt>Guardian</tt>)</span>
<em class="default">(defaults to: <tt></tt>)</em>
</li>
<li>
<span class='name'>status</span>
<span class='type'>(<tt>String</tt>)</span>
<em class="default">(defaults to: <tt></tt>)</em>
</li>
</ul>
<p class="tag_title">Returns:</p>
<ul class="return">
<li>
<span class='type'>(<tt><span class='object_link'><a href="Base/Context.html" title="Chat::Service::Base::Context (class)">Chat::Service::Base::Context</a></span></tt>)</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
0.9.28.
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,274 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Class: Chat::Service::UpdateUserLastRead
&mdash; Documentation by YARD 0.9.28
</title>
<link rel="stylesheet" href="../../css/style.css" type="text/css" />
<link rel="stylesheet" href="../../css/common.css" type="text/css" />
<script type="text/javascript">
pathId = "Chat::Service::UpdateUserLastRead";
relpath = '../../';
</script>
<script type="text/javascript" charset="utf-8" src="../../js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="../../js/app.js"></script>
</head>
<body>
<div class="nav_wrap">
<iframe id="nav" src="../../class_list.html?1"></iframe>
<div id="resizer"></div>
</div>
<div id="main" tabindex="-1">
<div id="header">
<div id="menu">
<a href="../../_index.html">Index (U)</a> &raquo;
<span class='title'><span class='object_link'><a href="../../Chat.html" title="Chat (module)">Chat</a></span></span> &raquo; <span class='title'><span class='object_link'><a href="../Service.html" title="Chat::Service (module)">Service</a></span></span>
&raquo;
<span class="title">UpdateUserLastRead</span>
</div>
<div id="search">
<a class="full_list_link" id="class_list_link"
href="../../class_list.html">
<svg width="24" height="24">
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
</svg>
</a>
</div>
<div class="clear"></div>
</div>
<div id="content"><h1>Class: Chat::Service::UpdateUserLastRead
</h1>
<div class="box_info">
<dl>
<dt>Inherits:</dt>
<dd>
<span class="inheritName">Object</span>
<ul class="fullTree">
<li>Object</li>
<li class="next">Chat::Service::UpdateUserLastRead</li>
</ul>
<a href="#" class="inheritanceTree">show all</a>
</dd>
</dl>
<dl>
<dt>Includes:</dt>
<dd><span class='object_link'><a href="Base.html" title="Chat::Service::Base (module)">Base</a></span></dd>
</dl>
<dl>
<dt>Defined in:</dt>
<dd>plugins/chat/app/services/update_user_last_read.rb</dd>
</dl>
</div>
<h2>Overview</h2><div class="docstring">
<div class="discussion">
<p>Service responsible for updating the last read message id of a membership.</p>
</div>
</div>
<div class="tags">
<div class="examples">
<p class="tag_title">Examples:</p>
<pre class="example code"><code><span class='const'><span class='object_link'><a href="../../Chat.html" title="Chat (module)">Chat</a></span></span><span class='op'>::</span><span class='const'><span class='object_link'><a href="../Service.html" title="Chat::Service (module)">Service</a></span></span><span class='op'>::</span><span class='const'>UpdateUserLastRead</span><span class='period'>.</span><span class='id identifier rubyid_call'><span class='object_link'><a href="#call-instance_method" title="Chat::Service::UpdateUserLastRead#call (method)">call</a></span></span><span class='lparen'>(</span><span class='label'>user_id:</span> <span class='int'>1</span><span class='comma'>,</span> <span class='label'>channel_id:</span> <span class='int'>2</span><span class='comma'>,</span> <span class='label'>guardian:</span> <span class='id identifier rubyid_guardian'>guardian</span><span class='rparen'>)</span></code></pre>
</div>
</div>
<h2>
Instance Method Summary
<small><a href="#" class="summary_toggle">collapse</a></small>
</h2>
<ul class="summary">
<li class="public ">
<span class="summary_signature">
<a href="#call-instance_method" title="#call (instance method)">#<strong>call</strong>(user_id: , channel_id: , guardian: ) &#x21d2; Chat::Service::Base::Context </a>
</span>
<span class="summary_desc"><div class='inline'></div></span>
</li>
</ul>
<h3 class="inherited">Methods included from <span class='object_link'><a href="Base.html" title="Chat::Service::Base (module)">Base</a></span></h3>
<p class="inherited"><span class='object_link'><a href="Base.html#contract-class_method" title="Chat::Service::Base.contract (method)">contract</a></span>, <span class='object_link'><a href="Base.html#model-class_method" title="Chat::Service::Base.model (method)">model</a></span>, <span class='object_link'><a href="Base.html#policy-class_method" title="Chat::Service::Base.policy (method)">policy</a></span>, <span class='object_link'><a href="Base.html#step-class_method" title="Chat::Service::Base.step (method)">step</a></span>, <span class='object_link'><a href="Base.html#transaction-class_method" title="Chat::Service::Base.transaction (method)">transaction</a></span></p>
<div id="instance_method_details" class="method_details_list">
<h2>Instance Method Details</h2>
<div class="method_details first">
<h3 class="signature first" id="call-instance_method">
#<strong>call</strong>(user_id: , channel_id: , guardian: ) &#x21d2; <tt><span class='object_link'><a href="Base/Context.html" title="Chat::Service::Base::Context (class)">Chat::Service::Base::Context</a></span></tt>
</h3><div class="docstring">
<div class="discussion">
</div>
</div>
<div class="tags">
<p class="tag_title">Parameters:</p>
<ul class="param">
<li>
<span class='name'>user_id</span>
<span class='type'>(<tt>Integer</tt>)</span>
<em class="default">(defaults to: <tt></tt>)</em>
</li>
<li>
<span class='name'>channel_id</span>
<span class='type'>(<tt>Integer</tt>)</span>
<em class="default">(defaults to: <tt></tt>)</em>
</li>
<li>
<span class='name'>guardian</span>
<span class='type'>(<tt>Guardian</tt>)</span>
<em class="default">(defaults to: <tt></tt>)</em>
</li>
</ul>
<p class="tag_title">Returns:</p>
<ul class="return">
<li>
<span class='type'>(<tt><span class='object_link'><a href="Base/Context.html" title="Chat::Service::Base::Context (class)">Chat::Service::Base::Context</a></span></tt>)</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
0.9.28.
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,204 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Documentation by YARD 0.9.28
</title>
<link rel="stylesheet" href="css/style.css" type="text/css" />
<link rel="stylesheet" href="css/common.css" type="text/css" />
<script type="text/javascript">
pathId = null;
relpath = '';
</script>
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="js/app.js"></script>
</head>
<body>
<div class="nav_wrap">
<iframe id="nav" src="class_list.html?1"></iframe>
<div id="resizer"></div>
</div>
<div id="main" tabindex="-1">
<div id="header">
<div id="menu">
</div>
<div id="search">
<a class="full_list_link" id="class_list_link"
href="class_list.html">
<svg width="24" height="24">
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
</svg>
</a>
</div>
<div class="clear"></div>
</div>
<div id="content"><h1 class="noborder title">Documentation by YARD 0.9.28</h1>
<div id="listing">
<h1 class="alphaindex">Alphabetic Index</h1>
<h2>File Listing</h2>
<ul id="files" class="index_inline_list">
<li class="r1"><a href="index.html" title="README">README</a></li>
</ul>
<div class="clear"></div>
<h2>Namespace Listing A-Z</h2>
<table>
<tr>
<td valign='top' width="33%">
<ul id="alpha_B" class="alpha">
<li class="letter">B</li>
<ul>
<li>
<span class='object_link'><a href="Chat/Service/Base.html" title="Chat::Service::Base (module)">Base</a></span>
<small>(Chat::Service)</small>
</li>
</ul>
</ul>
<ul id="alpha_C" class="alpha">
<li class="letter">C</li>
<ul>
<li>
<span class='object_link'><a href="Chat.html" title="Chat (module)">Chat</a></span>
</li>
<li>
<span class='object_link'><a href="Chat/Service/Base/Context.html" title="Chat::Service::Base::Context (class)">Context</a></span>
<small>(Chat::Service::Base)</small>
</li>
</ul>
</ul>
<ul id="alpha_F" class="alpha">
<li class="letter">F</li>
<ul>
<li>
<span class='object_link'><a href="Chat/Service/Base/Failure.html" title="Chat::Service::Base::Failure (class)">Failure</a></span>
<small>(Chat::Service::Base)</small>
</li>
</ul>
</ul>
<ul id="alpha_S" class="alpha">
<li class="letter">S</li>
<ul>
<li>
<span class='object_link'><a href="Chat/Service.html" title="Chat::Service (module)">Service</a></span>
<small>(Chat)</small>
</li>
</ul>
</ul>
<ul id="alpha_T" class="alpha">
<li class="letter">T</li>
<ul>
<li>
<span class='object_link'><a href="Chat/Service/TrashChannel.html" title="Chat::Service::TrashChannel (class)">TrashChannel</a></span>
<small>(Chat::Service)</small>
</li>
</ul>
</ul>
<ul id="alpha_U" class="alpha">
<li class="letter">U</li>
<ul>
<li>
<span class='object_link'><a href="Chat/Service/UpdateChannel.html" title="Chat::Service::UpdateChannel (class)">UpdateChannel</a></span>
<small>(Chat::Service)</small>
</li>
<li>
<span class='object_link'><a href="Chat/Service/UpdateChannelStatus.html" title="Chat::Service::UpdateChannelStatus (class)">UpdateChannelStatus</a></span>
<small>(Chat::Service)</small>
</li>
<li>
<span class='object_link'><a href="Chat/Service/UpdateUserLastRead.html" title="Chat::Service::UpdateUserLastRead (class)">UpdateUserLastRead</a></span>
<small>(Chat::Service)</small>
</li>
</ul>
</ul>
</td>
</tr>
</table>
</div>
</div>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
0.9.28.
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<link rel="stylesheet" href="css/full_list.css" type="text/css" media="screen" />
<link rel="stylesheet" href="css/common.css" type="text/css" media="screen" />
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="js/full_list.js"></script>
<title>Class List</title>
<base id="base_target" target="_parent" />
</head>
<body>
<div id="content">
<div class="fixed_header">
<h1 id="full_list_header">Class List</h1>
<div id="full_list_nav">
<span><a target="_self" href="class_list.html">
Classes
</a></span>
<span><a target="_self" href="method_list.html">
Methods
</a></span>
<span><a target="_self" href="file_list.html">
Files
</a></span>
</div>
<div id="search">Search: <input type="text" /></div>
</div>
<ul id="full_list" class="class">
<li id="object_" class="odd"><div class="item" style="padding-left:30px"><span class='object_link'><a href="top-level-namespace.html" title="Top Level Namespace (root)">Top Level Namespace</a></span></div></li>
<li id='object_Chat' class='even'><div class='item' style='padding-left:30px'><a class='toggle'></a> <span class='object_link'><a href="Chat.html" title="Chat (module)">Chat</a></span><small class='search_info'>Top Level Namespace</small></div><ul><li id='object_Chat::Service' class='collapsed odd'><div class='item' style='padding-left:45px'><a class='toggle'></a> <span class='object_link'><a href="Chat/Service.html" title="Chat::Service (module)">Service</a></span><small class='search_info'>Chat</small></div><ul><li id='object_Chat::Service::Base' class='collapsed'><div class='item' style='padding-left:60px'><a class='toggle'></a> <span class='object_link'><a href="Chat/Service/Base.html" title="Chat::Service::Base (module)">Base</a></span><small class='search_info'>Chat::Service</small></div><ul><li id='object_Chat::Service::Base::Context' class='collapsed'><div class='item' style='padding-left:75px'><span class='object_link'><a href="Chat/Service/Base/Context.html" title="Chat::Service::Base::Context (class)">Context</a></span> &lt; OpenStruct<small class='search_info'>Chat::Service::Base</small></div></li><li id='object_Chat::Service::Base::Failure' class='collapsed'><div class='item' style='padding-left:75px'><span class='object_link'><a href="Chat/Service/Base/Failure.html" title="Chat::Service::Base::Failure (class)">Failure</a></span> &lt; StandardError<small class='search_info'>Chat::Service::Base</small></div></li></ul></li><li id='object_Chat::Service::TrashChannel' class='collapsed'><div class='item' style='padding-left:60px'><span class='object_link'><a href="Chat/Service/TrashChannel.html" title="Chat::Service::TrashChannel (class)">TrashChannel</a></span> &lt; Object<small class='search_info'>Chat::Service</small></div></li><li id='object_Chat::Service::UpdateChannel' class='collapsed'><div class='item' style='padding-left:60px'><span class='object_link'><a href="Chat/Service/UpdateChannel.html" title="Chat::Service::UpdateChannel (class)">UpdateChannel</a></span> &lt; Object<small class='search_info'>Chat::Service</small></div></li><li id='object_Chat::Service::UpdateChannelStatus' class='collapsed'><div class='item' style='padding-left:60px'><span class='object_link'><a href="Chat/Service/UpdateChannelStatus.html" title="Chat::Service::UpdateChannelStatus (class)">UpdateChannelStatus</a></span> &lt; Object<small class='search_info'>Chat::Service</small></div></li><li id='object_Chat::Service::UpdateUserLastRead' class='collapsed'><div class='item' style='padding-left:60px'><span class='object_link'><a href="Chat/Service/UpdateUserLastRead.html" title="Chat::Service::UpdateUserLastRead (class)">UpdateUserLastRead</a></span> &lt; Object<small class='search_info'>Chat::Service</small></div></li></ul></li></ul></li>
</ul>
</div>
</body>
</html>

View File

@ -0,0 +1 @@
/* Override this file with custom rules */

View File

@ -0,0 +1,58 @@
body {
margin: 0;
font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif;
font-size: 13px;
height: 101%;
overflow-x: hidden;
background: #fafafa;
}
h1 { padding: 12px 10px; padding-bottom: 0; margin: 0; font-size: 1.4em; }
.clear { clear: both; }
.fixed_header { position: fixed; background: #fff; width: 100%; padding-bottom: 10px; margin-top: 0; top: 0; z-index: 9999; height: 70px; }
#search { position: absolute; right: 5px; top: 9px; padding-left: 24px; }
#content.insearch #search, #content.insearch #noresults { background: url(data:image/gif;base64,R0lGODlhEAAQAPYAAP///wAAAPr6+pKSkoiIiO7u7sjIyNjY2J6engAAAI6OjsbGxjIyMlJSUuzs7KamppSUlPLy8oKCghwcHLKysqSkpJqamvT09Pj4+KioqM7OzkRERAwMDGBgYN7e3ujo6Ly8vCoqKjY2NkZGRtTU1MTExDw8PE5OTj4+PkhISNDQ0MrKylpaWrS0tOrq6nBwcKysrLi4uLq6ul5eXlxcXGJiYoaGhuDg4H5+fvz8/KKiohgYGCwsLFZWVgQEBFBQUMzMzDg4OFhYWBoaGvDw8NbW1pycnOLi4ubm5kBAQKqqqiQkJCAgIK6urnJyckpKSjQ0NGpqatLS0sDAwCYmJnx8fEJCQlRUVAoKCggICLCwsOTk5ExMTPb29ra2tmZmZmhoaNzc3KCgoBISEiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCAAAACwAAAAAEAAQAAAHaIAAgoMgIiYlg4kACxIaACEJCSiKggYMCRselwkpghGJBJEcFgsjJyoAGBmfggcNEx0flBiKDhQFlIoCCA+5lAORFb4AJIihCRbDxQAFChAXw9HSqb60iREZ1omqrIPdJCTe0SWI09GBACH5BAkIAAAALAAAAAAQABAAAAdrgACCgwc0NTeDiYozCQkvOTo9GTmDKy8aFy+NOBA7CTswgywJDTIuEjYFIY0JNYMtKTEFiRU8Pjwygy4ws4owPyCKwsMAJSTEgiQlgsbIAMrO0dKDGMTViREZ14kYGRGK38nHguHEJcvTyIEAIfkECQgAAAAsAAAAABAAEAAAB2iAAIKDAggPg4iJAAMJCRUAJRIqiRGCBI0WQEEJJkWDERkYAAUKEBc4Po1GiKKJHkJDNEeKig4URLS0ICImJZAkuQAhjSi/wQyNKcGDCyMnk8u5rYrTgqDVghgZlYjcACTA1sslvtHRgQAh+QQJCAAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCQARAtOUoQRGRiFD0kJUYWZhUhKT1OLhR8wBaaFBzQ1NwAlkIszCQkvsbOHL7Y4q4IuEjaqq0ZQD5+GEEsJTDCMmIUhtgk1lo6QFUwJVDKLiYJNUd6/hoEAIfkECQgAAAAsAAAAABAAEAAAB2iAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4uen4ICCA+IkIsDCQkVACWmhwSpFqAABQoQF6ALTkWFnYMrVlhWvIKTlSAiJiVVPqlGhJkhqShHV1lCW4cMqSkAR1ofiwsjJyqGgQAh+QQJCAAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCSMhREZGIYYGY2ElYebi56fhyWQniSKAKKfpaCLFlAPhl0gXYNGEwkhGYREUywag1wJwSkHNDU3D0kJYIMZQwk8MjPBLx9eXwuETVEyAC/BOKsuEjYFhoEAIfkECQgAAAAsAAAAABAAEAAAB2eAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4ueICImip6CIQkJKJ4kigynKaqKCyMnKqSEK05StgAGQRxPYZaENqccFgIID4KXmQBhXFkzDgOnFYLNgltaSAAEpxa7BQoQF4aBACH5BAkIAAAALAAAAAAQABAAAAdogACCg4SFggJiPUqCJSWGgkZjCUwZACQkgxGEXAmdT4UYGZqCGWQ+IjKGGIUwPzGPhAc0NTewhDOdL7Ykji+dOLuOLhI2BbaFETICx4MlQitdqoUsCQ2vhKGjglNfU0SWmILaj43M5oEAOwAAAAAAAAAAAA==) no-repeat center left; }
#full_list { padding: 0; list-style: none; margin-left: 0; margin-top: 80px; font-size: 1.1em; }
#full_list ul { padding: 0; }
#full_list li { padding: 0; margin: 0; list-style: none; }
#full_list li .item { padding: 5px 5px 5px 12px; }
#noresults { padding: 7px 12px; background: #fff; }
#content.insearch #noresults { margin-left: 7px; }
li.collapsed ul { display: none; }
li a.toggle { cursor: default; position: relative; left: -5px; top: 4px; text-indent: -999px; width: 10px; height: 9px; margin-left: -10px; display: block; float: left; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK8AAACvABQqw0mAAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTM5jWRgMAAAAVdEVYdENyZWF0aW9uIFRpbWUAMy8xNC8wOeNZPpQAAAE2SURBVDiNrZTBccIwEEXfelIAHUA6CZ24BGaWO+FuzZAK4k6gg5QAdGAq+Bxs2Yqx7BzyL7Llp/VfzZeQhCTc/ezuGzKKnKSzpCxXJM8fwNXda3df5RZETlIt6YUzSQDs93sl8w3wBZxCCE10GM1OcWbWjB2mWgEH4Mfdyxm3PSepBHibgQE2wLe7r4HjEidpnXMYdQPKEMJcsZ4zs2POYQOcaPfwMVOo58zsAdMt18BuoVDPxUJRacELbXv3hUIX2vYmOUvi8C8ydz/ThjXrqKqqLbDIAdsCKBd+Wo7GWa7o9qzOQHVVVXeAbs+yHHCH4aTsaCOQqunmUy1yBUAXkdMIfMlgF5EXLo2OpV/c/Up7jG4hhHcYLgWzAZXUc2b2ixsfvc/RmNNfOXD3Q/oeL9axJE1yT9IOoUu6MGUkAAAAAElFTkSuQmCC) no-repeat bottom left; }
li.collapsed a.toggle { opacity: 0.5; cursor: default; background-position: top left; }
li { color: #888; cursor: pointer; }
li.deprecated { text-decoration: line-through; font-style: italic; }
li.odd { background: #f0f0f0; }
li.even { background: #fafafa; }
.item:hover { background: #ddd; }
li small:before { content: "("; }
li small:after { content: ")"; }
li small.search_info { display: none; }
a, a:visited { text-decoration: none; color: #05a; }
li.clicked > .item { background: #05a; color: #ccc; }
li.clicked > .item a, li.clicked > .item a:visited { color: #eee; }
li.clicked > .item a.toggle { opacity: 0.5; background-position: bottom right; }
li.collapsed.clicked a.toggle { background-position: top right; }
#search input { border: 1px solid #bbb; border-radius: 3px; }
#full_list_nav { margin-left: 10px; font-size: 0.9em; display: block; color: #aaa; }
#full_list_nav a, #nav a:visited { color: #358; }
#full_list_nav a:hover { background: transparent; color: #5af; }
#full_list_nav span:after { content: ' | '; }
#full_list_nav span:last-child:after { content: ''; }
#content h1 { margin-top: 0; }
li { white-space: nowrap; cursor: normal; }
li small { display: block; font-size: 0.8em; }
li small:before { content: ""; }
li small:after { content: ""; }
li small.search_info { display: none; }
#search { width: 170px; position: static; margin: 3px; margin-left: 10px; font-size: 0.9em; color: #888; padding-left: 0; padding-right: 24px; }
#content.insearch #search { background-position: center right; }
#search input { width: 110px; }
#full_list.insearch ul { display: block; }
#full_list.insearch .item { display: none; }
#full_list.insearch .found { display: block; padding-left: 11px !important; }
#full_list.insearch li a.toggle { display: none; }
#full_list.insearch li small.search_info { display: block; }

View File

@ -0,0 +1,497 @@
html {
width: 100%;
height: 100%;
}
body {
font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif;
font-size: 13px;
width: 100%;
margin: 0;
padding: 0;
display: flex;
display: -webkit-flex;
display: -ms-flexbox;
}
#nav {
position: relative;
width: 100%;
height: 100%;
border: 0;
border-right: 1px dotted #eee;
overflow: auto;
}
.nav_wrap {
margin: 0;
padding: 0;
width: 20%;
height: 100%;
position: relative;
display: flex;
display: -webkit-flex;
display: -ms-flexbox;
flex-shrink: 0;
-webkit-flex-shrink: 0;
-ms-flex: 1 0;
}
#resizer {
position: absolute;
right: -5px;
top: 0;
width: 10px;
height: 100%;
cursor: col-resize;
z-index: 9999;
}
#main {
flex: 5 1;
-webkit-flex: 5 1;
-ms-flex: 5 1;
outline: none;
position: relative;
background: #fff;
padding: 1.2em;
padding-top: 0.2em;
box-sizing: border-box;
}
@media (max-width: 920px) {
.nav_wrap { width: 100%; top: 0; right: 0; overflow: visible; position: absolute; }
#resizer { display: none; }
#nav {
z-index: 9999;
background: #fff;
display: none;
position: absolute;
top: 40px;
right: 12px;
width: 500px;
max-width: 80%;
height: 80%;
overflow-y: scroll;
border: 1px solid #999;
border-collapse: collapse;
box-shadow: -7px 5px 25px #aaa;
border-radius: 2px;
}
}
@media (min-width: 920px) {
body { height: 100%; overflow: hidden; }
#main { height: 100%; overflow: auto; }
#search { display: none; }
}
#main img { max-width: 100%; }
h1 { font-size: 25px; margin: 1em 0 0.5em; padding-top: 4px; border-top: 1px dotted #d5d5d5; }
h1.noborder { border-top: 0px; margin-top: 0; padding-top: 4px; }
h1.title { margin-bottom: 10px; }
h1.alphaindex { margin-top: 0; font-size: 22px; }
h2 {
padding: 0;
padding-bottom: 3px;
border-bottom: 1px #aaa solid;
font-size: 1.4em;
margin: 1.8em 0 0.5em;
position: relative;
}
h2 small { font-weight: normal; font-size: 0.7em; display: inline; position: absolute; right: 0; }
h2 small a {
display: block;
height: 20px;
border: 1px solid #aaa;
border-bottom: 0;
border-top-left-radius: 5px;
background: #f8f8f8;
position: relative;
padding: 2px 7px;
}
.clear { clear: both; }
.inline { display: inline; }
.inline p:first-child { display: inline; }
.docstring, .tags, #filecontents { font-size: 15px; line-height: 1.5145em; }
.docstring p > code, .docstring p > tt, .tags p > code, .tags p > tt {
color: #c7254e; background: #f9f2f4; padding: 2px 4px; font-size: 1em;
border-radius: 4px;
}
.docstring h1, .docstring h2, .docstring h3, .docstring h4 { padding: 0; border: 0; border-bottom: 1px dotted #bbb; }
.docstring h1 { font-size: 1.2em; }
.docstring h2 { font-size: 1.1em; }
.docstring h3, .docstring h4 { font-size: 1em; border-bottom: 0; padding-top: 10px; }
.summary_desc .object_link a, .docstring .object_link a {
font-family: monospace; font-size: 1.05em;
color: #05a; background: #EDF4FA; padding: 2px 4px; font-size: 1em;
border-radius: 4px;
}
.rdoc-term { padding-right: 25px; font-weight: bold; }
.rdoc-list p { margin: 0; padding: 0; margin-bottom: 4px; }
.summary_desc pre.code .object_link a, .docstring pre.code .object_link a {
padding: 0px; background: inherit; color: inherit; border-radius: inherit;
}
/* style for <table> */
#filecontents table, .docstring table { border-collapse: collapse; }
#filecontents table th, #filecontents table td,
.docstring table th, .docstring table td { border: 1px solid #ccc; padding: 8px; padding-right: 17px; }
#filecontents table tr:nth-child(odd),
.docstring table tr:nth-child(odd) { background: #eee; }
#filecontents table tr:nth-child(even),
.docstring table tr:nth-child(even) { background: #fff; }
#filecontents table th, .docstring table th { background: #fff; }
/* style for <ul> */
#filecontents li > p, .docstring li > p { margin: 0px; }
#filecontents ul, .docstring ul { padding-left: 20px; }
/* style for <dl> */
#filecontents dl, .docstring dl { border: 1px solid #ccc; }
#filecontents dt, .docstring dt { background: #ddd; font-weight: bold; padding: 3px 5px; }
#filecontents dd, .docstring dd { padding: 5px 0px; margin-left: 18px; }
#filecontents dd > p, .docstring dd > p { margin: 0px; }
.note {
color: #222;
margin: 20px 0;
padding: 10px;
border: 1px solid #eee;
border-radius: 3px;
display: block;
}
.docstring .note {
border-left-color: #ccc;
border-left-width: 5px;
}
.note.todo { background: #ffffc5; border-color: #ececaa; }
.note.returns_void { background: #efefef; }
.note.deprecated { background: #ffe5e5; border-color: #e9dada; }
.note.title.deprecated { background: #ffe5e5; border-color: #e9dada; }
.note.private { background: #ffffc5; border-color: #ececaa; }
.note.title { padding: 3px 6px; font-size: 0.9em; font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; display: inline; }
.summary_signature + .note.title { margin-left: 7px; }
h1 .note.title { font-size: 0.5em; font-weight: normal; padding: 3px 5px; position: relative; top: -3px; text-transform: capitalize; }
.note.title { background: #efefef; }
.note.title.constructor { color: #fff; background: #6a98d6; border-color: #6689d6; }
.note.title.writeonly { color: #fff; background: #45a638; border-color: #2da31d; }
.note.title.readonly { color: #fff; background: #6a98d6; border-color: #6689d6; }
.note.title.private { background: #d5d5d5; border-color: #c5c5c5; }
.note.title.not_defined_here { background: transparent; border: none; font-style: italic; }
.discussion .note { margin-top: 6px; }
.discussion .note:first-child { margin-top: 0; }
h3.inherited {
font-style: italic;
font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif;
font-weight: normal;
padding: 0;
margin: 0;
margin-top: 12px;
margin-bottom: 3px;
font-size: 13px;
}
p.inherited {
padding: 0;
margin: 0;
margin-left: 25px;
}
.box_info dl {
margin: 0;
border: 0;
width: 100%;
font-size: 1em;
display: flex;
display: -webkit-flex;
display: -ms-flexbox;
}
.box_info dl dt {
flex-shrink: 0;
-webkit-flex-shrink: 1;
-ms-flex-shrink: 1;
width: 100px;
text-align: right;
font-weight: bold;
border: 1px solid #aaa;
border-width: 1px 0px 0px 1px;
padding: 6px 0;
padding-right: 10px;
}
.box_info dl dd {
flex-grow: 1;
-webkit-flex-grow: 1;
-ms-flex: 1;
max-width: 420px;
padding: 6px 0;
padding-right: 20px;
border: 1px solid #aaa;
border-width: 1px 1px 0 0;
overflow: hidden;
position: relative;
}
.box_info dl:last-child > * {
border-bottom: 1px solid #aaa;
}
.box_info dl:nth-child(odd) > * { background: #eee; }
.box_info dl:nth-child(even) > * { background: #fff; }
.box_info dl > * { margin: 0; }
ul.toplevel { list-style: none; padding-left: 0; font-size: 1.1em; }
.index_inline_list { padding-left: 0; font-size: 1.1em; }
.index_inline_list li {
list-style: none;
display: inline-block;
padding: 0 12px;
line-height: 30px;
margin-bottom: 5px;
}
dl.constants { margin-left: 10px; }
dl.constants dt { font-weight: bold; font-size: 1.1em; margin-bottom: 5px; }
dl.constants.compact dt { display: inline-block; font-weight: normal }
dl.constants dd { width: 75%; white-space: pre; font-family: monospace; margin-bottom: 18px; }
dl.constants .docstring .note:first-child { margin-top: 5px; }
.summary_desc {
margin-left: 32px;
display: block;
font-family: sans-serif;
font-size: 1.1em;
margin-top: 8px;
line-height: 1.5145em;
margin-bottom: 0.8em;
}
.summary_desc tt { font-size: 0.9em; }
dl.constants .note { padding: 2px 6px; padding-right: 12px; margin-top: 6px; }
dl.constants .docstring { margin-left: 32px; font-size: 0.9em; font-weight: normal; }
dl.constants .tags { padding-left: 32px; font-size: 0.9em; line-height: 0.8em; }
dl.constants .discussion *:first-child { margin-top: 0; }
dl.constants .discussion *:last-child { margin-bottom: 0; }
.method_details { border-top: 1px dotted #ccc; margin-top: 25px; padding-top: 0; }
.method_details.first { border: 0; margin-top: 5px; }
.method_details.first h3.signature { margin-top: 1em; }
p.signature, h3.signature {
font-size: 1.1em; font-weight: normal; font-family: Monaco, Consolas, Courier, monospace;
padding: 6px 10px; margin-top: 1em;
background: #E8F4FF; border: 1px solid #d8d8e5; border-radius: 5px;
}
p.signature tt,
h3.signature tt { font-family: Monaco, Consolas, Courier, monospace; }
p.signature .overload,
h3.signature .overload { display: block; }
p.signature .extras,
h3.signature .extras { font-weight: normal; font-family: sans-serif; color: #444; font-size: 1em; }
p.signature .not_defined_here,
h3.signature .not_defined_here,
p.signature .aliases,
h3.signature .aliases { display: block; font-weight: normal; font-size: 0.9em; font-family: sans-serif; margin-top: 0px; color: #555; }
p.signature .aliases .names,
h3.signature .aliases .names { font-family: Monaco, Consolas, Courier, monospace; font-weight: bold; color: #000; font-size: 1.2em; }
.tags .tag_title { font-size: 1.05em; margin-bottom: 0; font-weight: bold; }
.tags .tag_title tt { color: initial; padding: initial; background: initial; }
.tags ul { margin-top: 5px; padding-left: 30px; list-style: square; }
.tags ul li { margin-bottom: 3px; }
.tags ul .name { font-family: monospace; font-weight: bold; }
.tags ul .note { padding: 3px 6px; }
.tags { margin-bottom: 12px; }
.tags .examples .tag_title { margin-bottom: 10px; font-weight: bold; }
.tags .examples .inline p { padding: 0; margin: 0; font-weight: bold; font-size: 1em; }
.tags .examples .inline p:before { content: "▸"; font-size: 1em; margin-right: 5px; }
.tags .overload .overload_item { list-style: none; margin-bottom: 25px; }
.tags .overload .overload_item .signature {
padding: 2px 8px;
background: #F1F8FF; border: 1px solid #d8d8e5; border-radius: 3px;
}
.tags .overload .signature { margin-left: -15px; font-family: monospace; display: block; font-size: 1.1em; }
.tags .overload .docstring { margin-top: 15px; }
.defines { display: none; }
#method_missing_details .notice.this { position: relative; top: -8px; color: #888; padding: 0; margin: 0; }
.showSource { font-size: 0.9em; }
.showSource a, .showSource a:visited { text-decoration: none; color: #666; }
#content a, #content a:visited { text-decoration: none; color: #05a; }
#content a:hover { background: #ffffa5; }
ul.summary {
list-style: none;
font-family: monospace;
font-size: 1em;
line-height: 1.5em;
padding-left: 0px;
}
ul.summary a, ul.summary a:visited {
text-decoration: none; font-size: 1.1em;
}
ul.summary li { margin-bottom: 5px; }
.summary_signature { padding: 4px 8px; background: #f8f8f8; border: 1px solid #f0f0f0; border-radius: 5px; }
.summary_signature:hover { background: #CFEBFF; border-color: #A4CCDA; cursor: pointer; }
.summary_signature.deprecated { background: #ffe5e5; border-color: #e9dada; }
ul.summary.compact li { display: inline-block; margin: 0px 5px 0px 0px; line-height: 2.6em;}
ul.summary.compact .summary_signature { padding: 5px 7px; padding-right: 4px; }
#content .summary_signature:hover a,
#content .summary_signature:hover a:visited {
background: transparent;
color: #049;
}
p.inherited a { font-family: monospace; font-size: 0.9em; }
p.inherited { word-spacing: 5px; font-size: 1.2em; }
p.children { font-size: 1.2em; }
p.children a { font-size: 0.9em; }
p.children strong { font-size: 0.8em; }
p.children strong.modules { padding-left: 5px; }
ul.fullTree { display: none; padding-left: 0; list-style: none; margin-left: 0; margin-bottom: 10px; }
ul.fullTree ul { margin-left: 0; padding-left: 0; list-style: none; }
ul.fullTree li { text-align: center; padding-top: 18px; padding-bottom: 12px; background: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAHtJREFUeNqMzrEJAkEURdGzuhgZbSoYWcAWoBVsB4JgZAGmphsZCZYzTQgWNCYrDN9RvMmHx+X916SUBFbo8CzD1idXrLErw1mQttgXtyrOcQ/Ny5p4Qh+2XqLYYazsPWNTiuMkRxa4vcV+evuNAUOLIx5+c2hyzv7hNQC67Q+/HHmlEwAAAABJRU5ErkJggg==) no-repeat top center; }
ul.fullTree li:first-child { padding-top: 0; background: transparent; }
ul.fullTree li:last-child { padding-bottom: 0; }
.showAll ul.fullTree { display: block; }
.showAll .inheritName { display: none; }
#search { position: absolute; right: 12px; top: 0px; z-index: 9000; }
#search a {
display: block; float: left;
padding: 4px 8px; text-decoration: none; color: #05a; fill: #05a;
border: 1px solid #d8d8e5;
border-bottom-left-radius: 3px; border-bottom-right-radius: 3px;
background: #F1F8FF;
box-shadow: -1px 1px 3px #ddd;
}
#search a:hover { background: #f5faff; color: #06b; fill: #06b; }
#search a.active {
background: #568; padding-bottom: 20px; color: #fff; fill: #fff;
border: 1px solid #457;
border-top-left-radius: 5px; border-top-right-radius: 5px;
}
#search a.inactive { color: #999; fill: #999; }
.inheritanceTree, .toggleDefines {
float: right;
border-left: 1px solid #aaa;
position: absolute; top: 0; right: 0;
height: 100%;
background: #f6f6f6;
padding: 5px;
min-width: 55px;
text-align: center;
}
#menu { font-size: 1.3em; color: #bbb; }
#menu .title, #menu a { font-size: 0.7em; }
#menu .title a { font-size: 1em; }
#menu .title { color: #555; }
#menu a, #menu a:visited { color: #333; text-decoration: none; border-bottom: 1px dotted #bbd; }
#menu a:hover { color: #05a; }
#footer { margin-top: 15px; border-top: 1px solid #ccc; text-align: center; padding: 7px 0; color: #999; }
#footer a, #footer a:visited { color: #444; text-decoration: none; border-bottom: 1px dotted #bbd; }
#footer a:hover { color: #05a; }
#listing ul.alpha { font-size: 1.1em; }
#listing ul.alpha { margin: 0; padding: 0; padding-bottom: 10px; list-style: none; }
#listing ul.alpha li.letter { font-size: 1.4em; padding-bottom: 10px; }
#listing ul.alpha ul { margin: 0; padding-left: 15px; }
#listing ul small { color: #666; font-size: 0.7em; }
li.r1 { background: #f0f0f0; }
li.r2 { background: #fafafa; }
#content ul.summary li.deprecated .summary_signature a,
#content ul.summary li.deprecated .summary_signature a:visited { text-decoration: line-through; font-style: italic; }
#toc {
position: relative;
float: right;
overflow-x: auto;
right: -3px;
margin-left: 20px;
margin-bottom: 20px;
padding: 20px; padding-right: 30px;
max-width: 300px;
z-index: 5000;
background: #fefefe;
border: 1px solid #ddd;
box-shadow: -2px 2px 6px #bbb;
}
#toc .title { margin: 0; }
#toc ol { padding-left: 1.8em; }
#toc li { font-size: 1.1em; line-height: 1.7em; }
#toc > ol > li { font-size: 1.1em; font-weight: bold; }
#toc ol > li > ol { font-size: 0.9em; }
#toc ol ol > li > ol { padding-left: 2.3em; }
#toc ol + li { margin-top: 0.3em; }
#toc.hidden { padding: 10px; background: #fefefe; box-shadow: none; }
#toc.hidden:hover { background: #fafafa; }
#filecontents h1 + #toc.nofloat { margin-top: 0; }
@media (max-width: 560px) {
#toc {
margin-left: 0;
margin-top: 16px;
float: none;
max-width: none;
}
}
/* syntax highlighting */
.source_code { display: none; padding: 3px 8px; border-left: 8px solid #ddd; margin-top: 5px; }
#filecontents pre.code, .docstring pre.code, .source_code pre { font-family: monospace; }
#filecontents pre.code, .docstring pre.code { display: block; }
.source_code .lines { padding-right: 12px; color: #555; text-align: right; }
#filecontents pre.code, .docstring pre.code,
.tags pre.example {
padding: 9px 14px;
margin-top: 4px;
border: 1px solid #e1e1e8;
background: #f7f7f9;
border-radius: 4px;
font-size: 1em;
overflow-x: auto;
line-height: 1.2em;
}
pre.code { color: #000; tab-size: 2; }
pre.code .info.file { color: #555; }
pre.code .val { color: #036A07; }
pre.code .tstring_content,
pre.code .heredoc_beg, pre.code .heredoc_end,
pre.code .qwords_beg, pre.code .qwords_end, pre.code .qwords_sep,
pre.code .words_beg, pre.code .words_end, pre.code .words_sep,
pre.code .qsymbols_beg, pre.code .qsymbols_end, pre.code .qsymbols_sep,
pre.code .symbols_beg, pre.code .symbols_end, pre.code .symbols_sep,
pre.code .tstring, pre.code .dstring { color: #036A07; }
pre.code .fid, pre.code .rubyid_new, pre.code .rubyid_to_s,
pre.code .rubyid_to_sym, pre.code .rubyid_to_f,
pre.code .dot + pre.code .id,
pre.code .rubyid_to_i pre.code .rubyid_each { color: #0085FF; }
pre.code .comment { color: #0066FF; }
pre.code .const, pre.code .constant { color: #585CF6; }
pre.code .label,
pre.code .symbol { color: #C5060B; }
pre.code .kw,
pre.code .rubyid_require,
pre.code .rubyid_extend,
pre.code .rubyid_include { color: #0000FF; }
pre.code .ivar { color: #318495; }
pre.code .gvar,
pre.code .rubyid_backref,
pre.code .rubyid_nth_ref { color: #6D79DE; }
pre.code .regexp, .dregexp { color: #036A07; }
pre.code a { border-bottom: 1px dotted #bbf; }
/* inline code */
*:not(pre) > code {
padding: 1px 3px 1px 3px;
border: 1px solid #E1E1E8;
background: #F7F7F9;
border-radius: 4px;
}
/* Color fix for links */
#content .summary_desc pre.code .id > .object_link a, /* identifier */
#content .docstring pre.code .id > .object_link a { color: #0085FF; }
#content .summary_desc pre.code .const > .object_link a, /* constant */
#content .docstring pre.code .const > .object_link a { color: #585CF6; }

View File

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
File: README
&mdash; Documentation by YARD 0.9.28
</title>
<link rel="stylesheet" href="css/style.css" type="text/css" />
<link rel="stylesheet" href="css/common.css" type="text/css" />
<script type="text/javascript">
pathId = "README";
relpath = '';
</script>
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="js/app.js"></script>
</head>
<body>
<div class="nav_wrap">
<iframe id="nav" src="file_list.html?1"></iframe>
<div id="resizer"></div>
</div>
<div id="main" tabindex="-1">
<div id="header">
<div id="menu">
<a href="_index.html">Index</a> &raquo;
<span class="title">File: README</span>
</div>
<div id="search">
<a class="full_list_link" id="class_list_link"
href="class_list.html">
<svg width="24" height="24">
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
</svg>
</a>
</div>
<div class="clear"></div>
</div>
<div id="content"><div id='filecontents'>
<p>This plugin is still in active development and may change frequently</p>
<h2 id="label-Documentation">Documentation</h2>
<p>The Discourse Chat plugin adds chat functionality to your Discourse so it can natively support both long-form and short-form communication needs of your online community.</p>
<p>For user documentation, see <a href="https://meta.discourse.org/t/discourse-chat/230881">Discourse Chat</a>.</p>
<p>For developer documentation, see <a href="https://discourse.github.io/discourse/">Discourse Documentation</a>.</p>
</div></div>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
0.9.28.
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<link rel="stylesheet" href="css/full_list.css" type="text/css" media="screen" />
<link rel="stylesheet" href="css/common.css" type="text/css" media="screen" />
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="js/full_list.js"></script>
<title>File List</title>
<base id="base_target" target="_parent" />
</head>
<body>
<div id="content">
<div class="fixed_header">
<h1 id="full_list_header">File List</h1>
<div id="full_list_nav">
<span><a target="_self" href="class_list.html">
Classes
</a></span>
<span><a target="_self" href="method_list.html">
Methods
</a></span>
<span><a target="_self" href="file_list.html">
Files
</a></span>
</div>
<div id="search">Search: <input type="text" /></div>
</div>
<ul id="full_list" class="file">
<li id="object_README" class="odd">
<div class="item"><span class="object_link"><a href="index.html" title="README">README</a></span></div>
</li>
</ul>
</div>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Documentation by YARD 0.9.28</title>
</head>
<script type="text/javascript">
var match = unescape(window.location.hash).match(/^#!(.+)/);
var name = match ? match[1] : 'index.html';
name = name.replace(/^(\w+):\/\//, '').replace(/^\/\//, '');
window.top.location = name;
</script>
<noscript>
<h1>Oops!</h1>
<h2>YARD requires JavaScript!</h2>
</noscript>
</html>

View File

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
File: README
&mdash; Documentation by YARD 0.9.28
</title>
<link rel="stylesheet" href="css/style.css" type="text/css" />
<link rel="stylesheet" href="css/common.css" type="text/css" />
<script type="text/javascript">
pathId = "README";
relpath = '';
</script>
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="js/app.js"></script>
</head>
<body>
<div class="nav_wrap">
<iframe id="nav" src="class_list.html?1"></iframe>
<div id="resizer"></div>
</div>
<div id="main" tabindex="-1">
<div id="header">
<div id="menu">
<a href="_index.html">Index</a> &raquo;
<span class="title">File: README</span>
</div>
<div id="search">
<a class="full_list_link" id="class_list_link"
href="class_list.html">
<svg width="24" height="24">
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
</svg>
</a>
</div>
<div class="clear"></div>
</div>
<div id="content"><div id='filecontents'>
<p>This plugin is still in active development and may change frequently</p>
<h2 id="label-Documentation">Documentation</h2>
<p>The Discourse Chat plugin adds chat functionality to your Discourse so it can natively support both long-form and short-form communication needs of your online community.</p>
<p>For user documentation, see <a href="https://meta.discourse.org/t/discourse-chat/230881">Discourse Chat</a>.</p>
<p>For developer documentation, see <a href="https://discourse.github.io/discourse/">Discourse Documentation</a>.</p>
</div></div>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
0.9.28.
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,314 @@
(function() {
var localStorage = {}, sessionStorage = {};
try { localStorage = window.localStorage; } catch (e) { }
try { sessionStorage = window.sessionStorage; } catch (e) { }
function createSourceLinks() {
$('.method_details_list .source_code').
before("<span class='showSource'>[<a href='#' class='toggleSource'>View source</a>]</span>");
$('.toggleSource').toggle(function() {
$(this).parent().nextAll('.source_code').slideDown(100);
$(this).text("Hide source");
},
function() {
$(this).parent().nextAll('.source_code').slideUp(100);
$(this).text("View source");
});
}
function createDefineLinks() {
var tHeight = 0;
$('.defines').after(" <a href='#' class='toggleDefines'>more...</a>");
$('.toggleDefines').toggle(function() {
tHeight = $(this).parent().prev().height();
$(this).prev().css('display', 'inline');
$(this).parent().prev().height($(this).parent().height());
$(this).text("(less)");
},
function() {
$(this).prev().hide();
$(this).parent().prev().height(tHeight);
$(this).text("more...");
});
}
function createFullTreeLinks() {
var tHeight = 0;
$('.inheritanceTree').toggle(function() {
tHeight = $(this).parent().prev().height();
$(this).parent().toggleClass('showAll');
$(this).text("(hide)");
$(this).parent().prev().height($(this).parent().height());
},
function() {
$(this).parent().toggleClass('showAll');
$(this).parent().prev().height(tHeight);
$(this).text("show all");
});
}
function searchFrameButtons() {
$('.full_list_link').click(function() {
toggleSearchFrame(this, $(this).attr('href'));
return false;
});
window.addEventListener('message', function(e) {
if (e.data === 'navEscape') {
$('#nav').slideUp(100);
$('#search a').removeClass('active inactive');
$(window).focus();
}
});
$(window).resize(function() {
if ($('#search:visible').length === 0) {
$('#nav').removeAttr('style');
$('#search a').removeClass('active inactive');
$(window).focus();
}
});
}
function toggleSearchFrame(id, link) {
var frame = $('#nav');
$('#search a').removeClass('active').addClass('inactive');
if (frame.attr('src') === link && frame.css('display') !== "none") {
frame.slideUp(100);
$('#search a').removeClass('active inactive');
}
else {
$(id).addClass('active').removeClass('inactive');
if (frame.attr('src') !== link) frame.attr('src', link);
frame.slideDown(100);
}
}
function linkSummaries() {
$('.summary_signature').click(function() {
document.location = $(this).find('a').attr('href');
});
}
function summaryToggle() {
$('.summary_toggle').click(function(e) {
e.preventDefault();
localStorage.summaryCollapsed = $(this).text();
$('.summary_toggle').each(function() {
$(this).text($(this).text() == "collapse" ? "expand" : "collapse");
var next = $(this).parent().parent().nextAll('ul.summary').first();
if (next.hasClass('compact')) {
next.toggle();
next.nextAll('ul.summary').first().toggle();
}
else if (next.hasClass('summary')) {
var list = $('<ul class="summary compact" />');
list.html(next.html());
list.find('.summary_desc, .note').remove();
list.find('a').each(function() {
$(this).html($(this).find('strong').html());
$(this).parent().html($(this)[0].outerHTML);
});
next.before(list);
next.toggle();
}
});
return false;
});
if (localStorage.summaryCollapsed == "collapse") {
$('.summary_toggle').first().click();
} else { localStorage.summaryCollapsed = "expand"; }
}
function constantSummaryToggle() {
$('.constants_summary_toggle').click(function(e) {
e.preventDefault();
localStorage.summaryCollapsed = $(this).text();
$('.constants_summary_toggle').each(function() {
$(this).text($(this).text() == "collapse" ? "expand" : "collapse");
var next = $(this).parent().parent().nextAll('dl.constants').first();
if (next.hasClass('compact')) {
next.toggle();
next.nextAll('dl.constants').first().toggle();
}
else if (next.hasClass('constants')) {
var list = $('<dl class="constants compact" />');
list.html(next.html());
list.find('dt').each(function() {
$(this).addClass('summary_signature');
$(this).text( $(this).text().split('=')[0]);
if ($(this).has(".deprecated").length) {
$(this).addClass('deprecated');
};
});
// Add the value of the constant as "Tooltip" to the summary object
list.find('pre.code').each(function() {
console.log($(this).parent());
var dt_element = $(this).parent().prev();
var tooltip = $(this).text();
if (dt_element.hasClass("deprecated")) {
tooltip = 'Deprecated. ' + tooltip;
};
dt_element.attr('title', tooltip);
});
list.find('.docstring, .tags, dd').remove();
next.before(list);
next.toggle();
}
});
return false;
});
if (localStorage.summaryCollapsed == "collapse") {
$('.constants_summary_toggle').first().click();
} else { localStorage.summaryCollapsed = "expand"; }
}
function generateTOC() {
if ($('#filecontents').length === 0) return;
var _toc = $('<ol class="top"></ol>');
var show = false;
var toc = _toc;
var counter = 0;
var tags = ['h2', 'h3', 'h4', 'h5', 'h6'];
var i;
var curli;
if ($('#filecontents h1').length > 1) tags.unshift('h1');
for (i = 0; i < tags.length; i++) { tags[i] = '#filecontents ' + tags[i]; }
var lastTag = parseInt(tags[0][1], 10);
$(tags.join(', ')).each(function() {
if ($(this).parents('.method_details .docstring').length != 0) return;
if (this.id == "filecontents") return;
show = true;
var thisTag = parseInt(this.tagName[1], 10);
if (this.id.length === 0) {
var proposedId = $(this).attr('toc-id');
if (typeof(proposedId) != "undefined") this.id = proposedId;
else {
var proposedId = $(this).text().replace(/[^a-z0-9-]/ig, '_');
if ($('#' + proposedId).length > 0) { proposedId += counter; counter++; }
this.id = proposedId;
}
}
if (thisTag > lastTag) {
for (i = 0; i < thisTag - lastTag; i++) {
if ( typeof(curli) == "undefined" ) {
curli = $('<li/>');
toc.append(curli);
}
toc = $('<ol/>');
curli.append(toc);
curli = undefined;
}
}
if (thisTag < lastTag) {
for (i = 0; i < lastTag - thisTag; i++) {
toc = toc.parent();
toc = toc.parent();
}
}
var title = $(this).attr('toc-title');
if (typeof(title) == "undefined") title = $(this).text();
curli =$('<li><a href="#' + this.id + '">' + title + '</a></li>');
toc.append(curli);
lastTag = thisTag;
});
if (!show) return;
html = '<div id="toc"><p class="title hide_toc"><a href="#"><strong>Table of Contents</strong></a></p></div>';
$('#content').prepend(html);
$('#toc').append(_toc);
$('#toc .hide_toc').toggle(function() {
$('#toc .top').slideUp('fast');
$('#toc').toggleClass('hidden');
$('#toc .title small').toggle();
}, function() {
$('#toc .top').slideDown('fast');
$('#toc').toggleClass('hidden');
$('#toc .title small').toggle();
});
}
function navResizeFn(e) {
if (e.which !== 1) {
navResizeFnStop();
return;
}
sessionStorage.navWidth = e.pageX.toString();
$('.nav_wrap').css('width', e.pageX);
$('.nav_wrap').css('-ms-flex', 'inherit');
}
function navResizeFnStop() {
$(window).unbind('mousemove', navResizeFn);
window.removeEventListener('message', navMessageFn, false);
}
function navMessageFn(e) {
if (e.data.action === 'mousemove') navResizeFn(e.data.event);
if (e.data.action === 'mouseup') navResizeFnStop();
}
function navResizer() {
$('#resizer').mousedown(function(e) {
e.preventDefault();
$(window).mousemove(navResizeFn);
window.addEventListener('message', navMessageFn, false);
});
$(window).mouseup(navResizeFnStop);
if (sessionStorage.navWidth) {
navResizeFn({which: 1, pageX: parseInt(sessionStorage.navWidth, 10)});
}
}
function navExpander() {
var done = false, timer = setTimeout(postMessage, 500);
function postMessage() {
if (done) return;
clearTimeout(timer);
var opts = { action: 'expand', path: pathId };
document.getElementById('nav').contentWindow.postMessage(opts, '*');
done = true;
}
window.addEventListener('message', function(event) {
if (event.data === 'navReady') postMessage();
return false;
}, false);
}
function mainFocus() {
var hash = window.location.hash;
if (hash !== '' && $(hash)[0]) {
$(hash)[0].scrollIntoView();
}
setTimeout(function() { $('#main').focus(); }, 10);
}
function navigationChange() {
// This works around the broken anchor navigation with the YARD template.
window.onpopstate = function() {
var hash = window.location.hash;
if (hash !== '' && $(hash)[0]) {
$(hash)[0].scrollIntoView();
}
};
}
$(document).ready(function() {
navResizer();
navExpander();
createSourceLinks();
createDefineLinks();
createFullTreeLinks();
searchFrameButtons();
linkSummaries();
summaryToggle();
constantSummaryToggle();
generateTOC();
mainFocus();
navigationChange();
});
})();

View File

@ -0,0 +1,216 @@
(function() {
var $clicked = $(null);
var searchTimeout = null;
var searchCache = [];
var caseSensitiveMatch = false;
var ignoreKeyCodeMin = 8;
var ignoreKeyCodeMax = 46;
var commandKey = 91;
RegExp.escape = function(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
function escapeShortcut() {
$(document).keydown(function(evt) {
if (evt.which == 27) {
window.parent.postMessage('navEscape', '*');
}
});
}
function navResizer() {
$(window).mousemove(function(e) {
window.parent.postMessage({
action: 'mousemove', event: {pageX: e.pageX, which: e.which}
}, '*');
}).mouseup(function(e) {
window.parent.postMessage({action: 'mouseup'}, '*');
});
window.parent.postMessage("navReady", "*");
}
function clearSearchTimeout() {
clearTimeout(searchTimeout);
searchTimeout = null;
}
function enableLinks() {
// load the target page in the parent window
$('#full_list li').on('click', function(evt) {
$('#full_list li').removeClass('clicked');
$clicked = $(this);
$clicked.addClass('clicked');
evt.stopPropagation();
if (evt.target.tagName === 'A') return true;
var elem = $clicked.find('> .item .object_link a')[0];
var e = evt.originalEvent;
var newEvent = new MouseEvent(evt.originalEvent.type);
newEvent.initMouseEvent(e.type, e.canBubble, e.cancelable, e.view, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget);
elem.dispatchEvent(newEvent);
evt.preventDefault();
return false;
});
}
function enableToggles() {
// show/hide nested classes on toggle click
$('#full_list a.toggle').on('click', function(evt) {
evt.stopPropagation();
evt.preventDefault();
$(this).parent().parent().toggleClass('collapsed');
highlight();
});
}
function populateSearchCache() {
$('#full_list li .item').each(function() {
var $node = $(this);
var $link = $node.find('.object_link a');
if ($link.length > 0) {
searchCache.push({
node: $node,
link: $link,
name: $link.text(),
fullName: $link.attr('title').split(' ')[0]
});
}
});
}
function enableSearch() {
$('#search input').keyup(function(event) {
if (ignoredKeyPress(event)) return;
if (this.value === "") {
clearSearch();
} else {
performSearch(this.value);
}
});
$('#full_list').after("<div id='noresults' style='display:none'></div>");
}
function ignoredKeyPress(event) {
if (
(event.keyCode > ignoreKeyCodeMin && event.keyCode < ignoreKeyCodeMax) ||
(event.keyCode == commandKey)
) {
return true;
} else {
return false;
}
}
function clearSearch() {
clearSearchTimeout();
$('#full_list .found').removeClass('found').each(function() {
var $link = $(this).find('.object_link a');
$link.text($link.text());
});
$('#full_list, #content').removeClass('insearch');
$clicked.parents().removeClass('collapsed');
highlight();
}
function performSearch(searchString) {
clearSearchTimeout();
$('#full_list, #content').addClass('insearch');
$('#noresults').text('').hide();
partialSearch(searchString, 0);
}
function partialSearch(searchString, offset) {
var lastRowClass = '';
var i = null;
for (i = offset; i < Math.min(offset + 50, searchCache.length); i++) {
var item = searchCache[i];
var searchName = (searchString.indexOf('::') != -1 ? item.fullName : item.name);
var matchString = buildMatchString(searchString);
var matchRegexp = new RegExp(matchString, caseSensitiveMatch ? "" : "i");
if (searchName.match(matchRegexp) == null) {
item.node.removeClass('found');
item.link.text(item.link.text());
}
else {
item.node.addClass('found');
item.node.removeClass(lastRowClass).addClass(lastRowClass == 'r1' ? 'r2' : 'r1');
lastRowClass = item.node.hasClass('r1') ? 'r1' : 'r2';
item.link.html(item.name.replace(matchRegexp, "<strong>$&</strong>"));
}
}
if(i == searchCache.length) {
searchDone();
} else {
searchTimeout = setTimeout(function() {
partialSearch(searchString, i);
}, 0);
}
}
function searchDone() {
searchTimeout = null;
highlight();
if ($('#full_list li:visible').size() === 0) {
$('#noresults').text('No results were found.').hide().fadeIn();
} else {
$('#noresults').text('').hide();
}
$('#content').removeClass('insearch');
}
function buildMatchString(searchString, event) {
caseSensitiveMatch = searchString.match(/[A-Z]/) != null;
var regexSearchString = RegExp.escape(searchString);
if (caseSensitiveMatch) {
regexSearchString += "|" +
$.map(searchString.split(''), function(e) { return RegExp.escape(e); }).
join('.+?');
}
return regexSearchString;
}
function highlight() {
$('#full_list li:visible').each(function(n) {
$(this).removeClass('even odd').addClass(n % 2 == 0 ? 'odd' : 'even');
});
}
/**
* Expands the tree to the target element and its immediate
* children.
*/
function expandTo(path) {
var $target = $(document.getElementById('object_' + path));
$target.addClass('clicked');
$target.removeClass('collapsed');
$target.parentsUntil('#full_list', 'li').removeClass('collapsed');
if($target[0]) {
window.scrollTo(window.scrollX, $target.offset().top - 250);
highlight();
}
}
function windowEvents(event) {
var msg = event.data;
if (msg.action === "expand") {
expandTo(msg.path);
}
return false;
}
window.addEventListener("message", windowEvents, false);
$(document).ready(function() {
escapeShortcut();
navResizer();
enableLinks();
enableToggles();
populateSearchCache();
enableSearch();
});
})();

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,163 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<link rel="stylesheet" href="css/full_list.css" type="text/css" media="screen" />
<link rel="stylesheet" href="css/common.css" type="text/css" media="screen" />
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="js/full_list.js"></script>
<title>Method List</title>
<base id="base_target" target="_parent" />
</head>
<body>
<div id="content">
<div class="fixed_header">
<h1 id="full_list_header">Method List</h1>
<div id="full_list_nav">
<span><a target="_self" href="class_list.html">
Classes
</a></span>
<span><a target="_self" href="method_list.html">
Methods
</a></span>
<span><a target="_self" href="file_list.html">
Files
</a></span>
</div>
<div id="search">Search: <input type="text" /></div>
</div>
<ul id="full_list" class="method">
<li class="odd ">
<div class="item">
<span class='object_link'><a href="Chat/Service/Base.html#contract-class_method" title="Chat::Service::Base.contract (method)">contract</a></span>
<small>Chat::Service::Base</small>
</div>
</li>
<li class="even ">
<div class="item">
<span class='object_link'><a href="Chat/Service/Base.html#model-class_method" title="Chat::Service::Base.model (method)">model</a></span>
<small>Chat::Service::Base</small>
</div>
</li>
<li class="odd ">
<div class="item">
<span class='object_link'><a href="Chat/Service/Base.html#policy-class_method" title="Chat::Service::Base.policy (method)">policy</a></span>
<small>Chat::Service::Base</small>
</div>
</li>
<li class="even ">
<div class="item">
<span class='object_link'><a href="Chat/Service/Base.html#step-class_method" title="Chat::Service::Base.step (method)">step</a></span>
<small>Chat::Service::Base</small>
</div>
</li>
<li class="odd ">
<div class="item">
<span class='object_link'><a href="Chat/Service/Base.html#transaction-class_method" title="Chat::Service::Base.transaction (method)">transaction</a></span>
<small>Chat::Service::Base</small>
</div>
</li>
<li class="even ">
<div class="item">
<span class='object_link'><a href="Chat/Service/Base/Context.html#fail-instance_method" title="Chat::Service::Base::Context#fail (method)">#fail</a></span>
<small>Chat::Service::Base::Context</small>
</div>
</li>
<li class="odd ">
<div class="item">
<span class='object_link'><a href="Chat/Service/Base/Context.html#fail!-instance_method" title="Chat::Service::Base::Context#fail! (method)">#fail!</a></span>
<small>Chat::Service::Base::Context</small>
</div>
</li>
<li class="even ">
<div class="item">
<span class='object_link'><a href="Chat/Service/Base/Context.html#failure%3F-instance_method" title="Chat::Service::Base::Context#failure? (method)">#failure?</a></span>
<small>Chat::Service::Base::Context</small>
</div>
</li>
<li class="odd ">
<div class="item">
<span class='object_link'><a href="Chat/Service/Base/Context.html#success%3F-instance_method" title="Chat::Service::Base::Context#success? (method)">#success?</a></span>
<small>Chat::Service::Base::Context</small>
</div>
</li>
<li class="even ">
<div class="item">
<span class='object_link'><a href="Chat/Service/Base/Failure.html#context-instance_method" title="Chat::Service::Base::Failure#context (method)">#context</a></span>
<small>Chat::Service::Base::Failure</small>
</div>
</li>
<li class="odd ">
<div class="item">
<span class='object_link'><a href="Chat/Service/TrashChannel.html#call-instance_method" title="Chat::Service::TrashChannel#call (method)">#call</a></span>
<small>Chat::Service::TrashChannel</small>
</div>
</li>
<li class="even ">
<div class="item">
<span class='object_link'><a href="Chat/Service/UpdateChannel.html#call-instance_method" title="Chat::Service::UpdateChannel#call (method)">#call</a></span>
<small>Chat::Service::UpdateChannel</small>
</div>
</li>
<li class="odd ">
<div class="item">
<span class='object_link'><a href="Chat/Service/UpdateChannelStatus.html#call-instance_method" title="Chat::Service::UpdateChannelStatus#call (method)">#call</a></span>
<small>Chat::Service::UpdateChannelStatus</small>
</div>
</li>
<li class="even ">
<div class="item">
<span class='object_link'><a href="Chat/Service/UpdateUserLastRead.html#call-instance_method" title="Chat::Service::UpdateUserLastRead#call (method)">#call</a></span>
<small>Chat::Service::UpdateUserLastRead</small>
</div>
</li>
</ul>
</div>
</body>
</html>

View File

@ -0,0 +1,111 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Top Level Namespace
&mdash; Documentation by YARD 0.9.28
</title>
<link rel="stylesheet" href="css/style.css" type="text/css" />
<link rel="stylesheet" href="css/common.css" type="text/css" />
<script type="text/javascript">
pathId = "";
relpath = '';
</script>
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
<script type="text/javascript" charset="utf-8" src="js/app.js"></script>
</head>
<body>
<div class="nav_wrap">
<iframe id="nav" src="class_list.html?1"></iframe>
<div id="resizer"></div>
</div>
<div id="main" tabindex="-1">
<div id="header">
<div id="menu">
<a href="_index.html">Index</a> &raquo;
<span class="title">Top Level Namespace</span>
</div>
<div id="search">
<a class="full_list_link" id="class_list_link"
href="class_list.html">
<svg width="24" height="24">
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
</svg>
</a>
</div>
<div class="clear"></div>
</div>
<div id="content"><h1>Top Level Namespace
</h1>
<div class="box_info">
</div>
<h2>Defined Under Namespace</h2>
<p class="children">
<strong class="modules">Modules:</strong> <span class='object_link'><a href="Chat.html" title="Chat (module)">Chat</a></span>
</p>
</div>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
0.9.28.
</div>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,372 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Discourse: Global</title>
<link type="text/css" rel="stylesheet" href="styles/vendor/prism-custom.css">
<link type="text/css" rel="stylesheet" href="styles/styles.css">
</head>
<body>
<header class="layout-header">
<h1>
<a href="./index.html">
Discourse
</a>
</h1>
<nav class="layout-nav">
<ul><li class="nav-heading">Classes</li><li class="nav-heading"><span class="nav-item-type type-class" title="class">C</span><span class="nav-item-name is-class"><a href="PluginApi.html">PluginApi</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="PluginApi.html#decorateChatMessage">decorateChatMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="PluginApi.html#registerChatComposerButton">registerChatComposerButton</a></span></li><li class="nav-heading"><span class="nav-item-type type-class" title="class">C</span><span class="nav-item-name is-class"><a href="module.exports.html">exports</a></span></li></ul><ul><li class="nav-heading">Modules</li><li class="nav-heading"><span class="nav-item-type type-module" title="module">M</span><span class="nav-item-name is-module"><a href="module-ChatApi.html">ChatApi</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#categoryPermissions">categoryPermissions</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#channel">channel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#channels">channels</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#createChannel">createChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#createChannelArchive">createChannelArchive</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#destroyChannel">destroyChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#followChannel">followChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#listChannelMemberships">listChannelMemberships</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#listCurrentUserChannels">listCurrentUserChannels</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#moveChannelMessages">moveChannelMessages</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#sendMessage">sendMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#unfollowChannel">unfollowChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateChannel">updateChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateChannelStatus">updateChannelStatus</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateCurrentUserChannelNotificationsSettings">updateCurrentUserChannelNotificationsSettings</a></span></li></ul><li class="nav-heading"><a href="global.html">Globals</a></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="global.html#load">load</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="global.html#loadMore">loadMore</a></span></li>
</nav>
</header>
<main class="layout-main ">
<div class="container">
<p class="page-kind"></p>
<h1 class="page-title">Global</h1>
<section>
<header class="not-class">
<!-- <h2></h2> -->
</header>
<article>
<div class="container-overview">
<div class="details">
</div>
</div>
<h3 class="subtitle">Methods</h3>
<article class="method">
<div class="method-type">
</div>
<h4 class="method-name" id="load">load<span class="signature">()</span><span class="return-type-signature"> &rarr; {Promise}</span>
</h4>
<div class="method-description">
Loads first batch of results
</div>
<div class="details">
</div>
<h4 class="method-heading">Returns</h4>
<ul>
<li class="method-returns">
<code>Promise</code>
</li>
</ul>
<h4 class="method-heading">Source</h4>
<ul>
<li class="method-source">
<a href="lib_collection.js.html">lib/collection.js</a><a href="lib_collection.js.html#source.51">, line 51</a>
</li>
</ul>
</article>
<article class="method">
<div class="method-type">
</div>
<h4 class="method-name" id="loadMore">loadMore<span class="signature">()</span><span class="return-type-signature"> &rarr; {Promise}</span>
</h4>
<div class="method-description">
Attempts to load more results
</div>
<div class="details">
</div>
<h4 class="method-heading">Returns</h4>
<ul>
<li class="method-returns">
<code>Promise</code>
</li>
</ul>
<h4 class="method-heading">Source</h4>
<ul>
<li class="method-source">
<a href="lib_collection.js.html">lib/collection.js</a><a href="lib_collection.js.html#source.81">, line 81</a>
</li>
</ul>
</article>
</article>
</section>
</div>
</main>
<footer class="layout-footer">
<div class="container">
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.0</a>
</div>
</footer>
<script src="scripts/prism.dev.js"></script>
</body>
</html>

View File

@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Discourse: </title>
<link type="text/css" rel="stylesheet" href="styles/vendor/prism-custom.css">
<link type="text/css" rel="stylesheet" href="styles/styles.css">
</head>
<body>
<header class="layout-header">
<h1>
<a href="./index.html">
Discourse
</a>
</h1>
<nav class="layout-nav">
<ul><li class="nav-heading">Classes</li><li class="nav-heading"><span class="nav-item-type type-class" title="class">C</span><span class="nav-item-name is-class"><a href="PluginApi.html">PluginApi</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="PluginApi.html#decorateChatMessage">decorateChatMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="PluginApi.html#registerChatComposerButton">registerChatComposerButton</a></span></li><li class="nav-heading"><span class="nav-item-type type-class" title="class">C</span><span class="nav-item-name is-class"><a href="module.exports.html">exports</a></span></li></ul><ul><li class="nav-heading">Modules</li><li class="nav-heading"><span class="nav-item-type type-module" title="module">M</span><span class="nav-item-name is-module"><a href="module-ChatApi.html">ChatApi</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#categoryPermissions">categoryPermissions</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#channel">channel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#channels">channels</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#createChannel">createChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#createChannelArchive">createChannelArchive</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#destroyChannel">destroyChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#followChannel">followChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#listChannelMemberships">listChannelMemberships</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#listCurrentUserChannels">listCurrentUserChannels</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#moveChannelMessages">moveChannelMessages</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#sendMessage">sendMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#unfollowChannel">unfollowChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateChannel">updateChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateChannelStatus">updateChannelStatus</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateCurrentUserChannelNotificationsSettings">updateCurrentUserChannelNotificationsSettings</a></span></li></ul><li class="nav-heading"><a href="global.html">Globals</a></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="global.html#load">load</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="global.html#loadMore">loadMore</a></span></li>
</nav>
</header>
<main class="layout-main ">
<div class="container">
<p class="page-kind"></p>
<h1 class="page-title"></h1>
<h3> </h3>
<section class="readme">
<article><p>This plugin is still in active development and may change frequently</p>
<h2>Documentation</h2>
<p>The Discourse Chat plugin adds chat functionality to your Discourse so it can natively support both long-form and short-form communication needs of your online community.</p>
<p>For user documentation, see <a href="https://meta.discourse.org/t/discourse-chat/230881">Discourse Chat</a>.</p>
<p>For developer documentation, see <a href="https://discourse.github.io/discourse/">Discourse Documentation</a>.</p></article>
</section>
</div>
</main>
<footer class="layout-footer">
<div class="container">
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.0</a>
</div>
</footer>
<script src="scripts/prism.dev.js"></script>
</body>
</html>

View File

@ -0,0 +1,178 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Discourse: lib/collection.js</title>
<link type="text/css" rel="stylesheet" href="styles/vendor/prism-custom.css">
<link type="text/css" rel="stylesheet" href="styles/styles.css">
</head>
<body>
<header class="layout-header">
<h1>
<a href="./index.html">
Discourse
</a>
</h1>
<nav class="layout-nav">
<ul><li class="nav-heading">Classes</li><li class="nav-heading"><span class="nav-item-type type-class" title="class">C</span><span class="nav-item-name is-class"><a href="PluginApi.html">PluginApi</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="PluginApi.html#decorateChatMessage">decorateChatMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="PluginApi.html#registerChatComposerButton">registerChatComposerButton</a></span></li><li class="nav-heading"><span class="nav-item-type type-class" title="class">C</span><span class="nav-item-name is-class"><a href="module.exports.html">exports</a></span></li></ul><ul><li class="nav-heading">Modules</li><li class="nav-heading"><span class="nav-item-type type-module" title="module">M</span><span class="nav-item-name is-module"><a href="module-ChatApi.html">ChatApi</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#categoryPermissions">categoryPermissions</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#channel">channel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#channels">channels</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#createChannel">createChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#createChannelArchive">createChannelArchive</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#destroyChannel">destroyChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#followChannel">followChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#listChannelMemberships">listChannelMemberships</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#listCurrentUserChannels">listCurrentUserChannels</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#moveChannelMessages">moveChannelMessages</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#sendMessage">sendMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#unfollowChannel">unfollowChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateChannel">updateChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateChannelStatus">updateChannelStatus</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateCurrentUserChannelNotificationsSettings">updateCurrentUserChannelNotificationsSettings</a></span></li></ul><li class="nav-heading"><a href="global.html">Globals</a></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="global.html#load">load</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="global.html#loadMore">loadMore</a></span></li>
</nav>
</header>
<main class="layout-main layout-content--source">
<div class="container">
<p class="page-kind">source</p>
<h1 class="page-title">lib/collection.js</h1>
<section>
<article>
<pre id="source" class="source-page line-numbers"><code class="language-js">import { ajax } from "discourse/lib/ajax";
import { tracked } from "@glimmer/tracking";
import { bind } from "discourse-common/utils/decorators";
import { Promise } from "rsvp";
/**
* Handles a paginated API response.
*/
export default class Collection {
@tracked items = [];
@tracked meta = {};
@tracked loading = false;
constructor(resourceURL, handler) {
this._resourceURL = resourceURL;
this._handler = handler;
this._fetchedAll = false;
}
get loadMoreURL() {
return this.meta.load_more_url;
}
get totalRows() {
return this.meta.total_rows;
}
get length() {
return this.items.length;
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index &lt; this.items.length) {
return { value: this.items[index++], done: false };
} else {
return { done: true };
}
},
};
}
/**
* Loads first batch of results
* @returns {Promise}
*/
@bind
load(params = {}) {
this._fetchedAll = false;
if (this.loading) {
return Promise.resolve();
}
this.loading = true;
const filteredQueryParams = Object.entries(params).filter(
([, v]) => v !== undefined
);
const queryString = new URLSearchParams(filteredQueryParams).toString();
const endpoint = this._resourceURL + (queryString ? `?${queryString}` : "");
return this.#fetch(endpoint)
.then((result) => {
this.items = this._handler(result);
this.meta = result.meta;
})
.finally(() => {
this.loading = false;
});
}
/**
* Attempts to load more results
* @returns {Promise}
*/
@bind
loadMore() {
let promise = Promise.resolve();
if (this.loading) {
return promise;
}
if (
this._fetchedAll ||
(this.totalRows &amp;&amp; this.items.length >= this.totalRows)
) {
return promise;
}
this.loading = true;
if (this.loadMoreURL) {
promise = this.#fetch(this.loadMoreURL).then((result) => {
const newItems = this._handler(result);
if (newItems.length) {
this.items = this.items.concat(newItems);
} else {
this._fetchedAll = true;
}
this.meta = result.meta;
});
}
return promise.finally(() => {
this.loading = false;
});
}
#fetch(url) {
return ajax(url, { type: "GET" });
}
}
</code></pre>
</article>
</section>
</div>
</main>
<footer class="layout-footer">
<div class="container">
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.0</a>
</div>
</footer>
<script src="scripts/prism.dev.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,198 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Discourse: exports</title>
<link type="text/css" rel="stylesheet" href="styles/vendor/prism-custom.css">
<link type="text/css" rel="stylesheet" href="styles/styles.css">
</head>
<body>
<header class="layout-header">
<h1>
<a href="./index.html">
Discourse
</a>
</h1>
<nav class="layout-nav">
<ul><li class="nav-heading">Classes</li><li class="nav-heading"><span class="nav-item-type type-class" title="class">C</span><span class="nav-item-name is-class"><a href="PluginApi.html">PluginApi</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="PluginApi.html#decorateChatMessage">decorateChatMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="PluginApi.html#registerChatComposerButton">registerChatComposerButton</a></span></li><li class="nav-heading"><span class="nav-item-type type-class" title="class">C</span><span class="nav-item-name is-class"><a href="module.exports.html">exports</a></span></li></ul><ul><li class="nav-heading">Modules</li><li class="nav-heading"><span class="nav-item-type type-module" title="module">M</span><span class="nav-item-name is-module"><a href="module-ChatApi.html">ChatApi</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#categoryPermissions">categoryPermissions</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#channel">channel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#channels">channels</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#createChannel">createChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#createChannelArchive">createChannelArchive</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#destroyChannel">destroyChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#followChannel">followChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#listChannelMemberships">listChannelMemberships</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#listCurrentUserChannels">listCurrentUserChannels</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#moveChannelMessages">moveChannelMessages</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#sendMessage">sendMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#unfollowChannel">unfollowChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateChannel">updateChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateChannelStatus">updateChannelStatus</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateCurrentUserChannelNotificationsSettings">updateCurrentUserChannelNotificationsSettings</a></span></li></ul><li class="nav-heading"><a href="global.html">Globals</a></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="global.html#load">load</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="global.html#loadMore">loadMore</a></span></li>
</nav>
</header>
<main class="layout-main ">
<div class="container">
<p class="page-kind">Class</p>
<h1 class="page-title">exports</h1>
<section>
<header class="class">
<!-- <h2>exports</h2> -->
<div class="class-description">Handles a paginated API response.</div>
</header>
<article>
<div class="container-overview">
<h3 class="subtitle">Constructor</h3>
<div class="method-type">
</div>
<h4 class="method-name" id="exports">new exports<span class="signature">()</span><span class="return-type-signature"></span>
</h4>
<div class="details">
</div>
<h4 class="method-heading">Source</h4>
<ul>
<li class="method-source">
<a href="lib_collection.js.html">lib/collection.js</a><a href="lib_collection.js.html#source.9">, line 9</a>
</li>
</ul>
</div>
</article>
</section>
</div>
</main>
<footer class="layout-footer">
<div class="container">
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.0</a>
</div>
</footer>
<script src="scripts/prism.dev.js"></script>
</body>
</html>

View File

@ -0,0 +1,156 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Discourse: pre-initializers/chat-plugin-api.js</title>
<link type="text/css" rel="stylesheet" href="styles/vendor/prism-custom.css">
<link type="text/css" rel="stylesheet" href="styles/styles.css">
</head>
<body>
<header class="layout-header">
<h1>
<a href="./index.html">
Discourse
</a>
</h1>
<nav class="layout-nav">
<ul><li class="nav-heading">Classes</li><li class="nav-heading"><span class="nav-item-type type-class" title="class">C</span><span class="nav-item-name is-class"><a href="PluginApi.html">PluginApi</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="PluginApi.html#decorateChatMessage">decorateChatMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="PluginApi.html#registerChatComposerButton">registerChatComposerButton</a></span></li><li class="nav-heading"><span class="nav-item-type type-class" title="class">C</span><span class="nav-item-name is-class"><a href="module.exports.html">exports</a></span></li></ul><ul><li class="nav-heading">Modules</li><li class="nav-heading"><span class="nav-item-type type-module" title="module">M</span><span class="nav-item-name is-module"><a href="module-ChatApi.html">ChatApi</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#categoryPermissions">categoryPermissions</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#channel">channel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#channels">channels</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#createChannel">createChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#createChannelArchive">createChannelArchive</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#destroyChannel">destroyChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#followChannel">followChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#listChannelMemberships">listChannelMemberships</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#listCurrentUserChannels">listCurrentUserChannels</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#moveChannelMessages">moveChannelMessages</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#sendMessage">sendMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#unfollowChannel">unfollowChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateChannel">updateChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateChannelStatus">updateChannelStatus</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateCurrentUserChannelNotificationsSettings">updateCurrentUserChannelNotificationsSettings</a></span></li></ul><li class="nav-heading"><a href="global.html">Globals</a></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="global.html#load">load</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="global.html#loadMore">loadMore</a></span></li>
</nav>
</header>
<main class="layout-main layout-content--source">
<div class="container">
<p class="page-kind">source</p>
<h1 class="page-title">pre-initializers/chat-plugin-api.js</h1>
<section>
<article>
<pre id="source" class="source-page line-numbers"><code class="language-js">import { withPluginApi } from "discourse/lib/plugin-api";
import {
addChatMessageDecorator,
resetChatMessageDecorators,
} from "discourse/plugins/chat/discourse/components/chat-message";
import { registerChatComposerButton } from "discourse/plugins/chat/discourse/lib/chat-composer-buttons";
/**
* Class exposing the javascript API available to plugins and themes.
* @class PluginApi
*/
/**
* Callback used to decorate a chat message
*
* @callback PluginApi~decorateChatMessageCallback
* @param {ChatMessage} chatMessage - model
* @param {HTMLElement} messageContainer - DOM node
* @param {ChatChannel} chatChannel - model
*/
/**
* Decorate a chat message
*
* @memberof PluginApi
* @instance
* @function decorateChatMessage
* @param {PluginApi~decorateChatMessageCallback} decorator
* @example
*
* api.decorateChatMessage((chatMessage, messageContainer) => {
* messageContainer.dataset.foo = chatMessage.id;
* });
*/
/**
* Register a button in the chat composer
*
* @memberof PluginApi
* @instance
* @function registerChatComposerButton
* @param {Object} options
* @param {number} options.id - The id of the button
* @param {function} options.action - An action name or an anonymous function called when the button is pressed, eg: "onFooClicked" or `() => { console.log("clicked") }`
* @param {string} options.icon - A valid font awesome icon name, eg: "far fa-image"
* @param {string} options.label - Text displayed on the button, a translatable key, eg: "foo.bar"
* @param {string} options.translatedLabel - Text displayed on the button, a string, eg: "Add gifs"
* @param {string} [options.position] - Can be "inline" or "dropdown", defaults to "inline"
* @param {string} [options.title] - Title attribute of the button, a translatable key, eg: "foo.bar"
* @param {string} [options.translatedTitle] - Title attribute of the button, a string, eg: "Add gifs"
* @param {string} [options.ariaLabel] - aria-label attribute of the button, a translatable key, eg: "foo.bar"
* @param {string} [options.translatedAriaLabel] - aria-label attribute of the button, a string, eg: "Add gifs"
* @param {string} [options.classNames] - Additional names to add to the buttons class attribute, eg: ["foo", "bar"]
* @param {boolean} [options.displayed] - Hide or show the button
* @param {boolean} [options.disabled] - Sets the disabled attribute on the button
* @param {number} [options.priority] - An integer defining the order of the buttons, higher comes first, eg: `700`
* @param {Array.&lt;string>} [options.dependentKeys] - List of property names which should trigger a refresh of the buttons when changed, eg: `["foo.bar", "bar.baz"]`
* @example
*
* api.registerChatComposerButton({
* id: "foo",
* displayed() {
* return this.site.mobileView &amp;&amp; this.canAttachUploads;
* }
* });
*/
export default {
name: "chat-plugin-api",
after: "inject-discourse-objects",
initialize() {
withPluginApi("1.2.0", (api) => {
const apiPrototype = Object.getPrototypeOf(api);
if (!apiPrototype.hasOwnProperty("decorateChatMessage")) {
Object.defineProperty(apiPrototype, "decorateChatMessage", {
value(decorator) {
addChatMessageDecorator(decorator);
},
});
}
if (!apiPrototype.hasOwnProperty("registerChatComposerButton")) {
Object.defineProperty(apiPrototype, "registerChatComposerButton", {
value(button) {
registerChatComposerButton(button);
},
});
}
});
},
teardown() {
resetChatMessageDecorators();
},
};
</code></pre>
</article>
</section>
</div>
</main>
<footer class="layout-footer">
<div class="container">
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.0</a>
</div>
</footer>
<script src="scripts/prism.dev.js"></script>
</body>
</html>

View File

@ -0,0 +1,57 @@
(function() {
if (typeof self === 'undefined' || !self.Prism || !self.document) {
return;
}
Prism.hooks.add('complete', function (env) {
if (!env.code) {
return;
}
// works only for <code> wrapped inside <pre> (not inline)
var pre = env.element.parentNode;
var clsReg = /\s*\bline-numbers\b\s*/;
if (
!pre || !/pre/i.test(pre.nodeName) ||
// Abort only if nor the <pre> nor the <code> have the class
(!clsReg.test(pre.className) && !clsReg.test(env.element.className))
) {
return;
}
if (env.element.querySelector(".line-numbers-rows")) {
// Abort if line numbers already exists
return;
}
if (clsReg.test(env.element.className)) {
// Remove the class "line-numbers" from the <code>
env.element.className = env.element.className.replace(clsReg, '');
}
if (!clsReg.test(pre.className)) {
// Add the class "line-numbers" to the <pre>
pre.className += ' line-numbers';
}
var match = env.code.match(/\n(?!$)/g);
var linesNum = match ? match.length + 1 : 1;
var lineNumbersWrapper;
var lines = new Array(linesNum + 1);
lines = lines.join('<span></span>');
lineNumbersWrapper = document.createElement('span');
lineNumbersWrapper.setAttribute('aria-hidden', 'true');
lineNumbersWrapper.className = 'line-numbers-rows';
lineNumbersWrapper.innerHTML = lines;
if (pre.hasAttribute('data-start')) {
pre.style.counterReset = 'linenumber ' + (parseInt(pre.getAttribute('data-start'), 10) - 1);
}
env.element.appendChild(lineNumbersWrapper);
});
}());

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,325 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Discourse: services/chat-api.js</title>
<link type="text/css" rel="stylesheet" href="styles/vendor/prism-custom.css">
<link type="text/css" rel="stylesheet" href="styles/styles.css">
</head>
<body>
<header class="layout-header">
<h1>
<a href="./index.html">
Discourse
</a>
</h1>
<nav class="layout-nav">
<ul><li class="nav-heading">Classes</li><li class="nav-heading"><span class="nav-item-type type-class" title="class">C</span><span class="nav-item-name is-class"><a href="PluginApi.html">PluginApi</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="PluginApi.html#decorateChatMessage">decorateChatMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="PluginApi.html#registerChatComposerButton">registerChatComposerButton</a></span></li><li class="nav-heading"><span class="nav-item-type type-class" title="class">C</span><span class="nav-item-name is-class"><a href="module.exports.html">exports</a></span></li></ul><ul><li class="nav-heading">Modules</li><li class="nav-heading"><span class="nav-item-type type-module" title="module">M</span><span class="nav-item-name is-module"><a href="module-ChatApi.html">ChatApi</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#categoryPermissions">categoryPermissions</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#channel">channel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#channels">channels</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#createChannel">createChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#createChannelArchive">createChannelArchive</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#destroyChannel">destroyChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#followChannel">followChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#listChannelMemberships">listChannelMemberships</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#listCurrentUserChannels">listCurrentUserChannels</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#moveChannelMessages">moveChannelMessages</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#sendMessage">sendMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#unfollowChannel">unfollowChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateChannel">updateChannel</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateChannelStatus">updateChannelStatus</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="module-ChatApi.html#updateCurrentUserChannelNotificationsSettings">updateCurrentUserChannelNotificationsSettings</a></span></li></ul><li class="nav-heading"><a href="global.html">Globals</a></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="global.html#load">load</a></span></li><li class="nav-item"><span class="nav-item-type type-function" title="function">F</span><span class="nav-item-name is-function"><a href="global.html#loadMore">loadMore</a></span></li>
</nav>
</header>
<main class="layout-main layout-content--source">
<div class="container">
<p class="page-kind">source</p>
<h1 class="page-title">services/chat-api.js</h1>
<section>
<article>
<pre id="source" class="source-page line-numbers"><code class="language-js">import Service, { inject as service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import UserChatChannelMembership from "discourse/plugins/chat/discourse/models/user-chat-channel-membership";
import Collection from "../lib/collection";
/**
* Chat API service. Provides methods to interact with the chat API.
*
* @module ChatApi
* @implements {@ember/service}
*/
export default class ChatApi extends Service {
@service chatChannelsManager;
/**
* Get a channel by its ID.
* @param {number} channelId - The ID of the channel.
* @returns {Promise}
*
* @example
*
* this.chatApi.channel(1).then(channel => { ... })
*/
channel(channelId) {
return this.#getRequest(`/channels/${channelId}`).then((result) =>
this.chatChannelsManager.store(result.channel)
);
}
/**
* List all accessible category channels of the current user.
* @returns {Collection}
*
* @example
*
* this.chatApi.channels.then(channels => { ... })
*/
channels() {
return new Collection(`${this.#basePath}/channels`, (response) => {
return response.channels.map((channel) =>
this.chatChannelsManager.store(channel)
);
});
}
/**
* Moves messages from one channel to another.
* @param {number} channelId - The ID of the original channel.
* @param {object} data - Params of the move.
* @param {Array.&lt;number>} data.message_ids - IDs of the moved messages.
* @param {number} data.destination_channel_id - ID of the channel where the messages are moved to.
* @returns {Promise}
*
* @example
*
* this.chatApi
* .moveChannelMessages(1, {
* message_ids: [2, 3],
* destination_channel_id: 4,
* }).then(() => { ... })
*/
moveChannelMessages(channelId, data = {}) {
return this.#postRequest(`/channels/${channelId}/messages/moves`, {
move: data,
});
}
/**
* Destroys a channel.
* @param {number} channelId - The ID of the channel.
* @returns {Promise}
*
* @example
*
* this.chatApi.destroyChannel(1).then(() => { ... })
*/
destroyChannel(channelId) {
return this.#deleteRequest(`/channels/${channelId}`);
}
/**
* Creates a channel.
* @param {object} data - Params of the channel.
* @param {string} data.name - The name of the channel.
* @param {string} data.chatable_id - The category of the channel.
* @param {string} data.description - The description of the channel.
* @param {boolean} [data.auto_join_users] - Should users join this channel automatically.
* @returns {Promise}
*
* @example
*
* this.chatApi
* .createChannel({ name: "foo", chatable_id: 1, description "bar" })
* .then((channel) => { ... })
*/
createChannel(data = {}) {
return this.#postRequest("/channels", { channel: data }).then((response) =>
this.chatChannelsManager.store(response.channel)
);
}
/**
* Lists chat permissions for a category.
* @param {number} categoryId - ID of the category.
* @returns {Promise}
*/
categoryPermissions(categoryId) {
return this.#getRequest(`/category-chatables/${categoryId}/permissions`);
}
/**
* Sends a message.
* @param {number} channelId - ID of the channel.
* @param {object} data - Params of the message.
* @param {string} data.message - The raw content of the message in markdown.
* @param {string} data.cooked - The cooked content of the message.
* @param {number} [data.in_reply_to_id] - The ID of the replied-to message.
* @param {number} [data.staged_id] - The staged ID of the message before it was persisted.
* @param {Array.&lt;number>} [data.upload_ids] - Array of upload ids linked to the message.
* @returns {Promise}
*/
sendMessage(channelId, data = {}) {
return ajax(`/chat/${channelId}`, {
ignoreUnsent: false,
type: "POST",
data,
});
}
/**
* Creates a channel archive.
* @param {number} channelId - The ID of the channel.
* @param {object} data - Params of the archive.
* @param {string} data.selection - "new_topic" or "existing_topic".
* @param {string} [data.title] - Title of the topic when creating a new topic.
* @param {string} [data.category_id] - ID of the category used when creating a new topic.
* @param {Array.&lt;string>} [data.tags] - tags used when creating a new topic.
* @param {string} [data.topic_id] - ID of the topic when using an existing topic.
* @returns {Promise}
*/
createChannelArchive(channelId, data = {}) {
return this.#postRequest(`/channels/${channelId}/archives`, {
archive: data,
});
}
/**
* Updates a channel.
* @param {number} channelId - The ID of the channel.
* @param {object} data - Params of the archive.
* @param {string} [data.description] - Description of the channel.
* @param {string} [data.name] - Name of the channel.
* @returns {Promise}
*/
updateChannel(channelId, data = {}) {
return this.#putRequest(`/channels/${channelId}`, { channel: data });
}
/**
* Updates the status of a channel.
* @param {number} channelId - The ID of the channel.
* @param {string} status - The new status, can be "open" or "closed".
* @returns {Promise}
*/
updateChannelStatus(channelId, status) {
return this.#putRequest(`/channels/${channelId}/status`, { status });
}
/**
* Lists members of a channel.
* @param {number} channelId - The ID of the channel.
* @returns {Collection}
*/
listChannelMemberships(channelId) {
return new Collection(
`${this.#basePath}/channels/${channelId}/memberships`,
(response) => {
return response.memberships.map((membership) =>
UserChatChannelMembership.create(membership)
);
}
);
}
/**
* Lists public and direct message channels of the current user.
* @returns {Promise}
*/
listCurrentUserChannels() {
return this.#getRequest("/channels/me").then((result) => {
return (result?.channels || []).map((channel) =>
this.chatChannelsManager.store(channel)
);
});
}
/**
* Makes current user follow a channel.
* @param {number} channelId - The ID of the channel.
* @returns {Promise}
*/
followChannel(channelId) {
return this.#postRequest(`/channels/${channelId}/memberships/me`).then(
(result) => UserChatChannelMembership.create(result.membership)
);
}
/**
* Makes current user unfollow a channel.
* @param {number} channelId - The ID of the channel.
* @returns {Promise}
*/
unfollowChannel(channelId) {
return this.#deleteRequest(`/channels/${channelId}/memberships/me`).then(
(result) => UserChatChannelMembership.create(result.membership)
);
}
/**
* Update notifications settings of current user for a channel.
* @param {number} channelId - The ID of the channel.
* @param {object} data - The settings to modify.
* @param {boolean} [data.muted] - Mutes the channel.
* @param {string} [data.desktop_notification_level] - Notifications level on desktop: never, mention or always.
* @param {string} [data.mobile_notification_level] - Notifications level on mobile: never, mention or always.
* @returns {Promise}
*/
updateCurrentUserChannelNotificationsSettings(channelId, data = {}) {
return this.#putRequest(
`/channels/${channelId}/notifications-settings/me`,
{ notifications_settings: data }
);
}
get #basePath() {
return "/chat/api";
}
#getRequest(endpoint, data = {}) {
return ajax(`${this.#basePath}${endpoint}`, {
type: "GET",
data,
});
}
#putRequest(endpoint, data = {}) {
return ajax(`${this.#basePath}${endpoint}`, {
type: "PUT",
data,
});
}
#postRequest(endpoint, data = {}) {
return ajax(`${this.#basePath}${endpoint}`, {
type: "POST",
data,
});
}
#deleteRequest(endpoint, data = {}) {
return ajax(`${this.#basePath}${endpoint}`, {
type: "DELETE",
data,
});
}
}
</code></pre>
</article>
</section>
</div>
</main>
<footer class="layout-footer">
<div class="container">
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.0</a>
</div>
</footer>
<script src="scripts/prism.dev.js"></script>
</body>
</html>

View File

@ -0,0 +1,498 @@
:root {
--primary-color: #0664a8;
--secondary-color: #107e7d;
--link-color: var(--primary-color);
--link-hover-color: var(--primary-color);
--border-color: #eee;
--code-color: #666;
--code-attention-color: #ca2d00;
--text-color: #4a4a4a;
--light-font-color: #999;
--supporting-color: #7097b5;
--heading-color: var(--text-color);
--subheading-color: var(--secondary-color);
--heading-background: #f7f7f7;
--code-bg-color: #f8f8f8;
--nav-title-color: var(--primary-color);
--nav-title-align: center;
--nav-title-size: 1rem;
--nav-title-margin-bottom: 1.5em;
--nav-title-font-weight: 600;
--nav-list-margin-left: 2em;
--nav-bg-color: #fff;
--nav-heading-display: block;
--nav-heading-color: #aaa;
--nav-link-color: #666;
--nav-text-color: #aaa;
--nav-type-class-color: #fff;
--nav-type-class-bg: #FF8C00;
--nav-type-member-color: #39b739;
--nav-type-member-bg: #d5efd5;
--nav-type-function-color: #549ab9;
--nav-type-function-bg: #e1f6ff;
--nav-type-namespace-color: #eb6420;
--nav-type-namespace-bg: #fad8c7;
--nav-type-typedef-color: #964cb1;
--nav-type-typedef-bg: #f2e4f7;
--nav-type-module-color: #964cb1;
--nav-type-module-bg: #f2e4f7;
--nav-type-event-color: #948b34;
--nav-type-event-bg: #fff6a6;
--max-content-width: 900px;
--nav-width: 320px;
--padding-unit: 30px;
--layout-footer-color: #aaa;
--member-name-signature-display: none;
--base-font-size: 16px;
--base-line-height: 1.7;
--body-font: -apple-system, system-ui, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
--code-font: Consolas, Monaco, "Andale Mono", monospace;
}
body {
font-family: var(--body-font);
font-size: var(--base-font-size);
line-height: var(--base-line-height);
color: var(--text-color);
-webkit-font-smoothing: antialiased;
text-size-adjust: 100%;
}
* {
box-sizing: border-box;
}
a {
text-decoration: none;
color: var(--link-color);
}
a:hover, a:active {
text-decoration: underline;
color: var(--link-hover-color);
}
img {
max-width: 100%;
}
img + p {
margin-top: 1em;
}
ul {
margin: 1em 0;
}
tt, code, kbd, samp {
font-family: var(--code-font);
}
code {
display: inline-block;
background-color: var(--code-bg-color);
padding: 2px 6px 0px;
border-radius: 3px;
color: var(--code-attention-color);
}
.prettyprint.source code:not([class*=language-]) {
display: block;
padding: 20px;
overflow: scroll;
color: var(--code-color);
}
.layout-main,
.layout-footer {
margin-left: var(--nav-width);
}
.container {
max-width: var(--max-content-width);
margin-left: auto;
margin-right: auto;
}
.layout-main {
margin-top: var(--padding-unit);
margin-bottom: var(--padding-unit);
padding: 0 var(--padding-unit);
}
.layout-header {
background: var(--nav-bg-color);
border-right: 1px solid var(--border-color);
position: fixed;
padding: 0 var(--padding-unit);
top: 0;
left: 0;
right: 0;
width: var(--nav-width);
height: 100%;
overflow: scroll;
}
.layout-header h1 {
display: block;
margin-bottom: var(--nav-title-margin-bottom);
font-size: var(--nav-title-size);
font-weight: var(--nav-title-font-weight);
text-align: var(--nav-title-align);
}
.layout-header h1 a:link, .layout-header h1 a:visited {
color: var(--nav-title-color);
}
.layout-header img {
max-width: 120px;
display: block;
margin: 1em auto;
}
.layout-nav {
margin-bottom: 2rem;
}
.layout-nav ul {
margin: 0 0 var(--nav-list-margin-left);
padding: 0;
}
.layout-nav li {
list-style-type: none;
font-size: 0.95em;
}
.layout-nav li.nav-heading:first-child {
display: var(--nav-heading-display);
margin-left: 0;
margin-bottom: 1em;
text-transform: uppercase;
color: var(--nav-heading-color);
font-size: 0.85em;
}
.layout-nav a {
color: var(--nav-link-color);
}
.layout-nav a:link, .layout-nav a a:visited {
color: var(--nav-link-color);
}
.layout-content--source {
max-width: none;
}
.nav-heading {
margin-top: 1em;
font-weight: 500;
}
.nav-heading a {
color: var(--nav-link-color);
}
.nav-heading a:link, .nav-heading a:visited {
color: var(--nav-link-color);
}
.nav-heading .nav-item-type {
font-size: 0.9em;
}
.nav-item-type {
display: inline-block;
font-size: 0.9em;
width: 1.2em;
height: 1.2em;
line-height: 1.2em;
display: inline-block;
text-align: center;
border-radius: 0.2em;
margin-right: 0.5em;
}
.nav-item-type.type-class {
color: var(--nav-type-class-color);
background: var(--nav-type-class-bg);
}
.nav-item-type.type-typedef {
color: var(--nav-type-typedef-color);
background: var(--nav-type-typedef-bg);
}
.nav-item-type.type-function {
color: var(--nav-type-function-color);
background: var(--nav-type-function-bg);
}
.nav-item-type.type-namespace {
color: var(--nav-type-namespace-color);
background: var(--nav-type-namespace-bg);
}
.nav-item-type.type-member {
color: var(--nav-type-member-color);
background: var(--nav-type-member-bg);
}
.nav-item-type.type-module {
color: var(--nav-type-module-color);
background: var(--nav-type-module-bg);
}
.nav-item-type.type-event {
color: var(--nav-type-event-color);
background: var(--nav-type-event-bg);
}
.nav-item-name.is-function:after {
display: inline;
content: "()";
color: var(--nav-link-color);
opacity: 0.75;
}
.nav-item-name.is-class {
font-size: 1.1em;
}
.layout-footer {
padding-top: 2rem;
padding-bottom: 2rem;
font-size: 0.8em;
text-align: center;
color: var(--layout-footer-color);
}
.layout-footer a {
color: var(--light-font-color);
text-decoration: underline;
}
h1 {
font-size: 2rem;
color: var(--heading-color);
}
h5 {
margin: 0;
font-weight: 500;
font-size: 1em;
}
h5 + .code-caption {
margin-top: 1em;
}
.page-kind {
margin: 0 0 -0.5em;
font-weight: 400;
color: var(--light-font-color);
text-transform: uppercase;
}
.page-title {
margin-top: 0;
}
.subtitle {
font-weight: 600;
font-size: 1.5em;
color: var(--subheading-color);
margin: 1em 0;
padding: 0.4em 0;
border-bottom: 1px solid var(--border-color);
}
.subtitle + .event, .subtitle + .member, .subtitle + .method {
border-top: none;
padding-top: 0;
}
.method-type + .method-name {
margin-top: 0.5em;
}
.event-name,
.member-name,
.method-name,
.type-definition-name {
margin: 1em 0;
font-size: 1.4rem;
font-family: var(--code-font);
font-weight: 600;
color: var(--primary-color);
}
.event-name .signature-attributes,
.member-name .signature-attributes,
.method-name .signature-attributes,
.type-definition-name .signature-attributes {
display: inline-block;
margin-left: 0.25em;
font-size: 60%;
color: #999;
font-style: italic;
font-weight: lighter;
}
.type-signature {
display: inline-block;
margin-left: 0.5em;
}
.member-name .type-signature {
display: var(--member-name-signature-display);
}
.type-signature,
.return-type-signature {
color: #aaa;
font-weight: 400;
}
.type-signature a:link, .type-signature a:visited,
.return-type-signature a:link,
.return-type-signature a:visited {
color: #aaa;
}
table {
margin-top: 1rem;
width: auto;
min-width: 400px;
max-width: 100%;
border-top: 1px solid var(--border-color);
border-right: 1px solid var(--border-color);
}
table th, table h4 {
font-weight: 500;
}
table th,
table td {
padding: 0.5rem 0.75rem;
}
table th,
table td {
border-left: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
}
table p:last-child {
margin-bottom: 0;
}
.readme h2 {
border-bottom: 1px solid var(--border-color);
margin: 1em 0;
padding-bottom: 0.5rem;
color: var(--subheading-color);
}
.readme h2 + h3 {
margin-top: 0;
}
.readme h3 {
margin: 2rem 0 1rem 0;
}
article.event, article.member, article.method {
padding: 1em 0 1em;
margin: 1em 0;
border-top: 1px solid var(--border-color);
}
.method-type-signature:not(:empty) {
display: inline-block;
background: #ecf0f1;
color: #627475;
padding: 0.25em 0.5em 0.35em;
font-weight: 300;
font-size: 0.8rem;
margin: 0 0.75em 0 0;
}
.method-heading {
margin: 1em 0;
}
li.method-returns,
.method-params li {
margin-bottom: 1em;
}
.method-source a:link, .method-source a:visited {
color: var(--light-font-color);
}
.method-returns p {
margin: 0;
}
.event-description,
.method-description {
margin: 0 0 2em;
}
.param-type code,
.method-returns code {
color: #111;
}
.param-name {
font-weight: 600;
display: inline-block;
margin-right: 0.5em;
}
.param-type,
.param-default,
.param-attributes {
font-family: var(--code-font);
}
.param-default::before {
display: inline-block;
content: "Default:";
font-family: var(--body-font);
}
.param-attributes {
color: var(--light-font-color);
}
.param-description p:first-child {
margin-top: 0;
}
.param-properties {
font-weight: 500;
margin: 1em 0 0;
}
.param-types,
.property-types {
display: inline-block;
margin: 0 0.5em 0 0.25em;
color: #999;
}
.param-attr,
.property-attr {
display: inline-block;
padding: 0.2em 0.5em;
border: 1px solid #eee;
color: #aaa;
font-weight: 300;
font-size: 0.8em;
vertical-align: baseline;
}
.properties-table p:last-child {
margin-bottom: 0;
}
pre[class*=language-] {
border-radius: 0;
}
code[class*=language-],
pre[class*=language-] {
text-shadow: none;
border: none;
}
code[class*=language-].source-page,
pre[class*=language-].source-page {
font-size: 0.9em;
}
.line-numbers .line-numbers-rows {
border-right: none;
}
.source-page {
font-size: 14px;
}
.source-page code {
z-index: 1;
}
.source-page .line-height.temporary {
z-index: 0;
}

View File

@ -0,0 +1,142 @@
/* PrismJS 1.17.1
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+http */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f6f8fa;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

125
documentation/index.html Normal file
View File

@ -0,0 +1,125 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="assets/favicon.ico">
<link rel="icon" href="assets/favicon.png">
<link rel="apple-touch-icon" href="assets/favicon.png">
<meta content="en" name="language">
<meta content="width=device-width, initial-scale=1" name="viewport">
<meta name="robots" content="noindex,nofollow,noarchive">
<title>Discourse documentation | Discourse - Civilized Discussion</title>
<style>
body { background: white; }
* , *:before, *:after {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: "Rubik", Helvetica, Arial, sans-serif;
}
h1 {
color: #111;
font-weight: 700;
}
h1 a, h1 a:visited {
background-image: url(assets/highlight.png);
background-size: contain;
background-position: bottom;
background-repeat: no-repeat;
color: black;
text-decoration: none;
}
a, a:visited {
color: rgb(14, 118, 178);
text-decoration: none;
}
.site-header-navigation__container {
display: flex;
justify-content: space-between;
align-items: center;
margin: 0 auto;
padding: 0 2em;
}
.content-header {
text-align: center;
margin-bottom: 5em;
}
.content {
background-image: url(assets/blobs.svg);
background-position: top center;
background-repeat: no-repeat;
background-size: 100% auto;
overflow: hidden;
text-align: center;
position: relative;
width: 100%;
min-height: 600px;
display: flex;
justify-content: center;
}
.content__wrapper {
padding: 0 2em;
}
.projects-list {
padding: 2em;
border-radius: 15px;
position: relative;
background-image: url(assets/blob-wide-yellow3.svg);
background-position: top center;
background-repeat: no-repeat;
background-size: 100% auto;
min-height: 400px;
width: 400px;
}
.projects-list__item {
list-style: none;
}
</style>
</head>
<body>
<header role="banner" class="site-header-navigation">
<div class="site-header-navigation__container">
<div class="site-header-navigation__logo">
<a href="https://discourse.github.io/discourse">
<svg width="385" height="104" viewBox="0 0 300 40">
<use xlink:href="assets/logo.svg#logo-large"></use>
</svg>
</a>
</div>
</div>
</header>
<header class="content-header">
<div class="content__wrapper">
<h1><a href="https://www.discourse.org">Discourse</a> projects documentation</h1>
</div>
</header>
<main class="content">
<div class="content__wrapper">
<ul class="projects-list">
<li class="projects-list__item">
<h4>Chat</h4>
<p>
<a href="chat/backend/index.html">Backend</a>
<span> - </span>
<a href="chat/frontend/index.html">Frontend</a>
</p>
</li>
</ul>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
# Order was not deterministic for identic method names defined with @!method
# so we sort the list on path instead
def generate_method_list
@items =
prune_method_listing(Registry.all(:method), false)
.reject { |m| m.name.to_s =~ /=$/ && m.is_attribute? }
.sort_by { |m| m.path }
@list_title = "Method List"
@list_type = "method"
generate_list_contents
end

View File

@ -0,0 +1,6 @@
<%# Removes date and ruby version to avoid differences in CI check %>
<div id="footer">
Generated by
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
<%= YARD::VERSION %>.
</div>

View File

@ -0,0 +1,4 @@
# frozen_string_literal: true
def source
end

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
require "fileutils"
task "documentation" do
generate_chat_documentation
end
def generate_chat_documentation
destination = File.join(Rails.root, "documentation/chat/frontend/")
config = File.join(Rails.root, ".jsdoc")
files = %w[
plugins/chat/assets/javascripts/discourse/lib/collection.js
plugins/chat/assets/javascripts/discourse/pre-initializers/chat-plugin-api.js
plugins/chat/assets/javascripts/discourse/services/chat-api.js
]
`yarn --silent jsdoc --readme plugins/chat/README.md -c #{config} #{files.join(" ")} -d #{destination}`
# unecessary files
%w[
documentation/chat/frontend/scripts/prism.min.js
documentation/chat/frontend/scripts/prism.js
documentation/chat/frontend/styles/vendor/prism-default.css
documentation/chat/frontend/styles/vendor/prism-okaidia.css
documentation/chat/frontend/styles/vendor/prism-tomorrow-night.css
].each { |file| FileUtils.rm(file) }
require "open3"
require "yard"
YARD::Templates::Engine.register_template_path(
File.join(Rails.root, "documentation", "yard-custom-template"),
)
files = %w[
plugins/chat/app/services/base.rb
plugins/chat/app/services/update_user_last_read.rb
plugins/chat/app/services/trash_channel.rb
plugins/chat/app/services/update_channel.rb
plugins/chat/app/services/update_channel_status.rb
]
cmd =
"bundle exec yardoc -p documentation/yard-custom-template -t default -r plugins/chat/README.md --output-dir documentation/chat/backend #{files.join(" ")}"
Open3.popen3(cmd) { |_, stderr| puts stderr.read }
end

View File

@ -31,9 +31,10 @@
"chrome-launcher": "^0.15.1",
"chrome-remote-interface": "^0.31.3",
"eslint-config-discourse": "^3.3.0",
"jsdoc-to-markdown": "^8.0.0",
"jsdoc": "^4.0.0",
"lefthook": "^1.2.0",
"puppeteer-core": "^13.7.0"
"puppeteer-core": "^13.7.0",
"tidy-jsdoc": "^1.4.1"
},
"scripts": {
"postinstall": "yarn --cwd app/assets/javascripts/discourse $(node -e 'if(JSON.parse(process.env.npm_config_argv).original.includes(`--frozen-lockfile`)){console.log(`--frozen-lockfile`)}')"

View File

@ -1,54 +1,9 @@
:warning: This plugin is still in active development and may change frequently
This plugin is still in active development and may change frequently
## Documentation
The Discourse Chat plugin adds chat functionality to your Discourse so it can natively support both long-form and short-form communication needs of your online community.
For documentation, see [Discourse Chat](https://meta.discourse.org/t/discourse-chat/230881)
For user documentation, see [Discourse Chat](https://meta.discourse.org/t/discourse-chat/230881).
## Plugin API
### registerChatComposerButton
#### Usage
```javascript
api.registerChatComposerButton({ id: "foo", ... });
```
#### Options
Every option accepts a `value` or a `function`, when passing a function `this` will be the `chat-composer` component instance. Example of an option using a function:
```javascript
api.registerChatComposerButton({
id: "foo",
displayed() {
return this.site.mobileView && this.canAttachUploads;
},
});
```
##### Required
- `id` unique, used to identify your button, eg: "gifs"
- `action` callback when the button is pressed, can be an action name or an anonymous function, eg: "onFooClicked" or `() => { console.log("clicked") }`
A button requires at least an icon or a label:
- `icon`, eg: "times"
- `label`, text displayed on the button, a translatable key, eg: "foo.bar"
- `translatedLabel`, text displayed on the button, a string, eg: "Add gifs"
##### Optional
- `position`, can be "inline" or "dropdown", defaults to "inline"
- `title`, title attribute of the button, a translatable key, eg: "foo.bar"
- `translatedTitle`, title attribute of the button, a string, eg: "Add gifs"
- `ariaLabel`, aria-label attribute of the button, a translatable key, eg: "foo.bar"
- `translatedAriaLabel`, aria-label attribute of the button, a string, eg: "Add gifs"
- `classNames`, additional names to add to the buttons class attribute, eg: ["foo", "bar"]
- `displayed`, hide/or show the button, expects a boolean
- `disabled`, sets the disabled attribute on the button, expects a boolean
- `priority`, an integer defining the order of the buttons, higher comes first, eg: `700`
- `dependentKeys`, list of property names which should trigger a refresh of the buttons when changed, eg: `["foo.bar", "bar.baz"]`
For developer documentation, see [Discourse Documentation](https://discourse.github.io/discourse/).

View File

@ -29,37 +29,9 @@ class Chat::Api::ChatChannelsController < Chat::Api
end
def destroy
confirmation = params.require(:channel).require(:name_confirmation)&.downcase
guardian.ensure_can_delete_chat_channel!
if channel_from_params.title(current_user).downcase != confirmation
raise Discourse::InvalidParameters.new(:name_confirmation)
with_service Chat::Service::TrashChannel do
on_model_not_found(:channel) { raise ActiveRecord::RecordNotFound }
end
begin
ChatChannel.transaction do
channel_from_params.update!(
slug:
"#{Time.now.strftime("%Y%m%d-%H%M")}-#{channel_from_params.slug}-deleted".truncate(
SiteSetting.max_topic_title_length,
omission: "",
),
)
channel_from_params.trash!(current_user)
StaffActionLogger.new(current_user).log_custom(
"chat_channel_delete",
{
chat_channel_id: channel_from_params.id,
chat_channel_name: channel_from_params.title(current_user),
},
)
end
rescue ActiveRecord::Rollback
return render_json_error(I18n.t("chat.errors.delete_channel_failed"))
end
Jobs.enqueue(:chat_channel_delete, { chat_channel_id: channel_from_params.id })
render json: success_json
end
def create
@ -118,38 +90,26 @@ class Chat::Api::ChatChannelsController < Chat::Api
end
def update
guardian.ensure_can_edit_chat_channel!
if channel_from_params.direct_message_channel?
raise Discourse::InvalidParameters.new(
I18n.t("chat.errors.cant_update_direct_message_channel"),
)
end
params_to_edit = editable_params(params, channel_from_params)
params_to_edit.each { |k, v| params_to_edit[k] = nil if params_to_edit[k].blank? }
if ActiveRecord::Type::Boolean.new.deserialize(params_to_edit[:auto_join_users])
auto_join_limiter(channel_from_params).performed!
end
channel_from_params.update!(params_to_edit)
ChatPublisher.publish_chat_channel_edit(channel_from_params, current_user)
if channel_from_params.category_channel? && channel_from_params.auto_join_users
Chat::ChatChannelMembershipManager.new(
channel_from_params,
).enforce_automatic_channel_memberships
end
with_service(Chat::Service::UpdateChannel, **params_to_edit) do
on_success do
render_serialized(
channel_from_params,
result.channel,
ChatChannelSerializer,
root: "channel",
membership: channel_from_params.membership_for(current_user),
membership: result.channel.membership_for(current_user),
)
end
on_model_not_found(:channel) { raise ActiveRecord::RecordNotFound }
on_failed_policy(:check_channel_permission) { raise Discourse::InvalidAccess }
on_failed_policy(:no_direct_message_channel) { raise Discourse::InvalidAccess }
end
end
private

View File

@ -2,17 +2,10 @@
class Chat::Api::ChatChannelsStatusController < Chat::Api::ChatChannelsController
def update
status = params.require(:status)
# we only want to use this endpoint for open/closed status changes,
# the others are more "special" and are handled by the archive endpoint
if !ChatChannel.statuses.keys.include?(status) || status == "read_only" || status == "archive"
raise Discourse::InvalidParameters
end
guardian.ensure_can_change_channel_status!(channel_from_params, status.to_sym)
channel_from_params.public_send("#{status}!", current_user)
render_serialized(channel_from_params, ChatChannelSerializer, root: "channel")
with_service(Chat::Service::UpdateChannelStatus) do
on_success { render_serialized(result.channel, ChatChannelSerializer, root: "channel") }
on_model_not_found(:channel) { raise ActiveRecord::RecordNotFound }
on_failed_policy(:check_channel_permission) { raise Discourse::InvalidAccess }
end
end
end

View File

@ -4,6 +4,8 @@ class Chat::Api < Chat::ChatBaseController
before_action :ensure_logged_in
before_action :ensure_can_chat
include Chat::WithServiceHelper
private
def ensure_can_chat

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
module Chat
module WithServiceHelper
def result
@_result
end
def with_service(service, default_actions: true, **dependencies, &block)
controller = self
merged_block =
proc do
instance_eval(&controller.default_actions_for_service) if default_actions
instance_eval(&(block || proc {}))
end
Chat::Endpoint.call(service, controller, **dependencies, &merged_block)
end
def run_service(service, dependencies)
@_result = service.call(params.to_unsafe_h.merge(guardian: guardian, **dependencies.to_h))
end
def default_actions_for_service
proc do
on_success { render(json: success_json) }
on_failure { render(json: failed_json, status: 422) }
on_failed_policy(:invalid_access) { raise Discourse::InvalidAccess }
on_failed_contract do
render(
json:
failed_json.merge(errors: result[:"result.contract.default"].errors.full_messages),
status: 400,
)
end
end
end
end
end

View File

@ -36,6 +36,10 @@ class ChatChannel < ActiveRecord::Base
delegate :empty?, to: :chat_messages, prefix: true
class << self
def editable_statuses
statuses.filter { |k, _| !%w[read_only archived].include?(k) }
end
def public_channel_chatable_types
["Category"]
end

View File

@ -0,0 +1,427 @@
# frozen_string_literal: true
module Chat
module Service
# Module to be included to provide steps DSL to any class. This allows to
# create easy to understand services as the whole service cycle is visible
# simply by reading the beginning of its class.
#
# Steps are executed in the order theyre defined. They will use their name
# to execute the corresponding method defined in the service class.
#
# Currently, there are 5 types of steps:
#
# * +model(name = :model)+: used to instantiate a model (either by building
# it or fetching it from the DB). If a falsy value is returned, then the
# step will fail. Otherwise the resulting object will be assigned in
# +context[name]+ (+context[:model]+ by default).
# * +policy(name = :default)+: used to perform a check on the state of the
# system. Typically used to run guardians. If a falsy value is returned,
# the step will fail.
# * +contract(name = :default)+: used to validate the input parameters,
# typically provided by a user calling an endpoint. A special embedded
# +Contract+ class has to be defined to holds the validations. If the
# validations fail, the step will fail. Otherwise, the resulting contract
# will be available in +context[:contract]+.
# * +step(name)+: used to run small snippets of arbitrary code. The step
# doesnt care about its return value, so to mark the service as failed,
# {#fail!} has to be called explicitly.
# * +transaction+: used to wrap other steps inside a DB transaction.
#
# The methods defined on the service are automatically provided with
# the whole context passed as keyword arguments. This allows to define in a
# very explicit way what dependencies are used by the method. If for
# whatever reason a key isnt found in the current context, then Ruby will
# raise an exception when the method is called.
#
# Regarding contract classes, they have automatically {ActiveModel} modules
# included so all the {ActiveModel} API is available.
#
# @example An example from the {TrashChannel} service
# class TrashChannel
# include Base
#
# model :channel, :fetch_channel
# policy :invalid_access
# transaction do
# step :prevents_slug_collision
# step :soft_delete_channel
# step :log_channel_deletion
# end
# step :enqueue_delete_channel_relations_job
#
# private
#
# def fetch_channel(channel_id:, **)
# ChatChannel.find_by(id: channel_id)
# end
#
# def invalid_access(guardian:, channel:, **)
# guardian.can_preview_chat_channel?(channel) && guardian.can_delete_chat_channel?
# end
#
# def prevents_slug_collision(channel:, **)
# …
# end
#
# def soft_delete_channel(guardian:, channel:, **)
# …
# end
#
# def log_channel_deletion(guardian:, channel:, **)
# …
# end
#
# def enqueue_delete_channel_relations_job(channel:, **)
# …
# end
# end
# @example An example from the {UpdateChannelStatus} service which uses a contract
# class UpdateChannelStatus
# include Base
#
# model :channel, :fetch_channel
# contract
# policy :check_channel_permission
# step :change_status
#
# class Contract
# attribute :status
# validates :status, inclusion: { in: ChatChannel.editable_statuses.keys }
# end
#
# …
# end
module Base
extend ActiveSupport::Concern
# The only exception that can be raised by a service.
class Failure < StandardError
# @return [Context]
attr_reader :context
# @!visibility private
def initialize(context = nil)
@context = context
super
end
end
# Simple structure to hold the context of the service during its whole lifecycle.
class Context < OpenStruct
# @return [Boolean] returns +true+ if the conext is set as successful (default)
def success?
!failure?
end
# @return [Boolean] returns +true+ if the context is set as failed
# @see #fail!
# @see #fail
def failure?
@failure || false
end
# Marks the context as failed.
# @param context [Hash, Context] the context to merge into the current one
# @example
# context.fail!("failure": "something went wrong")
# @return [Context]
def fail!(context = {})
fail(context)
raise Failure, self
end
# Marks the context as failed without raising an exception.
# @param context [Hash, Context] the context to merge into the current one
# @example
# context.fail("failure": "something went wrong")
# @return [Context]
def fail(context = {})
merge(context)
@failure = true
self
end
# Merges the given context into the current one.
# @!visibility private
def merge(other_context = {})
other_context.each { |key, value| self[key.to_sym] = value }
self
end
private
def self.build(context = {})
self === context ? context : new(context)
end
end
# Internal module to define available steps as DSL
# @!visibility private
module StepsHelpers
def model(name = :model, step_name = :"fetch_#{name}")
steps << ModelStep.new(name, step_name)
end
def contract(name = :default, class_name: self::Contract, default_values_from: nil)
steps << ContractStep.new(
name,
class_name: class_name,
default_values_from: default_values_from,
)
end
def policy(name = :default)
steps << PolicyStep.new(name)
end
def step(name)
steps << Step.new(name)
end
def transaction(&block)
steps << TransactionStep.new(&block)
end
end
# @!visibility private
class Step
attr_reader :name, :method_name, :class_name
def initialize(name, method_name = name, class_name: nil)
@name = name
@method_name = method_name
@class_name = class_name
end
def call(instance, context)
method = instance.method(method_name)
args = {}
args = context.to_h unless method.arity.zero?
context[result_key] = Context.build
instance.instance_exec(**args, &method)
end
private
def type
self.class.name.split("::").last.downcase.sub(/^(\w+)step$/, "\\1")
end
def result_key
"result.#{type}.#{name}"
end
end
# @!visibility private
class ModelStep < Step
def call(instance, context)
context[name] = super
raise ArgumentError, "Model not found" unless context[name]
rescue ArgumentError => exception
context[result_key].fail(exception: exception)
context.fail!
end
end
# @!visibility private
class PolicyStep < Step
def call(instance, context)
unless super
context[result_key].fail
context.fail!
end
end
end
# @!visibility private
class ContractStep < Step
attr_reader :default_values_from
def initialize(name, method_name = name, class_name: nil, default_values_from: nil)
super(name, method_name, class_name: class_name)
@default_values_from = default_values_from
end
def call(instance, context)
attributes = class_name.attribute_names.map(&:to_sym)
default_values = {}
default_values = context[default_values_from].slice(*attributes) if default_values_from
contract = class_name.new(default_values.merge(context.to_h.slice(*attributes)))
context[contract_name] = contract
context[result_key] = Context.build
unless contract.valid?
context[result_key].fail(errors: contract.errors)
context.fail!
end
end
private
def contract_name
return :contract if name.to_sym == :default
:"#{name}_contract"
end
end
# @!visibility private
class TransactionStep < Step
include StepsHelpers
attr_reader :steps
def initialize(&block)
@steps = []
instance_exec(&block)
end
def call(instance, context)
ActiveRecord::Base.transaction { steps.each { |step| step.call(instance, context) } }
end
end
included do
# The global context which is available from any step.
attr_reader :context
# @!visibility private
# Internal class used to setup the base contract of the service.
self::Contract =
Class.new do
include ActiveModel::API
include ActiveModel::Attributes
include ActiveModel::AttributeMethods
include ActiveModel::Validations::Callbacks
end
end
class_methods do
include StepsHelpers
def call(context = {})
new(context).tap(&:run).context
end
def call!(context = {})
new(context).tap(&:run!).context
end
def steps
@steps ||= []
end
end
# @!scope class
# @!method model(name = :model, step_name = :"fetch_#{name}")
# @param name [Symbol] name of the model
# @param step_name [Symbol] name of the method to call for this step
# Evaluates arbitrary code to build or fetch a model (typically from the
# DB). If the step returns a falsy value, then the step will fail.
#
# It stores the resulting model in +context[:model]+ by default (can be
# customized by providing the +name+ argument).
#
# @example
# model :channel, :fetch_channel
#
# private
#
# def fetch_channel(channel_id:, **)
# ChatChannel.find_by(id: channel_id)
# end
# @!scope class
# @!method policy(name = :default)
# @param name [Symbol] name for this policy
# Performs checks related to the state of the system. If the
# step doesnt return a truthy value, then the policy will fail.
#
# @example
# policy :no_direct_message_channel
#
# private
#
# def no_direct_message_channel(channel:, **)
# !channel.direct_message_channel?
# end
# @!scope class
# @!method contract(name = :default, class_name: self::Contract, default_values_from: nil)
# @param name [Symbol] name for this contract
# @param class_name [Class] a class defining the contract
# @param default_values_from [Symbol] name of the model to get default values from
# Checks the validity of the input parameters.
# Implements ActiveModel::Validations and ActiveModel::Attributes.
#
# It stores the resulting contract in +context[:contract]+ by default
# (can be customized by providing the +name+ argument).
#
# @example
# contract
#
# class Contract
# attribute :name
# validates :name, presence: true
# end
# @!scope class
# @!method step(name)
# @param name [Symbol] the name of this step
# Runs arbitrary code. To mark a step as failed, a call to {#fail!} needs
# to be made explicitly.
#
# @example
# step :update_channel
#
# private
#
# def update_channel(channel:, params_to_edit:, **)
# channel.update!(params_to_edit)
# end
# @example using {#fail!} in a step
# step :save_channel
#
# private
#
# def save_channel(channel:, **)
# fail!("something went wrong") unless channel.save
# end
# @!scope class
# @!method transaction(&block)
# @param block [Proc] a block containing steps to be run inside a transaction
# Runs steps inside a DB transaction.
#
# @example
# transaction do
# step :prevents_slug_collision
# step :soft_delete_channel
# step :log_channel_deletion
# end
# @!visibility private
def initialize(initial_context = {})
@initial_context = initial_context.with_indifferent_access
@context = Context.build(initial_context.merge(__steps__: self.class.steps))
end
private
def run
run!
rescue Failure => exception
raise if context.object_id != exception.context.object_id
end
def run!
self.class.steps.each { |step| step.call(self, context) }
end
def fail!(message)
step_name = caller_locations(1, 1)[0].label
context["result.step.#{step_name}"].fail(error: message)
context.fail!
end
end
end
end

View File

@ -0,0 +1,66 @@
# frozen_string_literal: true
module Chat
module Service
# Service responsible for trashing a chat channel.
# Note the slug is modified to prevent collisions.
#
# @example
# Chat::Service::TrashChannel.call(channel_id: 2, guardian: guardian)
#
class TrashChannel
include Base
# @!method call(channel_id:, guardian:)
# @param [Integer] channel_id
# @param [Guardian] guardian
# @return [Chat::Service::Base::Context]
DELETE_CHANNEL_LOG_KEY = "chat_channel_delete"
model :channel, :fetch_channel
policy :invalid_access
transaction do
step :prevents_slug_collision
step :soft_delete_channel
step :log_channel_deletion
end
step :enqueue_delete_channel_relations_job
private
def fetch_channel(channel_id:, **)
ChatChannel.find_by(id: channel_id)
end
def invalid_access(guardian:, channel:, **)
guardian.can_preview_chat_channel?(channel) && guardian.can_delete_chat_channel?
end
def prevents_slug_collision(channel:, **)
channel.update!(
slug:
"#{Time.current.strftime("%Y%m%d-%H%M")}-#{channel.slug}-deleted".truncate(
SiteSetting.max_topic_title_length,
omission: "",
),
)
end
def soft_delete_channel(guardian:, channel:, **)
channel.trash!(guardian.user)
end
def log_channel_deletion(guardian:, channel:, **)
StaffActionLogger.new(guardian.user).log_custom(
DELETE_CHANNEL_LOG_KEY,
{ chat_channel_id: channel.id, chat_channel_name: channel.title(guardian.user) },
)
end
def enqueue_delete_channel_relations_job(channel:, **)
Jobs.enqueue(:chat_channel_delete, chat_channel_id: channel.id)
end
end
end
end

View File

@ -0,0 +1,88 @@
# frozen_string_literal: true
module Chat
module Service
# Service responsible for updating a chat channel's name, slug, and description.
#
# For a CategoryChannel, the settings for auto_join_users and allow_channel_wide_mentions
# are also editable.
#
# @example
# Chat::Service::UpdateChannel.call(
# channel_id: 2,
# guardian: guardian,
# name: "SuperChannel",
# description: "This is the best channel",
# slug: "super-channel",
# )
#
class UpdateChannel
include Base
# @!method call(channel_id:, guardian:, **params_to_edit)
# @param [Integer] channel_id
# @param [Guardian] guardian
# @param [Hash] params_to_edit
# @option params_to_edit [String,nil] name
# @option params_to_edit [String,nil] description
# @option params_to_edit [String,nil] slug
# @option params_to_edit [Boolean] auto_join_users Only valid for {CategoryChannel}. Whether active users
# with permission to see the category should automatically join the channel.
# @option params_to_edit [Boolean] allow_channel_wide_mentions Allow the use of @here and @all in the channel.
# @return [Chat::Service::Base::Context]
model :channel, :fetch_channel
policy :no_direct_message_channel
policy :check_channel_permission
contract default_values_from: :channel
step :update_channel
step :publish_channel_update
step :auto_join_users_if_needed
# @!visibility private
class Contract
attribute :name, :string
attribute :description, :string
attribute :slug, :string
attribute :auto_join_users, :boolean, default: false
attribute :allow_channel_wide_mentions, :boolean, default: true
before_validation do
assign_attributes(
attributes
.symbolize_keys
.slice(:name, :description, :slug)
.transform_values(&:presence),
)
end
end
private
def fetch_channel(channel_id:, **)
ChatChannel.find_by(id: channel_id)
end
def no_direct_message_channel(channel:, **)
!channel.direct_message_channel?
end
def check_channel_permission(guardian:, channel:, **)
guardian.can_preview_chat_channel?(channel) && guardian.can_edit_chat_channel?
end
def update_channel(channel:, contract:, **)
channel.update!(contract.attributes)
end
def publish_channel_update(channel:, guardian:, **)
ChatPublisher.publish_chat_channel_edit(channel, guardian.user)
end
def auto_join_users_if_needed(channel:, **)
return unless channel.auto_join_users?
Chat::ChatChannelMembershipManager.new(channel).enforce_automatic_channel_memberships
end
end
end
end

View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
module Chat
module Service
# Service responsible for updating a chat channel status.
#
# @example
# Chat::Service::UpdateChannelStatus.call(channel_id: 2, guardian: guardian, status: "open")
#
class UpdateChannelStatus
include Base
# @!method call(channel_id:, guardian:, status:)
# @param [Integer] channel_id
# @param [Guardian] guardian
# @param [String] status
# @return [Chat::Service::Base::Context]
model :channel, :fetch_channel
contract
policy :check_channel_permission
step :change_status
# @!visibility private
class Contract
attribute :status
validates :status, inclusion: { in: ChatChannel.editable_statuses.keys }
end
private
def fetch_channel(channel_id:, **)
ChatChannel.find_by(id: channel_id)
end
def check_channel_permission(guardian:, channel:, status:, **)
guardian.can_preview_chat_channel?(channel) &&
guardian.can_change_channel_status?(channel, status.to_sym)
end
def change_status(channel:, status:, guardian:, **)
channel.public_send("#{status}!", guardian.user)
end
end
end
end

View File

@ -0,0 +1,79 @@
# frozen_string_literal: true
module Chat
module Service
# Service responsible for updating the last read message id of a membership.
#
# @example
# Chat::Service::UpdateUserLastRead.call(user_id: 1, channel_id: 2, message_id: 3, guardian: guardian)
#
class UpdateUserLastRead
include Base
# @!method call(user_id:, channel_id:, message_id:, guardian:)
# @param [Integer] user_id
# @param [Integer] channel_id
# @param [Integer] message_id
# @param [Guardian] guardian
# @return [Chat::Service::Base::Context]
model :membership, :fetch_active_membership
policy :invalid_access
contract
policy :ensure_message_id_recency
policy :ensure_message_exists
step :update_last_read_message_id
step :mark_associated_mentions_as_read
step :publish_new_last_read_to_clients
# @!visibility private
class Contract
attribute :message_id, :integer
attribute :user_id, :integer
attribute :channel_id, :integer
end
private
def fetch_active_membership(user_id:, channel_id:, **)
UserChatChannelMembership.includes(:user, :chat_channel).find_by(
user_id: user_id,
chat_channel_id: channel_id,
following: true,
)
end
def invalid_access(guardian:, membership:, **)
guardian.can_join_chat_channel?(membership.chat_channel)
end
def ensure_message_id_recency(message_id:, membership:, **)
!membership.last_read_message_id || message_id >= membership.last_read_message_id
end
def ensure_message_exists(channel_id:, message_id:, **)
ChatMessage.with_deleted.exists?(chat_channel_id: channel_id, id: message_id)
end
def update_last_read_message_id(message_id:, membership:, **)
membership.update!(last_read_message_id: message_id)
end
def mark_associated_mentions_as_read(membership:, message_id:, **)
Notification
.where(notification_type: Notification.types[:chat_mention])
.where(user: membership.user)
.where(read: false)
.joins("INNER JOIN chat_mentions ON chat_mentions.notification_id = notifications.id")
.joins("INNER JOIN chat_messages ON chat_mentions.chat_message_id = chat_messages.id")
.where("chat_messages.id <= ?", message_id)
.where("chat_messages.chat_channel_id = ?", membership.chat_channel.id)
.update_all(read: true)
end
def publish_new_last_read_to_clients(guardian:, channel_id:, message_id:, **)
ChatPublisher.publish_user_tracking_state(guardian.user, channel_id, message_id)
end
end
end
end

View File

@ -1,5 +1,3 @@
/** @module Collection */
import { ajax } from "discourse/lib/ajax";
import { tracked } from "@glimmer/tracking";
import { bind } from "discourse-common/utils/decorators";
@ -7,19 +5,12 @@ import { Promise } from "rsvp";
/**
* Handles a paginated API response.
*
* @class
*/
export default class Collection {
@tracked items = [];
@tracked meta = {};
@tracked loading = false;
/**
* Create a Collection instance
* @param {string} resourceURL - the API endpoint to call
* @param {callback} handler - anonymous function used to handle the response
*/
constructor(resourceURL, handler) {
this._resourceURL = resourceURL;
this._handler = handler;

View File

@ -5,6 +5,66 @@ import {
} from "discourse/plugins/chat/discourse/components/chat-message";
import { registerChatComposerButton } from "discourse/plugins/chat/discourse/lib/chat-composer-buttons";
/**
* Class exposing the javascript API available to plugins and themes.
* @class PluginApi
*/
/**
* Callback used to decorate a chat message
*
* @callback PluginApi~decorateChatMessageCallback
* @param {ChatMessage} chatMessage - model
* @param {HTMLElement} messageContainer - DOM node
* @param {ChatChannel} chatChannel - model
*/
/**
* Decorate a chat message
*
* @memberof PluginApi
* @instance
* @function decorateChatMessage
* @param {PluginApi~decorateChatMessageCallback} decorator
* @example
*
* api.decorateChatMessage((chatMessage, messageContainer) => {
* messageContainer.dataset.foo = chatMessage.id;
* });
*/
/**
* Register a button in the chat composer
*
* @memberof PluginApi
* @instance
* @function registerChatComposerButton
* @param {Object} options
* @param {number} options.id - The id of the button
* @param {function} options.action - An action name or an anonymous function called when the button is pressed, eg: "onFooClicked" or `() => { console.log("clicked") }`
* @param {string} options.icon - A valid font awesome icon name, eg: "far fa-image"
* @param {string} options.label - Text displayed on the button, a translatable key, eg: "foo.bar"
* @param {string} options.translatedLabel - Text displayed on the button, a string, eg: "Add gifs"
* @param {string} [options.position] - Can be "inline" or "dropdown", defaults to "inline"
* @param {string} [options.title] - Title attribute of the button, a translatable key, eg: "foo.bar"
* @param {string} [options.translatedTitle] - Title attribute of the button, a string, eg: "Add gifs"
* @param {string} [options.ariaLabel] - aria-label attribute of the button, a translatable key, eg: "foo.bar"
* @param {string} [options.translatedAriaLabel] - aria-label attribute of the button, a string, eg: "Add gifs"
* @param {string} [options.classNames] - Additional names to add to the buttons class attribute, eg: ["foo", "bar"]
* @param {boolean} [options.displayed] - Hide or show the button
* @param {boolean} [options.disabled] - Sets the disabled attribute on the button
* @param {number} [options.priority] - An integer defining the order of the buttons, higher comes first, eg: `700`
* @param {Array.<string>} [options.dependentKeys] - List of property names which should trigger a refresh of the buttons when changed, eg: `["foo.bar", "bar.baz"]`
* @example
*
* api.registerChatComposerButton({
* id: "foo",
* displayed() {
* return this.site.mobileView && this.canAttachUploads;
* }
* });
*/
export default {
name: "chat-plugin-api",
after: "inject-discourse-objects",

View File

@ -14,7 +14,8 @@ export default function withChatChannel(extendedClass) {
this.controllerFor("chat-channel").set("targetMessageId", null);
this.chat.activeChannel = model;
let { messageId } = this.paramsFor(this.routeName);
let { messageId, channelTitle } = this.paramsFor(this.routeName);
// messageId query param backwards-compatibility
if (messageId) {
this.router.replaceWith(
@ -24,7 +25,6 @@ export default function withChatChannel(extendedClass) {
);
}
const { channelTitle } = this.paramsFor("chat.channel");
if (channelTitle && channelTitle !== model.slugifiedTitle) {
const nearMessageParams = this.paramsFor("chat.channel.near-message");
if (nearMessageParams.messageId) {

View File

@ -1,5 +1,3 @@
/** @module ChatApi */
import Service, { inject as service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import UserChatChannelMembership from "discourse/plugins/chat/discourse/models/user-chat-channel-membership";
@ -8,7 +6,7 @@ import Collection from "../lib/collection";
/**
* Chat API service. Provides methods to interact with the chat API.
*
* @class
* @module ChatApi
* @implements {@ember/service}
*/
export default class ChatApi extends Service {
@ -31,7 +29,7 @@ export default class ChatApi extends Service {
/**
* List all accessible category channels of the current user.
* @returns {module:Collection}
* @returns {Collection}
*
* @example
*
@ -70,17 +68,14 @@ export default class ChatApi extends Service {
/**
* Destroys a channel.
* @param {number} channelId - The ID of the channel.
* @param {string} channelName - The name of the channel to be destroyed, used as confirmation.
* @returns {Promise}
*
* @example
*
* this.chatApi.destroyChannel(1, "foo").then(() => { ... })
* this.chatApi.destroyChannel(1).then(() => { ... })
*/
destroyChannel(channelId, channelName) {
return this.#deleteRequest(`/channels/${channelId}`, {
channel: { name_confirmation: channelName },
});
destroyChannel(channelId) {
return this.#deleteRequest(`/channels/${channelId}`);
}
/**
@ -174,7 +169,7 @@ export default class ChatApi extends Service {
/**
* Lists members of a channel.
* @param {number} channelId - The ID of the channel.
* @returns {module:Collection}
* @returns {Collection}
*/
listChannelMemberships(channelId) {
return new Collection(

View File

@ -1,352 +0,0 @@
## Modules
<dl>
<dt><a href="#module_Collection">Collection</a></dt>
<dd></dd>
<dt><a href="#module_ChatApi">ChatApi</a></dt>
<dd></dd>
</dl>
<a name="module_Collection"></a>
## Collection
* [Collection](#module_Collection)
* [module.exports](#exp_module_Collection--module.exports) ⏏
* [new module.exports(resourceURL, handler)](#new_module_Collection--module.exports_new)
* [.load()](#module_Collection--module.exports+load) ⇒ <code>Promise</code>
* [.loadMore()](#module_Collection--module.exports+loadMore) ⇒ <code>Promise</code>
* * *
<a name="exp_module_Collection--module.exports"></a>
### module.exports ⏏
Handles a paginated API response.
**Kind**: Exported class
* * *
<a name="new_module_Collection--module.exports_new"></a>
#### new module.exports(resourceURL, handler)
Create a Collection instance
| Param | Type | Description |
| --- | --- | --- |
| resourceURL | <code>string</code> | the API endpoint to call |
| handler | <code>callback</code> | anonymous function used to handle the response |
* * *
<a name="module_Collection--module.exports+load"></a>
#### module.exports.load() ⇒ <code>Promise</code>
Loads first batch of results
**Kind**: instance method of [<code>module.exports</code>](#exp_module_Collection--module.exports)
* * *
<a name="module_Collection--module.exports+loadMore"></a>
#### module.exports.loadMore() ⇒ <code>Promise</code>
Attempts to load more results
**Kind**: instance method of [<code>module.exports</code>](#exp_module_Collection--module.exports)
* * *
<a name="module_ChatApi"></a>
## ChatApi
* [ChatApi](#module_ChatApi)
* [module.exports](#exp_module_ChatApi--module.exports) ⏏
* [.channel(channelId)](#module_ChatApi--module.exports+channel) ⇒ <code>Promise</code>
* [.channels()](#module_ChatApi--module.exports+channels) ⇒ [<code>module.exports</code>](#exp_module_Collection--module.exports)
* [.moveChannelMessages(channelId, data)](#module_ChatApi--module.exports+moveChannelMessages) ⇒ <code>Promise</code>
* [.destroyChannel(channelId, channelName)](#module_ChatApi--module.exports+destroyChannel) ⇒ <code>Promise</code>
* [.createChannel(data)](#module_ChatApi--module.exports+createChannel) ⇒ <code>Promise</code>
* [.categoryPermissions(categoryId)](#module_ChatApi--module.exports+categoryPermissions) ⇒ <code>Promise</code>
* [.sendMessage(channelId, data)](#module_ChatApi--module.exports+sendMessage) ⇒ <code>Promise</code>
* [.createChannelArchive(channelId, data)](#module_ChatApi--module.exports+createChannelArchive) ⇒ <code>Promise</code>
* [.updateChannel(channelId, data)](#module_ChatApi--module.exports+updateChannel) ⇒ <code>Promise</code>
* [.updateChannelStatus(channelId, status)](#module_ChatApi--module.exports+updateChannelStatus) ⇒ <code>Promise</code>
* [.listChannelMemberships(channelId)](#module_ChatApi--module.exports+listChannelMemberships) ⇒ [<code>module.exports</code>](#exp_module_Collection--module.exports)
* [.listCurrentUserChannels()](#module_ChatApi--module.exports+listCurrentUserChannels) ⇒ <code>Promise</code>
* [.followChannel(channelId)](#module_ChatApi--module.exports+followChannel) ⇒ <code>Promise</code>
* [.unfollowChannel(channelId)](#module_ChatApi--module.exports+unfollowChannel) ⇒ <code>Promise</code>
* [.updateCurrentUserChannelNotificationsSettings(channelId, data)](#module_ChatApi--module.exports+updateCurrentUserChannelNotificationsSettings) ⇒ <code>Promise</code>
* * *
<a name="exp_module_ChatApi--module.exports"></a>
### module.exports ⏏
Chat API service. Provides methods to interact with the chat API.
**Kind**: Exported class
**Implements**: <code>{@ember/service}</code>
* * *
<a name="module_ChatApi--module.exports+channel"></a>
#### module.exports.channel(channelId) ⇒ <code>Promise</code>
Get a channel by its ID.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
| Param | Type | Description |
| --- | --- | --- |
| channelId | <code>number</code> | The ID of the channel. |
**Example**
```js
this.chatApi.channel(1).then(channel => { ... })
```
* * *
<a name="module_ChatApi--module.exports+channels"></a>
#### module.exports.channels() ⇒ [<code>module.exports</code>](#exp_module_Collection--module.exports)
List all accessible category channels of the current user.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
**Example**
```js
this.chatApi.channels.then(channels => { ... })
```
* * *
<a name="module_ChatApi--module.exports+moveChannelMessages"></a>
#### module.exports.moveChannelMessages(channelId, data) ⇒ <code>Promise</code>
Moves messages from one channel to another.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
| Param | Type | Description |
| --- | --- | --- |
| channelId | <code>number</code> | The ID of the original channel. |
| data | <code>object</code> | Params of the move. |
| data.message_ids | <code>Array.&lt;number&gt;</code> | IDs of the moved messages. |
| data.destination_channel_id | <code>number</code> | ID of the channel where the messages are moved to. |
**Example**
```js
this.chatApi
.moveChannelMessages(1, {
message_ids: [2, 3],
destination_channel_id: 4,
}).then(() => { ... })
```
* * *
<a name="module_ChatApi--module.exports+destroyChannel"></a>
#### module.exports.destroyChannel(channelId, channelName) ⇒ <code>Promise</code>
Destroys a channel.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
| Param | Type | Description |
| --- | --- | --- |
| channelId | <code>number</code> | The ID of the channel. |
| channelName | <code>string</code> | The name of the channel to be destroyed, used as confirmation. |
**Example**
```js
this.chatApi.destroyChannel(1, "foo").then(() => { ... })
```
* * *
<a name="module_ChatApi--module.exports+createChannel"></a>
#### module.exports.createChannel(data) ⇒ <code>Promise</code>
Creates a channel.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
| Param | Type | Description |
| --- | --- | --- |
| data | <code>object</code> | Params of the channel. |
| data.name | <code>string</code> | The name of the channel. |
| data.chatable_id | <code>string</code> | The category of the channel. |
| data.description | <code>string</code> | The description of the channel. |
| [data.auto_join_users] | <code>boolean</code> | Should users join this channel automatically. |
**Example**
```js
this.chatApi
.createChannel({ name: "foo", chatable_id: 1, description "bar" })
.then((channel) => { ... })
```
* * *
<a name="module_ChatApi--module.exports+categoryPermissions"></a>
#### module.exports.categoryPermissions(categoryId) ⇒ <code>Promise</code>
Lists chat permissions for a category.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
| Param | Type | Description |
| --- | --- | --- |
| categoryId | <code>number</code> | ID of the category. |
* * *
<a name="module_ChatApi--module.exports+sendMessage"></a>
#### module.exports.sendMessage(channelId, data) ⇒ <code>Promise</code>
Sends a message.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
| Param | Type | Description |
| --- | --- | --- |
| channelId | <code>number</code> | ID of the channel. |
| data | <code>object</code> | Params of the message. |
| data.message | <code>string</code> | The raw content of the message in markdown. |
| data.cooked | <code>string</code> | The cooked content of the message. |
| [data.in_reply_to_id] | <code>number</code> | The ID of the replied-to message. |
| [data.staged_id] | <code>number</code> | The staged ID of the message before it was persisted. |
| [data.upload_ids] | <code>Array.&lt;number&gt;</code> | Array of upload ids linked to the message. |
* * *
<a name="module_ChatApi--module.exports+createChannelArchive"></a>
#### module.exports.createChannelArchive(channelId, data) ⇒ <code>Promise</code>
Creates a channel archive.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
| Param | Type | Description |
| --- | --- | --- |
| channelId | <code>number</code> | The ID of the channel. |
| data | <code>object</code> | Params of the archive. |
| data.selection | <code>string</code> | "new_topic" or "existing_topic". |
| [data.title] | <code>string</code> | Title of the topic when creating a new topic. |
| [data.category_id] | <code>string</code> | ID of the category used when creating a new topic. |
| [data.tags] | <code>Array.&lt;string&gt;</code> | tags used when creating a new topic. |
| [data.topic_id] | <code>string</code> | ID of the topic when using an existing topic. |
* * *
<a name="module_ChatApi--module.exports+updateChannel"></a>
#### module.exports.updateChannel(channelId, data) ⇒ <code>Promise</code>
Updates a channel.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
| Param | Type | Description |
| --- | --- | --- |
| channelId | <code>number</code> | The ID of the channel. |
| data | <code>object</code> | Params of the archive. |
| [data.description] | <code>string</code> | Description of the channel. |
| [data.name] | <code>string</code> | Name of the channel. |
* * *
<a name="module_ChatApi--module.exports+updateChannelStatus"></a>
#### module.exports.updateChannelStatus(channelId, status) ⇒ <code>Promise</code>
Updates the status of a channel.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
| Param | Type | Description |
| --- | --- | --- |
| channelId | <code>number</code> | The ID of the channel. |
| status | <code>string</code> | The new status, can be "open" or "closed". |
* * *
<a name="module_ChatApi--module.exports+listChannelMemberships"></a>
#### module.exports.listChannelMemberships(channelId) ⇒ [<code>module.exports</code>](#exp_module_Collection--module.exports)
Lists members of a channel.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
| Param | Type | Description |
| --- | --- | --- |
| channelId | <code>number</code> | The ID of the channel. |
* * *
<a name="module_ChatApi--module.exports+listCurrentUserChannels"></a>
#### module.exports.listCurrentUserChannels() ⇒ <code>Promise</code>
Lists public and direct message channels of the current user.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
* * *
<a name="module_ChatApi--module.exports+followChannel"></a>
#### module.exports.followChannel(channelId) ⇒ <code>Promise</code>
Makes current user follow a channel.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
| Param | Type | Description |
| --- | --- | --- |
| channelId | <code>number</code> | The ID of the channel. |
* * *
<a name="module_ChatApi--module.exports+unfollowChannel"></a>
#### module.exports.unfollowChannel(channelId) ⇒ <code>Promise</code>
Makes current user unfollow a channel.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
| Param | Type | Description |
| --- | --- | --- |
| channelId | <code>number</code> | The ID of the channel. |
* * *
<a name="module_ChatApi--module.exports+updateCurrentUserChannelNotificationsSettings"></a>
#### module.exports.updateCurrentUserChannelNotificationsSettings(channelId, data) ⇒ <code>Promise</code>
Update notifications settings of current user for a channel.
**Kind**: instance method of [<code>module.exports</code>](#exp_module_ChatApi--module.exports)
| Param | Type | Description |
| --- | --- | --- |
| channelId | <code>number</code> | The ID of the channel. |
| data | <code>object</code> | The settings to modify. |
| [data.muted] | <code>boolean</code> | Mutes the channel. |
| [data.desktop_notification_level] | <code>string</code> | Notifications level on desktop: never, mention or always. |
| [data.mobile_notification_level] | <code>string</code> | Notifications level on mobile: never, mention or always. |
* * *

View File

@ -0,0 +1,109 @@
# frozen_string_literal: true
#
# = Chat::Endpoint
#
# This class is to be used via its helper +with_service+ in a controller. Its
# main purpose is to ease how actions can be run upon a service completion.
# Since a service will likely return the same kind of things over and over,
# this allows us to not have to repeat the same boilerplate code in every
# controller.
#
# There are several available actions and we can add new ones very easily:
#
# * +on_success+: will execute the provided block if the service succeeds
# * +on_failure+: will execute the provided block if the service fails
# * +on_failed_policy(name)+: will execute the provided block if the policy
# named `name` fails
# * +on_failed_contract(name)+: will execute the provided block if the contract
# named `name` fails
# * +on_model_not_found(name)+: will execute the provided block if the service
# fails and its model is not present
#
# @example
# # in a controller
# def create
# with_service MyService do
# on_success do
# flash[:notice] = "Success!"
# redirect_to a_path
# end
# on_failed_policy(:a_named_policy) { redirect_to root_path }
# on_failure { render :new }
# end
# end
#
# The actions will be evaluated in the order they appear. So even if the
# service will ultimately fail with a failed policy, in this example only the
# +on_failed_policy+ action will be executed and not the +on_failure+ one.
# The only exception to this being +on_failure+ as it will always be executed
# last.
#
class Chat::Endpoint
# @!visibility private
NULL_RESULT = OpenStruct.new(failure?: false)
# @!visibility private
AVAILABLE_ACTIONS = {
on_success: -> { result.success? },
on_failure: -> { result.failure? },
on_failed_policy: ->(name = "default") { failure_for?("result.policy.#{name}") },
on_failed_contract: ->(name = "default") { failure_for?("result.contract.#{name}") },
on_model_not_found: ->(name = "model") { failure_for?("result.model.#{name}") },
}.with_indifferent_access.freeze
# @!visibility private
attr_reader :service, :controller, :dependencies
delegate :result, to: :controller
# @!visibility private
def initialize(service, controller, **dependencies)
@service = service
@controller = controller
@dependencies = dependencies
@actions = {}
end
# @param service [Class] a class including {Chat::Service::Base}
# @param block [Proc] a block containing the steps to match on
# @return [void]
def self.call(service, controller, **dependencies, &block)
new(service, controller, **dependencies).call(&block)
end
# @!visibility private
def call(&block)
instance_eval(&block)
controller.run_service(service, dependencies)
# Always have `on_failure` as the last action
(
actions
.except(:on_failure)
.merge(actions.slice(:on_failure))
.detect { |name, (condition, _)| condition.call } || [-> {}]
).flatten.last.call
end
private
attr_reader :actions
def failure_for?(key)
(controller.result[key] || NULL_RESULT).failure?
end
def add_action(name, *args, &block)
actions[[name, *args].join("_").to_sym] = [
-> { instance_exec(*args, &AVAILABLE_ACTIONS[name]) },
-> { controller.instance_eval(&block) },
]
end
def method_missing(method_name, *args, &block)
return super unless AVAILABLE_ACTIONS[method_name]
add_action(method_name, *args, &block)
end
def respond_to_missing?(method_name, include_private = false)
AVAILABLE_ACTIONS[method_name] || super
end
end

View File

@ -61,6 +61,7 @@ module Chat::GuardianExtensions
return false if chat_channel.status.to_sym == target_status.to_sym
return false if !is_staff?
# FIXME: This logic shouldn't be handled in guardian
case target_status
when :closed
chat_channel.open?

View File

@ -0,0 +1,112 @@
# frozen_string_literal: true
module Chat
# = Chat::StepsInspector
#
# This class takes a {Chat::Service::Base::Context} object and inspects it.
# It will output a list of steps and what is their known state.
class StepsInspector
# @!visibility private
class Step
attr_reader :step, :result, :nesting_level
delegate :name, to: :step
delegate :failure?, :success?, :error, to: :step_result, allow_nil: true
def self.for(step, result, nesting_level: 0)
class_name =
"#{module_parent_name}::#{step.class.name.split("::").last.sub(/^(\w+)Step$/, "\\1")}"
class_name.constantize.new(step, result, nesting_level: nesting_level)
end
def initialize(step, result, nesting_level: 0)
@step = step
@result = result
@nesting_level = nesting_level
end
def type
self.class.name.split("::").last.downcase
end
def emoji
return "" if failure?
return "" if success?
""
end
def steps
[self]
end
def inspect
"#{" " * nesting_level}[#{type}] '#{name}' #{emoji}"
end
private
def step_result
result["result.#{type}.#{name}"]
end
end
# @!visibility private
class Model < Step
def error
step_result.exception.full_message
end
end
# @!visibility private
class Contract < Step
def error
step_result.errors.inspect
end
end
# @!visibility private
class Policy < Step
end
# @!visibility private
class Transaction < Step
def steps
[self, *step.steps.map { Step.for(_1, result, nesting_level: nesting_level + 1).steps }]
end
def inspect
"#{" " * nesting_level}[#{type}]"
end
def step_result
nil
end
end
attr_reader :steps, :result
def initialize(result)
@steps = result.__steps__.map { Step.for(_1, result).steps }.flatten
@result = result
end
# Inspect the provided result object.
# Example output:
# [1/4] [model] 'channel' ✅
# [2/4] [contract] 'default' ✅
# [3/4] [policy] 'check_channel_permission' ❌
# [4/4] [step] 'change_status'
# @return [String] the steps of the result object with their state
def inspect
steps
.map
.with_index { |step, index| "[#{index + 1}/#{steps.size}] #{step.inspect}" }
.join("\n")
end
# @return [String, nil] the first available error, if any.
def error
steps.detect(&:failure?)&.error
end
end
end

View File

@ -1,13 +0,0 @@
# frozen_string_literal: true
task "chat:doc" do
destination = File.join(Rails.root, "plugins/chat/docs/FRONTEND.md")
config = File.join(Rails.root, ".jsdoc")
files = %w[
plugins/chat/assets/javascripts/discourse/lib/collection.js
plugins/chat/assets/javascripts/discourse/services/chat-api.js
]
`yarn --silent jsdoc2md --separators -c #{config} -f #{files.join(" ")} > #{destination}`
end

View File

@ -91,6 +91,7 @@ require_relative "app/core_ext/plugin_instance.rb"
GlobalSetting.add_default(:allow_unsecure_chat_uploads, false)
after_initialize do
# Namespace for classes and modules parts of chat plugin
module ::Chat
PLUGIN_NAME = "chat"
HAS_CHAT_ENABLED = "has_chat_enabled"
@ -119,6 +120,7 @@ after_initialize do
"../app/controllers/admin/admin_incoming_chat_webhooks_controller.rb",
__FILE__,
)
load File.expand_path("../app/helpers/with_service_helper.rb", __FILE__)
load File.expand_path("../app/controllers/chat_base_controller.rb", __FILE__)
load File.expand_path("../app/controllers/chat_controller.rb", __FILE__)
load File.expand_path("../app/controllers/emojis_controller.rb", __FILE__)
@ -163,6 +165,7 @@ after_initialize do
load File.expand_path("../app/serializers/admin_chat_index_serializer.rb", __FILE__)
load File.expand_path("../app/serializers/user_chat_message_bookmark_serializer.rb", __FILE__)
load File.expand_path("../app/serializers/reviewable_chat_message_serializer.rb", __FILE__)
load File.expand_path("../app/services/base.rb", __FILE__)
load File.expand_path("../lib/chat_channel_fetcher.rb", __FILE__)
load File.expand_path("../lib/chat_channel_hashtag_data_source.rb", __FILE__)
load File.expand_path("../lib/chat_mailer.rb", __FILE__)
@ -191,6 +194,8 @@ after_initialize do
load File.expand_path("../lib/slack_compatibility.rb", __FILE__)
load File.expand_path("../lib/post_notification_handler.rb", __FILE__)
load File.expand_path("../lib/secure_uploads_compatibility.rb", __FILE__)
load File.expand_path("../lib/endpoint.rb", __FILE__)
load File.expand_path("../lib/steps_inspector.rb", __FILE__)
load File.expand_path("../app/jobs/regular/auto_manage_channel_memberships.rb", __FILE__)
load File.expand_path("../app/jobs/regular/auto_join_channel_batch.rb", __FILE__)
load File.expand_path("../app/jobs/regular/process_chat_message.rb", __FILE__)
@ -207,7 +212,11 @@ after_initialize do
load File.expand_path("../app/jobs/scheduled/auto_join_users.rb", __FILE__)
load File.expand_path("../app/jobs/scheduled/chat_periodical_updates.rb", __FILE__)
load File.expand_path("../app/services/chat_publisher.rb", __FILE__)
load File.expand_path("../app/services/trash_channel.rb", __FILE__)
load File.expand_path("../app/services/update_channel.rb", __FILE__)
load File.expand_path("../app/services/update_channel_status.rb", __FILE__)
load File.expand_path("../app/services/chat_message_destroyer.rb", __FILE__)
load File.expand_path("../app/services/update_user_last_read.rb", __FILE__)
load File.expand_path("../app/controllers/api_controller.rb", __FILE__)
load File.expand_path("../app/controllers/api/chat_channels_controller.rb", __FILE__)
load File.expand_path("../app/controllers/api/chat_current_user_channels_controller.rb", __FILE__)

View File

@ -59,9 +59,50 @@ Fabricator(:chat_message) do
end
Fabricator(:chat_mention) do
chat_message { Fabricate(:chat_message) }
transient read: false
transient high_priority: true
transient identifier: :direct_mentions
user { Fabricate(:user) }
notification { Fabricate(:notification) }
chat_message { Fabricate(:chat_message) }
notification do |attrs|
# All this setup should be in a service we could just call here
# At the moment the logic is all split in a job
channel = attrs[:chat_message].chat_channel
payload = {
is_direct_message_channel: channel.direct_message_channel?,
mentioned_by_username: attrs[:chat_message].user.username,
chat_channel_id: channel.id,
chat_message_id: attrs[:chat_message].id,
}
if channel.direct_message_channel?
payload[:chat_channel_title] = channel.title(membership.user)
payload[:chat_channel_slug] = channel.slug
end
unless attrs[:identifier] == :direct_mentions
case attrs[:identifier]
when :here_mentions
payload[:identifier] = "here"
when :global_mentions
payload[:identifier] = "all"
else
payload[:identifier] = attrs[:identifier] if attrs[:identifier]
payload[:is_group_mention] = true
end
end
Fabricate(
:notification,
notification_type: Notification.types[:chat_mention],
user: attrs[:user],
data: payload.to_json,
read: attrs[:read],
high_priority: attrs[:high_priority],
)
end
end
Fabricator(:chat_message_reaction) do

View File

@ -0,0 +1,242 @@
# frozen_string_literal: true
RSpec.describe Chat::Endpoint do
class SuccessService
include Chat::Service::Base
end
class FailureService
include Chat::Service::Base
step :fail_step
def fail_step
fail!("error")
end
end
class FailedPolicyService
include Chat::Service::Base
policy :test
def test
false
end
end
class SuccessPolicyService
include Chat::Service::Base
policy :test
def test
true
end
end
class FailedContractService
include Chat::Service::Base
class Contract
attribute :test
validates :test, presence: true
end
contract
end
class SuccessContractService
include Chat::Service::Base
contract
end
class FailureWithModelService
include Chat::Service::Base
model :fake_model, :fetch_fake_model
private
def fetch_fake_model
nil
end
end
class SuccessWithModelService
include Chat::Service::Base
model :fake_model, :fetch_fake_model
private
def fetch_fake_model
:model_found
end
end
describe ".call(service, &block)" do
subject(:endpoint) { described_class.call(service, controller, &actions_block) }
let(:result) { controller.result }
let(:actions_block) { controller.instance_eval(actions) }
let(:service) { SuccessService }
let(:actions) { "proc {}" }
let(:controller) do
Class
.new(Chat::Api) do
def request
OpenStruct.new
end
def params
ActionController::Parameters.new
end
def guardian
end
end
.new
end
it "runs the provided service in the context of a controller" do
endpoint
expect(result).to be_a Chat::Service::Base::Context
expect(result).to be_a_success
end
context "when using the on_success action" do
let(:actions) { <<-BLOCK }
proc do
on_success { :success }
end
BLOCK
context "when the service succeeds" do
it "runs the provided block" do
expect(endpoint).to eq :success
end
end
context "when the service does not succeed" do
let(:service) { FailureService }
it "does not run the provided block" do
expect(endpoint).not_to eq :success
end
end
end
context "when using the on_failure action" do
let(:actions) { <<-BLOCK }
proc do
on_failure { :fail }
end
BLOCK
context "when the service fails" do
let(:service) { FailureService }
it "runs the provided block" do
expect(endpoint).to eq :fail
end
end
context "when the service does not fail" do
let(:service) { SuccessService }
it "does not run the provided block" do
expect(endpoint).not_to eq :fail
end
end
end
context "when using the on_failed_policy action" do
let(:actions) { <<-BLOCK }
proc do
on_failed_policy(:test) { :policy_failure }
end
BLOCK
context "when the service policy fails" do
let(:service) { FailedPolicyService }
it "runs the provided block" do
expect(endpoint).to eq :policy_failure
end
end
context "when the service policy does not fail" do
let(:service) { SuccessPolicyService }
it "does not run the provided block" do
expect(endpoint).not_to eq :policy_failure
end
end
end
context "when using the on_failed_contract action" do
let(:actions) { <<-BLOCK }
proc do
on_failed_contract { :contract_failure }
end
BLOCK
context "when the service contract fails" do
let(:service) { FailedContractService }
it "runs the provided block" do
expect(endpoint).to eq :contract_failure
end
end
context "when the service contract does not fail" do
let(:service) { SuccessContractService }
it "does not run the provided block" do
expect(endpoint).not_to eq :contract_failure
end
end
end
context "when using the on_model_not_found action" do
let(:actions) { <<-BLOCK }
->(*) do
on_model_not_found(:fake_model) { :no_model }
end
BLOCK
context "when the service failed without a model" do
let(:service) { FailureWithModelService }
it "runs the provided block" do
expect(endpoint).to eq :no_model
end
end
context "when the service does not fail with a model" do
let(:service) { SuccessWithModelService }
it "does not run the provided block" do
expect(endpoint).not_to eq :no_model
end
end
end
context "when using several actions together" do
let(:service) { FailureService }
let(:actions) { <<-BLOCK }
proc do
on_success { :success }
on_failure { :failure }
on_failed_policy { :policy_failure }
end
BLOCK
it "runs the first matching action" do
expect(endpoint).to eq :failure
end
end
end
end

View File

@ -0,0 +1,175 @@
# frozen_string_literal: true
RSpec.describe Chat::StepsInspector do
class DummyService
include Chat::Service::Base
model :model
policy :policy
contract
transaction do
step :in_transaction_step_1
step :in_transaction_step_2
end
step :final_step
class Contract
attribute :parameter
validates :parameter, presence: true
end
end
subject(:inspector) { described_class.new(result) }
let(:parameter) { "present" }
let(:result) { DummyService.call(parameter: parameter) }
before do
class DummyService
%i[fetch_model policy in_transaction_step_1 in_transaction_step_2 final_step].each do |name|
define_method(name) { true }
end
end
end
describe "#inspect" do
subject(:output) { inspector.inspect }
context "when service runs without error" do
it "outputs all the steps of the service" do
expect(output).to eq <<~OUTPUT.chomp
[1/7] [model] 'model'
[2/7] [policy] 'policy'
[3/7] [contract] 'default'
[4/7] [transaction]
[5/7] [step] 'in_transaction_step_1'
[6/7] [step] 'in_transaction_step_2'
[7/7] [step] 'final_step'
OUTPUT
end
end
context "when the model step is failing" do
before do
class DummyService
def fetch_model
false
end
end
end
it "shows the failing step" do
expect(output).to eq <<~OUTPUT.chomp
[1/7] [model] 'model'
[2/7] [policy] 'policy'
[3/7] [contract] 'default'
[4/7] [transaction]
[5/7] [step] 'in_transaction_step_1'
[6/7] [step] 'in_transaction_step_2'
[7/7] [step] 'final_step'
OUTPUT
end
end
context "when the policy step is failing" do
before do
class DummyService
def policy
false
end
end
end
it "shows the failing step" do
expect(output).to eq <<~OUTPUT.chomp
[1/7] [model] 'model'
[2/7] [policy] 'policy'
[3/7] [contract] 'default'
[4/7] [transaction]
[5/7] [step] 'in_transaction_step_1'
[6/7] [step] 'in_transaction_step_2'
[7/7] [step] 'final_step'
OUTPUT
end
end
context "when the contract step is failing" do
let(:parameter) { nil }
it "shows the failing step" do
expect(output).to eq <<~OUTPUT.chomp
[1/7] [model] 'model'
[2/7] [policy] 'policy'
[3/7] [contract] 'default'
[4/7] [transaction]
[5/7] [step] 'in_transaction_step_1'
[6/7] [step] 'in_transaction_step_2'
[7/7] [step] 'final_step'
OUTPUT
end
end
context "when a common step is failing" do
before do
class DummyService
def in_transaction_step_2
fail!("step error")
end
end
end
it "shows the failing step" do
expect(output).to eq <<~OUTPUT.chomp
[1/7] [model] 'model'
[2/7] [policy] 'policy'
[3/7] [contract] 'default'
[4/7] [transaction]
[5/7] [step] 'in_transaction_step_1'
[6/7] [step] 'in_transaction_step_2'
[7/7] [step] 'final_step'
OUTPUT
end
end
end
describe "#error" do
subject(:error) { inspector.error }
context "when there are no errors" do
it "returns nothing" do
expect(error).to be_blank
end
end
context "when the model step is failing" do
before do
class DummyService
def fetch_model
false
end
end
end
it "returns an error related to the model" do
expect(error).to match(/Model not found/)
end
end
context "when the contract step is failing" do
let(:parameter) { nil }
it "returns an error related to the contract" do
expect(error).to match(/ActiveModel::Error attribute=parameter, type=blank, options={}/)
end
end
context "when a common step is failing" do
before { result["result.step.final_step"].fail(error: "my error") }
it "returns an error related to the step" do
expect(error).to eq("my error")
end
end
end
end

View File

@ -22,4 +22,7 @@ module ChatSystemHelpers
end
end
RSpec.configure { |config| config.include ChatSystemHelpers, type: :system }
RSpec.configure do |config|
config.include ChatSystemHelpers, type: :system
config.include Chat::ServiceMatchers
end

View File

@ -170,12 +170,7 @@ RSpec.describe Chat::Api::ChatChannelsController do
before { sign_in(current_user) }
it "returns an error" do
delete "/chat/api/channels/#{channel_1.id}",
params: {
channel: {
name_confirmation: channel_1.title(current_user),
},
}
delete "/chat/api/channels/#{channel_1.id}"
expect(response.status).to eq(403)
end
@ -190,38 +185,15 @@ RSpec.describe Chat::Api::ChatChannelsController do
before { channel_1.destroy! }
it "returns an error" do
delete "/chat/api/channels/#{channel_1.id}",
params: {
channel: {
name_confirmation: channel_1.title(current_user),
},
}
delete "/chat/api/channels/#{channel_1.id}"
expect(response.status).to eq(404)
end
end
context "when the confirmation doesnt match the channel name" do
it "returns an error" do
delete "/chat/api/channels/#{channel_1.id}",
params: {
channel: {
name_confirmation: channel_1.title(current_user) + "foo",
},
}
expect(response.status).to eq(400)
end
end
context "with valid params" do
it "properly destroys the channel" do
delete "/chat/api/channels/#{channel_1.id}",
params: {
channel: {
name_confirmation: channel_1.title(current_user),
},
}
delete "/chat/api/channels/#{channel_1.id}"
expect(response.status).to eq(200)
expect(channel_1.reload.trashed?).to eq(true)
@ -243,14 +215,7 @@ RSpec.describe Chat::Api::ChatChannelsController do
freeze_time(DateTime.parse("2022-07-08 09:30:00"))
old_slug = channel_1.slug
delete(
"/chat/api/channels/#{channel_1.id}",
params: {
channel: {
name_confirmation: channel_1.title(current_user),
},
},
)
delete "/chat/api/channels/#{channel_1.id}"
expect(response.status).to eq(200)
expect(channel_1.reload.slug).to eq(
@ -371,7 +336,13 @@ RSpec.describe Chat::Api::ChatChannelsController do
before { sign_in(Fabricate(:user)) }
it "returns a 403" do
put "/chat/api/channels/#{channel.id}"
put "/chat/api/channels/#{channel.id}",
params: {
channel: {
name: "joffrey",
description: "cat owner",
},
}
expect(response.status).to eq(403)
end
@ -400,7 +371,7 @@ RSpec.describe Chat::Api::ChatChannelsController do
it "nullifies the field and doesnt store an empty string" do
put "/chat/api/channels/#{channel.id}", params: { channel: { name: " " } }
expect(channel.reload.name).to be_nil
expect(channel.reload.name).to eq(nil)
end
it "doesnt nullify the description" do
@ -421,7 +392,7 @@ RSpec.describe Chat::Api::ChatChannelsController do
it "nullifies the field and doesnt store an empty string" do
put "/chat/api/channels/#{channel.id}", params: { channel: { description: " " } }
expect(channel.reload.description).to be_nil
expect(channel.reload.description).to eq(nil)
end
it "doesnt nullify the name" do

View File

@ -0,0 +1,54 @@
# frozen_string_literal: true
RSpec.describe(Chat::Service::TrashChannel) do
subject(:result) { described_class.call(guardian: guardian) }
let(:guardian) { Guardian.new(current_user) }
context "when channel_id is not provided" do
fab!(:current_user) { Fabricate(:admin) }
it { is_expected.to fail_to_find_a_model(:channel) }
end
context "when channel_id is provided" do
subject(:result) { described_class.call(channel_id: channel.id, guardian: guardian) }
fab!(:channel) { Fabricate(:chat_channel) }
context "when user is not allowed to perform the action" do
fab!(:current_user) { Fabricate(:user) }
it { is_expected.to fail_a_policy(:invalid_access) }
end
context "when user is allowed to perform the action" do
fab!(:current_user) { Fabricate(:admin) }
it "sets the service result as successful" do
expect(result).to be_a_success
end
it "trashes the channel" do
expect(result[:channel]).to be_trashed
end
it "logs the action" do
expect { result }.to change { UserHistory.count }.by(1)
expect(UserHistory.last).to have_attributes(
custom_type: "chat_channel_delete",
details:
"chat_channel_id: #{result[:channel].id}\nchat_channel_name: #{result[:channel].title(guardian.user)}",
)
end
it "changes the slug to prevent colisions" do
expect(result[:channel].slug).to include("deleted")
end
it "queues a job to delete channel relations" do
expect { result }.to change(Jobs::ChatChannelDelete.jobs, :size).by(1)
end
end
end
end

View File

@ -0,0 +1,107 @@
# frozen_string_literal: true
RSpec.describe Chat::Service::UpdateChannel do
subject(:result) { described_class.call(guardian: guardian, channel_id: channel.id, **params) }
fab!(:channel) { Fabricate(:chat_channel) }
fab!(:current_user) { Fabricate(:admin) }
let(:guardian) { Guardian.new(current_user) }
let(:params) do
{
name: "cool channel",
description: "a channel description",
slug: "snail",
allow_channel_wide_mentions: true,
auto_join_users: false,
}
end
context "when the user cannot edit the channel" do
fab!(:current_user) { Fabricate(:user) }
it { is_expected.to fail_a_policy(:check_channel_permission) }
end
context "when the user tries to edit a DM channel" do
fab!(:channel) { Fabricate(:direct_message_channel, users: [current_user, Fabricate(:user)]) }
it { is_expected.to fail_a_policy(:no_direct_message_channel) }
end
context "when channel is a category one" do
context "when a valid user provides valid params" do
let(:message) do
MessageBus.track_publish(ChatPublisher::CHANNEL_EDITS_MESSAGE_BUS_CHANNEL) { result }.first
end
it "sets the service result as successful" do
expect(result).to be_a_success
end
it "updates the channel accordingly" do
result
expect(channel.reload).to have_attributes(
name: "cool channel",
slug: "snail",
description: "a channel description",
allow_channel_wide_mentions: true,
auto_join_users: false,
)
end
it "publishes a MessageBus message" do
expect(message.data).to eq(
{
chat_channel_id: channel.id,
name: "cool channel",
description: "a channel description",
slug: "snail",
},
)
end
context "when the name is blank" do
before { params[:name] = "" }
it "nils out the name" do
result
expect(channel.reload.name).to be_nil
end
end
context "when the description is blank" do
before do
channel.update!(description: "something")
params[:description] = ""
end
it "nils out the description" do
result
expect(channel.reload.description).to be_nil
end
end
context "when auto_join_users is set to 'true'" do
before do
channel.update!(auto_join_users: false)
params[:auto_join_users] = true
end
it "updates the model accordingly" do
result
expect(channel.reload).to have_attributes(auto_join_users: true)
end
it "auto joins users" do
expect_enqueued_with(
job: :auto_manage_channel_memberships,
args: {
chat_channel_id: channel.id,
},
) { result }
end
end
end
end
end

View File

@ -0,0 +1,54 @@
# frozen_string_literal: true
RSpec.describe(Chat::Service::UpdateChannelStatus) do
subject(:result) do
described_class.call(guardian: guardian, channel_id: channel.id, status: status)
end
fab!(:channel) { Fabricate(:chat_channel) }
fab!(:current_user) { Fabricate(:admin) }
let(:guardian) { Guardian.new(current_user) }
let(:status) { "open" }
context "when no channel_id is given" do
subject(:result) { described_class.call(guardian: guardian, status: status) }
it { is_expected.to fail_to_find_a_model(:channel) }
end
context "when user is not allowed to change channel status" do
fab!(:current_user) { Fabricate(:user) }
it { is_expected.to fail_a_policy(:check_channel_permission) }
end
context "when status is not allowed" do
(ChatChannel.statuses.keys - ChatChannel.editable_statuses.keys).each do |na_status|
context "when status is '#{na_status}'" do
let(:status) { na_status }
it { is_expected.to fail_a_contract }
end
end
end
context "when new status is the same than the existing one" do
let(:status) { channel.status }
it { is_expected.to fail_a_policy(:check_channel_permission) }
end
context "when status is allowed" do
let(:status) { "closed" }
it "sets the service result as successful" do
expect(result).to be_a_success
end
it "changes the status" do
result
expect(channel.reload).to be_closed
end
end
end

View File

@ -0,0 +1,118 @@
# frozen_string_literal: true
RSpec.describe(Chat::Service::UpdateUserLastRead) do
subject(:result) { described_class.call(params) }
fab!(:current_user) { Fabricate(:user) }
fab!(:channel) { Fabricate(:chat_channel) }
fab!(:membership) do
Fabricate(:user_chat_channel_membership, user: current_user, chat_channel: channel)
end
fab!(:message_1) { Fabricate(:chat_message, chat_channel: membership.chat_channel) }
let(:guardian) { Guardian.new(current_user) }
let(:params) do
{
guardian: guardian,
user_id: current_user.id,
channel_id: channel.id,
message_id: message_1.id,
}
end
context "when channel_id is not provided" do
before { params.delete(:channel_id) }
it { is_expected.to fail_to_find_a_model(:membership) }
end
context "when user_id is not provided" do
before { params.delete(:user_id) }
it { is_expected.to fail_to_find_a_model(:membership) }
end
context "when user has no membership" do
before { membership.destroy! }
it { is_expected.to fail_to_find_a_model(:membership) }
end
context "when user cant access the channel" do
fab!(:membership) do
Fabricate(
:user_chat_channel_membership,
user: current_user,
chat_channel: Fabricate(:private_category_channel),
)
end
before { params[:channel_id] = membership.chat_channel.id }
it { is_expected.to fail_a_policy(:invalid_access) }
end
context "when message_id is older than membership's last_read_message_id" do
before do
params[:message_id] = -2
membership.update!(last_read_message_id: -1)
end
it { is_expected.to fail_a_policy(:ensure_message_id_recency) }
end
context "when message doesnt exist" do
before do
params[:message_id] = 2
membership.update!(last_read_message_id: 1)
end
it { is_expected.to fail_a_policy(:ensure_message_exists) }
end
context "when params are valid" do
before { Jobs.run_immediately! }
it "sets the service result as successful" do
expect(result).to be_a_success
end
it "updates the last_read message id" do
expect { result }.to change { membership.reload.last_read_message_id }.to(message_1.id)
end
it "marks existing notifications related to the message as read" do
expect {
notification =
Fabricate(
:notification,
notification_type: Notification.types[:chat_mention],
user: current_user,
)
# FIXME: we need a better way to create proper chat mention
ChatMention.create!(notification: notification, user: current_user, chat_message: message_1)
}.to change {
Notification.where(
notification_type: Notification.types[:chat_mention],
user: current_user,
read: false,
).count
}.by(1)
expect { result }.to change {
Notification.where(
notification_type: Notification.types[:chat_mention],
user: current_user,
read: false,
).count
}.by(-1)
end
it "publishes new last read to clients" do
messages = MessageBus.track_publish { result }
expect(messages.map(&:channel)).to include("/chat/user-tracking-state/#{current_user.id}")
end
end
end

View File

@ -0,0 +1,135 @@
# frozen_string_literal: true
module Chat
module ServiceMatchers
class FailStep
attr_reader :name, :result
def initialize(name)
@name = name
end
def matches?(result)
@result = result
step_exists? && step_failed? && service_failed?
end
def failure_message
message =
if !step_exists?
"Expected #{type} '#{name}' (key: '#{step}') was not found in the result object."
elsif !step_failed?
"Expected #{type} '#{name}' (key: '#{step}') to fail but it succeeded."
else
"expected the service to fail but it succeeded."
end
error_message_with_inspection(message)
end
def failure_message_when_negated
message = "Expected #{type} '#{name}' (key: '#{step}') to succeed but it failed."
error_message_with_inspection(message)
end
private
def step_exists?
result[step].present?
end
def step_failed?
result[step].failure?
end
def service_failed?
result.failure?
end
def type
"step"
end
def error_message_with_inspection(message)
inspector = StepsInspector.new(result)
"#{message}\n\n#{inspector.inspect}\n\n#{inspector.error}"
end
end
class FailContract < FailStep
attr_reader :error_message
def step
"result.contract.#{name}"
end
def type
"contract"
end
def matches?(service)
super && has_error?
end
def has_error?
result[step].errors.present?
end
def failure_message
return "expected contract '#{step}' to have errors" unless has_error?
super
end
def description
"fail a contract named '#{name}'"
end
end
class FailPolicy < FailStep
def type
"policy"
end
def step
"result.policy.#{name}"
end
def description
"fail a policy named '#{name}'"
end
end
class FailToFindModel < FailStep
def type
"model"
end
def step
"result.model.#{name}"
end
def description
"fail to find a model named '#{name}'"
end
end
def fail_a_policy(name)
FailPolicy.new(name)
end
def fail_a_contract(name = "default")
FailContract.new(name)
end
def fail_to_find_a_model(name = "model")
FailToFindModel.new(name)
end
def inspect_steps(result)
inspector = Chat::StepsInspector.new(result)
puts "Steps:"
puts inspector.inspect
puts "\nFirst error:"
puts inspector.error
end
end
end

428
yarn.lock
View File

@ -567,13 +567,6 @@ ajv@^6.10.0, ajv@^6.12.4:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ansi-escape-sequences@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz#2483c8773f50dd9174dd9557e92b1718f1816097"
integrity sha512-dzW9kHxH011uBsidTXd14JXgzye/YLb2LzeKZ4bsgl/Knwx8AtbSFkkGxagdNOoh0DlqHCmfiEjWKBaqjOanVw==
dependencies:
array-back "^3.0.1"
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
@ -604,40 +597,6 @@ aria-query@^5.0.0:
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c"
integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==
array-back@^1.0.2, array-back@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/array-back/-/array-back-1.0.4.tgz#644ba7f095f7ffcf7c43b5f0dc39d3c1f03c063b"
integrity sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==
dependencies:
typical "^2.6.0"
array-back@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/array-back/-/array-back-2.0.0.tgz#6877471d51ecc9c9bfa6136fb6c7d5fe69748022"
integrity sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==
dependencies:
typical "^2.6.1"
array-back@^3.0.1, array-back@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0"
integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==
array-back@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e"
integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==
array-back@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/array-back/-/array-back-5.0.0.tgz#e196609edcec48376236d163958df76e659a0d36"
integrity sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==
array-back@^6.2.2:
version "6.2.2"
resolved "https://registry.yarnpkg.com/array-back/-/array-back-6.2.2.tgz#f567d99e9af88a6d3d2f9dfcc21db6f9ba9fd157"
integrity sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==
array-union@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
@ -725,15 +684,6 @@ buffer@^5.2.1, buffer@^5.5.0:
base64-js "^1.3.1"
ieee754 "^1.1.13"
cache-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/cache-point/-/cache-point-2.0.0.tgz#91e03c38da9cfba9d95ac6a34d24cfe6eff8920f"
integrity sha512-4gkeHlFpSKgm3vm2gJN5sPqfmijYRFYCQ6tv5cLw0xVmT6r1z1vd4FNnpuOREco3cBs1G709sZ72LdgddKvL5w==
dependencies:
array-back "^4.0.1"
fs-then-native "^2.0.0"
mkdirp2 "^1.0.4"
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@ -832,14 +782,6 @@ clone@^1.0.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
collect-all@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/collect-all/-/collect-all-1.0.4.tgz#50cd7119ac24b8e12a661f0f8c3aa0ea7222ddfc"
integrity sha512-RKZhRwJtJEP5FWul+gkSMEnaK6H3AGPTTWOiRimCcs+rc/OmQE3Yhy1Q7A7KsdkG3ZXVdZq68Y6ONSdvkeEcKA==
dependencies:
stream-connect "^1.0.2"
stream-via "^1.0.4"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@ -869,37 +811,6 @@ colors@^1.4.0:
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
command-line-args@^5.0.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e"
integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==
dependencies:
array-back "^3.1.0"
find-replace "^3.0.0"
lodash.camelcase "^4.3.0"
typical "^4.0.0"
command-line-tool@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/command-line-tool/-/command-line-tool-0.8.0.tgz#b00290ef1dfc11cc731dd1f43a92cfa5f21e715b"
integrity sha512-Xw18HVx/QzQV3Sc5k1vy3kgtOeGmsKIqwtFFoyjI4bbcpSgnw2CWVULvtakyw4s6fhyAdI6soQQhXc2OzJy62g==
dependencies:
ansi-escape-sequences "^4.0.0"
array-back "^2.0.0"
command-line-args "^5.0.0"
command-line-usage "^4.1.0"
typical "^2.6.1"
command-line-usage@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-4.1.0.tgz#a6b3b2e2703b4dcf8bd46ae19e118a9a52972882"
integrity sha512-MxS8Ad995KpdAC0Jopo/ovGIroV/m0KHwzKfXxKag6FHOkGsH8/lv5yjgablcRxCJJC0oJeUMuO/gmaq+Wq46g==
dependencies:
ansi-escape-sequences "^4.0.0"
array-back "^2.0.0"
table-layout "^0.4.2"
typical "^2.6.1"
commander@2.11.x:
version "2.11.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
@ -915,23 +826,11 @@ commander@^8.3.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
common-sequence@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/common-sequence/-/common-sequence-2.0.2.tgz#accc76bdc5876a1fcd92b73484d4285fff99d838"
integrity sha512-jAg09gkdkrDO9EWTdXfv80WWH3yeZl5oT69fGfedBNS9pXUKYInVJ1bJ+/ht2+Moeei48TmSbQDYMc8EOx9G0g==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
config-master@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/config-master/-/config-master-3.1.0.tgz#667663590505a283bf26a484d68489d74c5485da"
integrity sha512-n7LBL1zBzYdTpF1mx5DNcZnZn05CWIdsdvtPL4MosvqbBUK3Rq6VWEtGUuF3Y0s9/CIhMejezqlSkP6TnCJ/9g==
dependencies:
walk-back "^2.0.1"
convert-source-map@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
@ -992,11 +891,6 @@ debug@^2.6.8:
dependencies:
ms "2.0.0"
deep-extend@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
@ -1026,24 +920,6 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
dmd@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/dmd/-/dmd-6.2.0.tgz#d267a9fb1ce62b74edca8bf5bcbd3b8e08574fe7"
integrity sha512-uXWxLF1H7TkUAuoHK59/h/ts5cKavm2LnhrIgJWisip4BVzPoXavlwyoprFFn2CzcahKYgvkfaebS6oxzgflkg==
dependencies:
array-back "^6.2.2"
cache-point "^2.0.0"
common-sequence "^2.0.2"
file-set "^4.0.2"
handlebars "^4.7.7"
marked "^4.2.3"
object-get "^2.1.1"
reduce-flatten "^3.0.1"
reduce-unique "^2.0.1"
reduce-without "^1.0.1"
test-value "^3.0.0"
walk-back "^5.1.0"
doctrine@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
@ -1416,14 +1292,6 @@ file-entry-cache@^6.0.1:
dependencies:
flat-cache "^3.0.4"
file-set@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/file-set/-/file-set-4.0.2.tgz#8d67c92a864202c2085ac9f03f1c9909c7e27030"
integrity sha512-fuxEgzk4L8waGXaAkd8cMr73Pm0FxOVkn8hztzUW7BAHhOGH90viQNXbiOsnecCWmfInqU6YmAMwxRMdKETceQ==
dependencies:
array-back "^5.0.0"
glob "^7.1.6"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@ -1431,13 +1299,6 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
find-replace@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38"
integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==
dependencies:
array-back "^3.0.1"
find-up@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
@ -1490,11 +1351,6 @@ fs-extra@^9.1.0:
jsonfile "^6.0.1"
universalify "^2.0.0"
fs-then-native@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fs-then-native/-/fs-then-native-2.0.0.tgz#19a124d94d90c22c8e045f2e8dd6ebea36d48c67"
integrity sha512-X712jAOaWXkemQCAmWeg5rOT2i+KOpWz1Z/txk/cW0qlOu2oQ9H61vc5w3X/iyuUEfq/OyaFJ78/cZAQD1/bgA==
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@ -1567,7 +1423,7 @@ glob-stream@^7.0.0:
to-absolute-glob "^2.0.2"
unique-stream "^2.3.1"
glob@^7.1.3, glob@^7.1.6, glob@^7.2.0:
glob@^7.1.3, glob@^7.2.0:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@ -1619,18 +1475,6 @@ graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
handlebars@^4.7.7:
version "4.7.7"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
dependencies:
minimist "^1.2.5"
neo-async "^2.6.0"
source-map "^0.6.1"
wordwrap "^1.0.0"
optionalDependencies:
uglify-js "^3.1.4"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@ -1692,6 +1536,11 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==
is-absolute@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576"
@ -1804,45 +1653,26 @@ js2xmlparser@^4.0.2:
dependencies:
xmlcreate "^2.0.4"
jsdoc-api@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/jsdoc-api/-/jsdoc-api-8.0.0.tgz#4b2c25ff60f91b80da51b6cd33943acc7b2cab74"
integrity sha512-Rnhor0suB1Ds1abjmFkFfKeD+kSMRN9oHMTMZoJVUrmtCGDwXty+sWMA9sa4xbe4UyxuPjhC7tavZ40mDKK6QQ==
jsdoc@^3.6.3:
version "3.6.11"
resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.6.11.tgz#8bbb5747e6f579f141a5238cbad4e95e004458ce"
integrity sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==
dependencies:
array-back "^6.2.2"
cache-point "^2.0.0"
collect-all "^1.0.4"
file-set "^4.0.2"
fs-then-native "^2.0.0"
jsdoc "^4.0.0"
object-to-spawn-args "^2.0.1"
temp-path "^1.0.0"
walk-back "^5.1.0"
jsdoc-parse@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/jsdoc-parse/-/jsdoc-parse-6.2.0.tgz#2b71d3925acfc4badc72526f2470766e0561f6b5"
integrity sha512-Afu1fQBEb7QHt6QWX/6eUWvYHJofB90Fjx7FuJYF7mnG9z5BkAIpms1wsnvYLytfmqpEENHs/fax9p8gvMj7dw==
dependencies:
array-back "^6.2.2"
lodash.omit "^4.5.0"
lodash.pick "^4.4.0"
reduce-extract "^1.0.0"
sort-array "^4.1.5"
test-value "^3.0.0"
jsdoc-to-markdown@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/jsdoc-to-markdown/-/jsdoc-to-markdown-8.0.0.tgz#27f32ed200d3b84dbf22a49beed485790f93b3ce"
integrity sha512-2FQvYkg491+FP6s15eFlgSSWs69CvQrpbABGYBtvAvGWy/lWo8IKKToarT283w59rQFrpcjHl3YdhHCa3l7gXg==
dependencies:
array-back "^6.2.2"
command-line-tool "^0.8.0"
config-master "^3.1.0"
dmd "^6.2.0"
jsdoc-api "^8.0.0"
jsdoc-parse "^6.2.0"
walk-back "^5.1.0"
"@babel/parser" "^7.9.4"
"@types/markdown-it" "^12.2.3"
bluebird "^3.7.2"
catharsis "^0.9.0"
escape-string-regexp "^2.0.0"
js2xmlparser "^4.0.2"
klaw "^3.0.0"
markdown-it "^12.3.2"
markdown-it-anchor "^8.4.1"
marked "^4.0.10"
mkdirp "^1.0.4"
requizzle "^0.2.3"
strip-json-comments "^3.1.0"
taffydb "2.6.2"
underscore "~1.13.2"
jsdoc@^4.0.0:
version "4.0.0"
@ -1999,11 +1829,6 @@ locate-path@^7.1.0:
dependencies:
p-locate "^6.0.0"
lodash.camelcase@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
lodash.kebabcase@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
@ -2014,21 +1839,6 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash.omit@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60"
integrity sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==
lodash.padend@^4.6.1:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e"
integrity sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==
lodash.pick@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
@ -2070,7 +1880,7 @@ markdown-it@^12.3.2:
mdurl "^1.0.1"
uc.micro "^1.0.5"
marked@^4.0.10, marked@^4.2.3:
marked@^4.0.10:
version "4.2.5"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.5.tgz#979813dfc1252cc123a79b71b095759a32f42a5d"
integrity sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ==
@ -2115,21 +1925,11 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies:
brace-expansion "^1.1.7"
minimist@^1.2.5:
version "1.2.7"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
mkdirp-classic@^0.5.2:
version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp2@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/mkdirp2/-/mkdirp2-1.0.5.tgz#68bbe61defefafce4b48948608ec0bac942512c2"
integrity sha512-xOE9xbICroUDmG1ye2h4bZ8WBie9EGmACaco8K8cx6RlkJJrxGIqjGqztAI+NMhexXBcdGbSEzI6N3EJPevxZw==
mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
@ -2162,11 +1962,6 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
neo-async@^2.6.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
no-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
@ -2187,16 +1982,6 @@ node-releases@^2.0.5:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666"
integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==
object-get@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/object-get/-/object-get-2.1.1.tgz#1dad63baf6d94df184d1c58756cc9be55b174dac"
integrity sha512-7n4IpLMzGGcLEMiQKsNR7vCe+N5E9LORFrtNUVy4sO3dj9a3HedZCxEL2T7QuLhcHN1NBuBsMOKaOsAYI9IIvg==
object-to-spawn-args@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/object-to-spawn-args/-/object-to-spawn-args-2.0.1.tgz#cf8b8e3c9b3589137a469cac90391f44870144a5"
integrity sha512-6FuKFQ39cOID+BMZ3QaphcC8Y4cw6LXBLyIgPU+OhIYwviJamPAn+4mITapnSBQrejB+NNp+FMskhD8Cq+Ys3w==
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@ -2452,35 +2237,6 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
reduce-extract@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/reduce-extract/-/reduce-extract-1.0.0.tgz#67f2385beda65061b5f5f4312662e8b080ca1525"
integrity sha512-QF8vjWx3wnRSL5uFMyCjDeDc5EBMiryoT9tz94VvgjKfzecHAVnqmXAwQDcr7X4JmLc2cjkjFGCVzhMqDjgR9g==
dependencies:
test-value "^1.0.1"
reduce-flatten@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-1.0.1.tgz#258c78efd153ddf93cb561237f61184f3696e327"
integrity sha512-j5WfFJfc9CoXv/WbwVLHq74i/hdTUpy+iNC534LxczMRP67vJeK3V9JOdnL0N1cIRbn9mYhE2yVjvvKXDxvNXQ==
reduce-flatten@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-3.0.1.tgz#3db6b48ced1f4dbe4f4f5e31e422aa9ff0cd21ba"
integrity sha512-bYo+97BmUUOzg09XwfkwALt4PQH1M5L0wzKerBt6WLm3Fhdd43mMS89HiT1B9pJIqko/6lWx3OnV4J9f2Kqp5Q==
reduce-unique@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/reduce-unique/-/reduce-unique-2.0.1.tgz#fb34b90e89297c1e08d75dcf17e9a6443ea71081"
integrity sha512-x4jH/8L1eyZGR785WY+ePtyMNhycl1N2XOLxhCbzZFaqF4AXjLzqSxa2UHgJ2ZVR/HHyPOvl1L7xRnW8ye5MdA==
reduce-without@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/reduce-without/-/reduce-without-1.0.1.tgz#68ad0ead11855c9a37d4e8256c15bbf87972fc8c"
integrity sha512-zQv5y/cf85sxvdrKPlfcRzlDn/OqKFThNimYmsS3flmkioKvkUGn2Qg9cJVoQiEvdxFGLE0MQER/9fZ9sUqdxg==
dependencies:
test-value "^2.0.0"
regexpp@^3.0.0, regexpp@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
@ -2619,47 +2375,22 @@ snake-case@^3.0.3:
dot-case "^3.0.4"
tslib "^2.0.3"
sort-array@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/sort-array/-/sort-array-4.1.5.tgz#64b92aaba222aec606786f4df28ae4e3e3e68313"
integrity sha512-Ya4peoS1fgFN42RN1REk2FgdNOeLIEMKFGJvs7VTP3OklF8+kl2SkpVliZ4tk/PurWsrWRsdNdU+tgyOBkB9sA==
dependencies:
array-back "^5.0.0"
typical "^6.0.1"
source-map-js@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
squoosh@discourse/squoosh#dc9649d:
version "2.0.0"
resolved "https://codeload.github.com/discourse/squoosh/tar.gz/dc9649d0a4d396d1251c22291b17d99f1716da44"
dependencies:
wasm-feature-detect "^1.2.11"
stream-connect@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/stream-connect/-/stream-connect-1.0.2.tgz#18bc81f2edb35b8b5d9a8009200a985314428a97"
integrity sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==
dependencies:
array-back "^1.0.2"
stream-shift@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
stream-via@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/stream-via/-/stream-via-1.0.4.tgz#8dccbb0ac909328eb8bc8e2a4bd3934afdaf606c"
integrity sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
@ -2714,16 +2445,15 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
table-layout@^0.4.2:
version "0.4.5"
resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-0.4.5.tgz#d906de6a25fa09c0c90d1d08ecd833ecedcb7378"
integrity sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==
dependencies:
array-back "^2.0.0"
deep-extend "~0.6.0"
lodash.padend "^4.6.1"
typical "^2.6.1"
wordwrapjs "^3.0.0"
taffydb@2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268"
integrity sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==
taffydb@^2.7.3:
version "2.7.3"
resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.7.3.tgz#2ad37169629498fca5bc84243096d3cde0ec3a34"
integrity sha512-GQ3gtYFSOAxSMN/apGtDKKkbJf+8izz5YfbGqIsUc7AMiQOapARZ76dhilRY2h39cynYxBFdafQo5HUL5vgkrg==
tar-fs@2.1.1:
version "2.1.1"
@ -2746,35 +2476,6 @@ tar-stream@^2.1.4:
inherits "^2.0.3"
readable-stream "^3.1.1"
temp-path@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/temp-path/-/temp-path-1.0.0.tgz#24b1543973ab442896d9ad367dd9cbdbfafe918b"
integrity sha512-TvmyH7kC6ZVTYkqCODjJIbgvu0FKiwQpZ4D1aknE7xpcDf/qEOB8KZEK5ef2pfbVoiBhNWs3yx4y+ESMtNYmlg==
test-value@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/test-value/-/test-value-1.1.0.tgz#a09136f72ec043d27c893707c2b159bfad7de93f"
integrity sha512-wrsbRo7qP+2Je8x8DsK8ovCGyxe3sYfQwOraIY/09A2gFXU9DYKiTF14W4ki/01AEh56kMzAmlj9CaHGDDUBJA==
dependencies:
array-back "^1.0.2"
typical "^2.4.2"
test-value@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/test-value/-/test-value-2.1.0.tgz#11da6ff670f3471a73b625ca4f3fdcf7bb748291"
integrity sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w==
dependencies:
array-back "^1.0.3"
typical "^2.6.0"
test-value@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/test-value/-/test-value-3.0.0.tgz#9168c062fab11a86b8d444dd968bb4b73851ce92"
integrity sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==
dependencies:
array-back "^2.0.0"
typical "^2.6.1"
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@ -2801,6 +2502,15 @@ through@^2.3.8:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
tidy-jsdoc@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/tidy-jsdoc/-/tidy-jsdoc-1.4.1.tgz#609289afb4094c4b4cb4367cbce746f940232edd"
integrity sha512-FpH1oL6fEMMO0qPPAjoV8peAriwTjdys92TMsfMufrDERDGfmg2w90ieqOQ4RGDH7yuvDTqxR7a0W1Mfun8fzA==
dependencies:
jsdoc "^3.6.3"
taffydb "^2.7.3"
util "^0.10.3"
tmp@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
@ -2855,31 +2565,11 @@ type-fest@^0.20.2:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
typical@^2.4.2, typical@^2.6.0, typical@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d"
integrity sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==
typical@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4"
integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==
typical@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/typical/-/typical-6.0.1.tgz#89bd1a6aa5e5e96fa907fb6b7579223bff558a06"
integrity sha512-+g3NEp7fJLe9DPa1TArHm9QAA7YciZmWnfAqEaFrBihQ7epOv9i99rjtgb6Iz0wh3WuQDjsCTDfgRoGnmHN81A==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
uglify-js@^3.1.4:
version "3.17.4"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c"
integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==
unbzip2-stream@1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7"
@ -2936,21 +2626,18 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
util@^0.10.3:
version "0.10.4"
resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
dependencies:
inherits "2.0.3"
v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
walk-back@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/walk-back/-/walk-back-2.0.1.tgz#554e2a9d874fac47a8cb006bf44c2f0c4998a0a4"
integrity sha512-Nb6GvBR8UWX1D+Le+xUq0+Q1kFmRBIWVrfLnQAOmcpEzA9oAxwJ9gIr36t9TWYfzvWRvuMtjHiVsJYEkXWaTAQ==
walk-back@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/walk-back/-/walk-back-5.1.0.tgz#486d6f29e67f56ab89b952d987028bbb1a4e956c"
integrity sha512-Uhxps5yZcVNbLEAnb+xaEEMdgTXl9qAQDzKYejG2AZ7qPwRQ81lozY9ECDbjLPNWm7YsO1IK5rsP1KoQzXAcGA==
wasm-feature-detect@^1.2.11:
version "1.3.0"
resolved "https://registry.yarnpkg.com/wasm-feature-detect/-/wasm-feature-detect-1.3.0.tgz#fb3fc5dd4a1ba950a429be843daad67fe048bc42"
@ -2988,19 +2675,6 @@ word-wrap@^1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
wordwrap@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
wordwrapjs@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-3.0.0.tgz#c94c372894cadc6feb1a66bff64e1d9af92c5d1e"
integrity sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==
dependencies:
reduce-flatten "^1.0.1"
typical "^2.6.1"
workbox-cacheable-response@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-4.3.1.tgz#f53e079179c095a3f19e5313b284975c91428c91"