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
85 changed files with 14567 additions and 932 deletions

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 button’s 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;
}