mirror of
https://github.com/wassname/talk.git
synced 2026-09-10 12:43:11 +08:00
Merge branch 'master' into extendable-tab-menu
Conflicts: client/coral-embed-stream/src/components/Embed.js client/coral-embed-stream/style/default.css client/coral-framework/helpers/plugins.js client/coral-plugin-replies/ReplyButton.css client/coral-plugin-replies/ReplyButton.js plugins/coral-plugin-like/client/LikeButton.js plugins/coral-plugin-like/client/styles.css plugins/coral-plugin-love/client/LoveButton.js plugins/coral-plugin-love/client/styles.css
This commit is contained in:
+120
-1
@@ -8,13 +8,14 @@
|
||||
// https://yarnpkg.com/
|
||||
|
||||
const program = require('./commander');
|
||||
const inquirer = require('inquirer');
|
||||
|
||||
// Make things colorful!
|
||||
require('colors');
|
||||
const emoji = require('node-emoji');
|
||||
|
||||
const dir = process.cwd();
|
||||
const fs = require('fs');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const spawn = require('cross-spawn');
|
||||
const semver = require('semver');
|
||||
@@ -272,10 +273,128 @@ async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote
|
||||
console.log(`✨ Done in ${totalTime}s.`);
|
||||
}
|
||||
|
||||
async function createSeedPlugin() {
|
||||
const pluginsDir = path.join(__dirname, 'plugins');
|
||||
|
||||
function pluginNameExists(pluginName) {
|
||||
const pluginNames = fs.readdirSync(pluginsDir);
|
||||
return !!pluginNames
|
||||
.filter((pn) => pn === pluginName).length;
|
||||
}
|
||||
|
||||
let answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'pluginName',
|
||||
message: 'Plugin Name:',
|
||||
validate: (input) => {
|
||||
|
||||
if (pluginNameExists(input)) {
|
||||
return 'Please, choose another name. That name already exists';
|
||||
}
|
||||
|
||||
if (input && input.length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'Plugin Name is required.';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'server',
|
||||
message: 'Is this plugin extending the server capabilities?'
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'client',
|
||||
message: 'Is this plugin extending the client capabilities?'
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'addPluginsJson',
|
||||
message: 'Should we add it to the plugins.json?'
|
||||
}
|
||||
]);
|
||||
|
||||
//==============================================================================
|
||||
// Creating plugin seed
|
||||
//==============================================================================
|
||||
|
||||
const seedPlugin = path.join(__dirname, 'bin/templates/plugin');
|
||||
const newPluginPath = path.join(pluginsDir, answers.pluginName);
|
||||
|
||||
if (fs.existsSync(seedPlugin)) {
|
||||
|
||||
if (answers.server && answers.client) {
|
||||
|
||||
// This is a server-side and client-side plugin!, let's copy the template
|
||||
fs.copySync(seedPlugin, newPluginPath);
|
||||
} else {
|
||||
|
||||
fs.copySync(seedPlugin, newPluginPath, {filter: (p) => {
|
||||
|
||||
// Allowing plugin folder and files with no subfolders
|
||||
const rootRx = /plugin$|plugin\/[^/]*(\.).{2,3}/igm;
|
||||
if (rootRx.test(p) && (fs.lstatSync(p).isDirectory() || fs.lstatSync(p).isFile())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it's a client-side plugin, copying client folder
|
||||
if (answers.client) {
|
||||
return /client/.test(p);
|
||||
}
|
||||
|
||||
// If it's a server-side plugin, copying server folder
|
||||
if (answers.server) {
|
||||
return /server/.test(p);
|
||||
}
|
||||
|
||||
}});
|
||||
}
|
||||
|
||||
// Let's add this to the plugins.json
|
||||
if (answers.addPluginsJson) {
|
||||
const pluginsJson = path.join(dir, 'plugins.json');
|
||||
|
||||
fs.readJson(pluginsJson)
|
||||
.then((j) => {
|
||||
|
||||
// This is a client-side plugin, let's push this.
|
||||
if (answers.client) {
|
||||
j.client.push(answers.pluginName);
|
||||
|
||||
const output = JSON.stringify(j, null, 2);
|
||||
fs.writeFileSync(pluginsJson, output);
|
||||
}
|
||||
|
||||
// This is a server-side plugin, let's push this.
|
||||
if (answers.server) {
|
||||
j.server.push(answers.pluginName);
|
||||
|
||||
const output = JSON.stringify(j, null, 2);
|
||||
fs.writeFileSync(pluginsJson, output);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`✨ Yay! Plugin created! Find your plugin: ${answers.pluginName} in the ./plugins folder`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// Setting up the program command line arguments.
|
||||
//==============================================================================
|
||||
|
||||
program
|
||||
.command('create')
|
||||
.description('creates a seed plugin')
|
||||
.action(createSeedPlugin);
|
||||
|
||||
program
|
||||
.command('list')
|
||||
.description('')
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015"
|
||||
],
|
||||
"plugins": [
|
||||
"add-module-exports",
|
||||
"transform-class-properties",
|
||||
"transform-decorators-legacy",
|
||||
"transform-object-assign",
|
||||
"transform-object-rest-spread",
|
||||
"transform-async-to-generator",
|
||||
"transform-react-jsx"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"mocha": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"no-console": ["warn", { "allow": ["warn", "error"] }]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
.myPluginContainer {
|
||||
padding: 10px;
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #d6d6d6;
|
||||
margin: 10px 0;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: block;
|
||||
animation: spin 2s infinite ease;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
color: #444444;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import {CoralLogo} from 'plugin-api/beta/client/components/ui';
|
||||
import styles from './MyPluginComponent.css';
|
||||
|
||||
class MyPluginComponent extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.myPluginContainer}>
|
||||
<CoralLogo className={styles.logo}/>
|
||||
<div className={styles.description}>
|
||||
<h3>Plugin created by Talk CLI</h3>
|
||||
|
||||
<small>
|
||||
To read more about plugins check{' '}
|
||||
<a href="https://coralproject.github.io/talk/plugins-client.html">
|
||||
our docs and guides!
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MyPluginComponent;
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
/**
|
||||
This is a client index example file and it could look like this:
|
||||
|
||||
```
|
||||
import LoveButton from './components/LoveButton';
|
||||
|
||||
export default {
|
||||
slots: {
|
||||
commentReactions: [LoveButton]
|
||||
},
|
||||
reducer,
|
||||
translations
|
||||
};
|
||||
```
|
||||
|
||||
To read more info on how to build client plugins. Please, go to: https://coralproject.github.io/talk/plugins-client.html
|
||||
*/
|
||||
|
||||
import MyPluginComponent from './components/MyPluginComponent';
|
||||
|
||||
export default {
|
||||
slots: {
|
||||
stream: [MyPluginComponent]
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# This file is for translations and they should look like this:
|
||||
#
|
||||
#
|
||||
# ```
|
||||
# en:
|
||||
# coral-plugin-respect:
|
||||
# respect: Respect
|
||||
# respected: Respected
|
||||
# es:
|
||||
# coral-plugin-respect:
|
||||
# respect: Respetar
|
||||
# respected: Respetado
|
||||
# ```
|
||||
#
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
@@ -8,6 +8,7 @@ import {TabBar, Tab, TabContent, TabPane} from 'coral-ui';
|
||||
import ProfileContainer from 'coral-settings/containers/ProfileContainer';
|
||||
import ConfigureStreamContainer
|
||||
from 'coral-configure/containers/ConfigureStreamContainer';
|
||||
import cn from 'classnames';
|
||||
|
||||
export default class Embed extends React.Component {
|
||||
changeTab = (tab) => {
|
||||
@@ -25,47 +26,46 @@ export default class Embed extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {activeTab} = this.props;
|
||||
const {activeTab, commentId} = this.props;
|
||||
const {user} = this.props.auth;
|
||||
const hasHighlightedComment = !!commentId;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="commentStream">
|
||||
<TabBar
|
||||
onTabClick={this.changeTab}
|
||||
activeTab={activeTab}
|
||||
className='talk-stream-tabbar'
|
||||
aria-controls='talk-embed-stream-tab-content'
|
||||
>
|
||||
<Tab tabId={'stream'} className={'talk-stream-comment-count-tab'}>
|
||||
{t('embed_comments_tab')}
|
||||
<div className={cn('talk-embed-stream', {'talk-embed-stream-highlight-comment': hasHighlightedComment})}>
|
||||
<TabBar
|
||||
onTabClick={this.changeTab}
|
||||
activeTab={activeTab}
|
||||
className='talk-embed-stream-tab-bar'
|
||||
aria-controls='talk-embed-stream-tab-content'
|
||||
>
|
||||
<Tab tabId={'stream'} className={'talk-embed-stream-comments-tab'}>
|
||||
{t('embed_comments_tab')}
|
||||
</Tab>
|
||||
<Tab tabId={'profile'} className={'talk-embed-stream-profile-tab'}>
|
||||
{t('framework.my_profile')}
|
||||
</Tab>
|
||||
{can(user, 'UPDATE_CONFIG') &&
|
||||
<Tab tabId={'config'} className={'talk-embed-stream-configuration-tab'}>
|
||||
{t('framework.configure_stream')}
|
||||
</Tab>
|
||||
<Tab tabId={'profile'} className={'talk-stream-profile-tab'}>
|
||||
{t('framework.my_profile')}
|
||||
</Tab>
|
||||
{can(user, 'UPDATE_CONFIG') &&
|
||||
<Tab tabId={'config'} className={'talk-stream-configuration-tab'}>
|
||||
{t('framework.configure_stream')}
|
||||
</Tab>
|
||||
}
|
||||
</TabBar>
|
||||
<Slot fill="embed" />
|
||||
}
|
||||
</TabBar>
|
||||
<Slot fill="embed" />
|
||||
|
||||
<TabContent
|
||||
activeTab={activeTab}
|
||||
id='talk-embed-stream-tab-content'
|
||||
>
|
||||
<TabPane tabId={'stream'}>
|
||||
<Stream data={this.props.data} root={this.props.root} />
|
||||
</TabPane>
|
||||
<TabPane tabId={'profile'}>
|
||||
<ProfileContainer />
|
||||
</TabPane>
|
||||
<TabPane tabId={'config'}>
|
||||
<ConfigureStreamContainer />
|
||||
</TabPane>
|
||||
</TabContent>
|
||||
</div>
|
||||
<TabContent
|
||||
activeTab={activeTab}
|
||||
id='talk-embed-stream-tab-content'
|
||||
>
|
||||
<TabPane tabId={'stream'}>
|
||||
<Stream data={this.props.data} root={this.props.root} />
|
||||
</TabPane>
|
||||
<TabPane tabId={'profile'}>
|
||||
<ProfileContainer />
|
||||
</TabPane>
|
||||
<TabPane tabId={'config'}>
|
||||
<ConfigureStreamContainer />
|
||||
</TabPane>
|
||||
</TabContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ class Stream extends React.Component {
|
||||
{comment &&
|
||||
<Button
|
||||
cStyle="darkGrey"
|
||||
className={styles.viewAllButton}
|
||||
className={cn('talk-stream-show-all-comments-button', styles.viewAllButton)}
|
||||
onClick={viewAllComments}
|
||||
>
|
||||
{t('framework.show_all_comments')}
|
||||
|
||||
@@ -147,9 +147,6 @@ const USERNAME_REJECTED_SUBSCRIPTION = gql`
|
||||
|
||||
const EMBED_QUERY = gql`
|
||||
query CoralEmbedStream_Embed($assetId: ID, $assetUrl: String, $commentId: ID!, $hasComment: Boolean!, $excludeIgnored: Boolean) {
|
||||
asset(id: $assetId, url: $assetUrl) {
|
||||
totalCommentCount(excludeIgnored: $excludeIgnored)
|
||||
}
|
||||
me {
|
||||
id
|
||||
status
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import update from 'immutability-helper';
|
||||
import {THREADING_LEVEL} from '../constants/stream';
|
||||
function determineCommentDepth(comment) {
|
||||
let depth = 0;
|
||||
let cur = comment;
|
||||
while (cur.parent) {
|
||||
cur = cur.parent;
|
||||
depth++;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
function applyToCommentsOrigin(root, callback) {
|
||||
if (root.comment) {
|
||||
let comment = root.comment;
|
||||
for (let depth = 0; depth <= THREADING_LEVEL; depth++) {
|
||||
for (let depth = 0; depth <= determineCommentDepth(comment); depth++) {
|
||||
let changes = {$apply: (node) => node ? callback(node) : node};
|
||||
for (let i = 0; i < depth; i++) {
|
||||
changes = {parent: changes};
|
||||
|
||||
@@ -48,5 +48,5 @@ render(
|
||||
<ApolloProvider client={client} store={store}>
|
||||
<AppRouter />
|
||||
</ApolloProvider>
|
||||
, document.querySelector('#coralStream')
|
||||
, document.querySelector('#talk-embed-stream-container')
|
||||
);
|
||||
|
||||
@@ -444,13 +444,4 @@ button.comment__action-button[disabled],
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (min-device-width : 300px) and (max-device-width : 420px) {
|
||||
.commentActionsLeft.comment__action-container .coral-plugin-replies-reply-button {
|
||||
visibility: collapse;
|
||||
margin-left: -30px;
|
||||
}
|
||||
|
||||
.commentActionsLeft.comment__action-container .coral-plugin-replies-reply-button .coral-plugin-replies-icon {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,9 @@ export function getSlotComponents(slot) {
|
||||
|
||||
return flatten(plugins
|
||||
|
||||
// Filter out components that have been disabled in `plugin_config`
|
||||
.filter((o) => !pluginConfig || !pluginConfig[o.name] || !pluginConfig[o.name].disable_components)
|
||||
// Filter out components that have slots and have been disabled in `plugin_config`
|
||||
.filter((o) => o.module.slots && (!pluginConfig || !pluginConfig[o.name] || !pluginConfig[o.name].disable_components))
|
||||
|
||||
.filter((o) => o.module.slots[slot])
|
||||
.map((o) => o.module.slots[slot])
|
||||
);
|
||||
@@ -77,7 +78,7 @@ export function getSlotsFragments(slots) {
|
||||
}
|
||||
const components = uniq(flattenDeep(slots.map((slot) => {
|
||||
return plugins
|
||||
.filter((o) => o.module.slots[slot])
|
||||
.filter((o) => o.module.slots ? o.module.slots[slot] : false)
|
||||
.map((o) => o.module.slots[slot]);
|
||||
})));
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ export default class FlagButton extends Component {
|
||||
<button
|
||||
ref={(ref) => this.flagButton = ref}
|
||||
onClick={!this.props.banned && !flaggedByCurrentUser && !localPost ? this.onReportClick : null}
|
||||
className={cn(`${name}-button`, styles.button)}>
|
||||
className={cn(`${name}-button`, {[`${name}-button-flagged`]: flagged}, styles.button)}>
|
||||
{
|
||||
flagged
|
||||
? <span className={`${name}-button-text`}>{t('reported')}</span>
|
||||
|
||||
@@ -2,3 +2,9 @@
|
||||
composes: buttonReset from "coral-framework/styles/reset.css";
|
||||
margin: 5px 10px 5px 0px;
|
||||
}
|
||||
|
||||
@media (max-width: 425px) {
|
||||
.label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, {PropTypes} from 'react';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
import styles from './ReplyButton.css';
|
||||
import cn from 'classnames';
|
||||
import styles from './ReplyButton.css';
|
||||
|
||||
const name = 'coral-plugin-replies';
|
||||
|
||||
@@ -11,9 +11,10 @@ const ReplyButton = ({onClick}) => {
|
||||
return (
|
||||
<button
|
||||
className={cn(`${name}-reply-button`, styles.button)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{t('reply')}
|
||||
onClick={onClick}>
|
||||
<span className={cn(`${name}-label`, styles.label)}>
|
||||
{t('reply')}
|
||||
</span>
|
||||
<i className={`${name}-icon material-icons`}
|
||||
aria-hidden={true}>reply</i>
|
||||
</button>
|
||||
|
||||
@@ -37,6 +37,20 @@ entries:
|
||||
url: /install-setup.html
|
||||
output: web
|
||||
|
||||
- title: Architecture
|
||||
output: web
|
||||
folderitems:
|
||||
- title: Overview
|
||||
url: /architecture.html
|
||||
output: web
|
||||
- title: Tags
|
||||
url: /architecture-tags.html
|
||||
output: web
|
||||
- title: cli
|
||||
url: /architecture-cli.html
|
||||
output: web
|
||||
|
||||
|
||||
- title: Plugins
|
||||
output: web
|
||||
folderitems:
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: The Talk cli
|
||||
keywords: architecture
|
||||
sidebar: talk_sidebar
|
||||
permalink: architecture-cli.html
|
||||
summary:
|
||||
---
|
||||
|
||||
Talk ships with a cli tool that allows access to a wide variety of functionality.
|
||||
|
||||
It is designed to provide a convenient way for engineers to perform key tasks without the need to muck about in the UI. It also opens the door for scripts to perform operations programmatically.
|
||||
|
||||
Note: the cli tool requires [the Talk environment to be configured](configuration.html) either via env vars or by using a `.cli` file via `bin/cli -c .env [command] ....`
|
||||
|
||||
## Using the cli
|
||||
|
||||
In a terminal, try:
|
||||
|
||||
```
|
||||
/path/to/talk/bin cli [options] [commands] [arguments]
|
||||
```
|
||||
|
||||
Commonly, you'll be in the `talk/` folder, in which case you can:
|
||||
|
||||
```
|
||||
bin/cli [options] [commands] [arguments]
|
||||
```
|
||||
|
||||
If you're a heavy cli user, consider adding the `bin` folder to your PATH so you can run `cli` from anywhere!
|
||||
|
||||
If you are using [our Docker environment](install-docker.html), the bin folder will already be in the PATH.
|
||||
|
||||
## What can I do with the cli?
|
||||
|
||||
The Talk cli ships with 'unix style' help. To access the docs, simply run the cli with insufficient arguments.
|
||||
|
||||
Let's say I wanted to figure out how to change a user's password. I'd start be seeing what the cli has for me.
|
||||
|
||||
(Note: the following output may change, please reference at the `--help` output for your version as you use the cli.)
|
||||
|
||||
```
|
||||
talk :) ]$ bin/cli --help
|
||||
|
||||
Usage: cli [options] [command]
|
||||
|
||||
|
||||
Commands:
|
||||
|
||||
serve serve the application
|
||||
settings interact with the application settings
|
||||
assets interact with assets
|
||||
setup setup the application
|
||||
jobs work with the job queues
|
||||
token work with the access tokens
|
||||
users work with the application auth
|
||||
migration provides utilities for migrating the database
|
||||
plugins provides utilities for interacting with the plugin system
|
||||
help [cmd] display help for [cmd]
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help output usage information
|
||||
-V, --version output the version number
|
||||
-c, --config [path] Specify the configuration file to load
|
||||
--pid [path] Specify a path to output the current PID to
|
||||
```
|
||||
|
||||
Most commands contain sub-commands. Running with cli with such a command generates the docs for the sub-commands and options therein.
|
||||
|
||||
Change user password is likely to be in the `users` command group.
|
||||
|
||||
```
|
||||
talk :) ]$ bin/cli users
|
||||
|
||||
Usage: cli-users [options] [command]
|
||||
|
||||
|
||||
Commands:
|
||||
|
||||
create [options] create a new user
|
||||
delete <userID> delete a user
|
||||
passwd <userID> change a password for a user
|
||||
update [options] <userID> update a user
|
||||
list list all the users in the database
|
||||
merge <dstUserID> <srcUserID> merge srcUser into the dstUser
|
||||
addrole <userID> <role> adds a role to a given user
|
||||
removerole <userID> <role> removes a role from a given user
|
||||
ban <userID> ban a given user
|
||||
uban <userID> unban a given user
|
||||
disable <userID> disable a given user from logging in
|
||||
enable <userID> enable a given user from logging in
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help output usage information
|
||||
-V, --version output the version number
|
||||
-c, --config [path] Specify the configuration file to load
|
||||
--pid [path] Specify a path to output the current PID to
|
||||
```
|
||||
|
||||
I now see that I can change a password like so:
|
||||
|
||||
```
|
||||
bin/cli users passwd [userID]
|
||||
```
|
||||
|
||||
You can also read these help prompts by [exploring the source code](https://github.com/coralproject/talk/blob/master/bin/cli).
|
||||
|
||||
### Usage Notes
|
||||
|
||||
If you haven't used a cli enabled system before, think of it like this: generally, you'd make a rest call, rpc, etc... to perform an action. The cli's api is designed in the same way, just for the audience of command line wielding engineers and scripts.
|
||||
|
||||
The best way to understand what the cli does is to explore the help commands. Uses of cli are also scattered through this documentation in context of their topics.
|
||||
|
||||
For some real world uses of the cli, see the scripts in the [package.json file](https://github.com/coralproject/talk/blob/d688f70c19d8dee48371784009fd07322dae4eb5/package.json#L8).
|
||||
|
||||
## What's really going on when I run the cli?
|
||||
|
||||
The cli tool is a standalone application. Running it starts up the internals of a talk process, executes the given command, then shuts it down. No server functionality is enabled by cli commands unless specifically noted.
|
||||
|
||||
The cli tool _does not require a talk server to be running._ This means that you can execute commands, for example, during and installation process before starting the server. The also means that you can execute commands using varying configurations (via the `-c [.env file]` flag).
|
||||
|
||||
### Accessing existing Talk installs with the cli
|
||||
|
||||
You may use the cli tool to 'remotely' control existing talk installs.
|
||||
|
||||
This is accomplished by running the cli tool on any box using the mongo/redis/etc... credentials for the environment that you would like to act on. For example, if you want something to happen periodically on your production Talk install, you could set up a utility box with a cron job that triggers the cli with the same db/cache credentials. If you want to do something quick on a staging server, you could run the cli locally with staging credentials.
|
||||
|
||||
The cli tool will connect directly with the install's db and redis instance(s) so ensure that your box can reach those servers on the appropriate ports.
|
||||
|
||||
Also, _please ensure your cli and the server(s) in an environment are using the same version of Talk._
|
||||
|
||||
Please secure your environments and credentials or the cli tool becomes a convenient way for someone to own your system.
|
||||
|
||||
## Extending the cli
|
||||
|
||||
The Talk cli is based on the excellent [commander](https://github.com/tj/commander.js/) library.
|
||||
|
||||
At the time of writing, there are no plugin hooks for the cli tool. If you would like to change this, whether by writing code yourself or recommending a need, please [write and issue](https://github.com/coralproject/talk/blob/053b687959d45bcd682a1a2a4b604ebfab7441bb/CONTRIBUTING.md#writing-code).
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Tags
|
||||
keywords: architecture
|
||||
sidebar: talk_sidebar
|
||||
permalink: architecture-tags.html
|
||||
summary:
|
||||
---
|
||||
|
||||
Tags are essentially strings that can be added to models. Currently, tags can be added to [Users, Comments and Assets](https://github.com/coralproject/talk/blob/ced449a1489d47c25d604020fa2e0b3b7a741353/graph/typeDefs.graphql#L144). If you would like to add tags to other models, you can extend this schema using [GraphQL hooks](plugins-server.html#graphql-hooks).
|
||||
|
||||
## Tag Definitions
|
||||
|
||||
When handling tags, the Talk Server references a set of definitions that describe how tags are handled. These definitions are keyed off the tag `name`, the simple string that is stored on items.
|
||||
|
||||
The schema for Tag definitions [can be found here](https://github.com/coralproject/talk/blob/3545bf01cd91044fdb738d337a0ac94d9f71fbc3/models/schema/tag.js).
|
||||
|
||||
Note that along with the `name`, tag definitions contains:
|
||||
|
||||
* `permissions` information about who can see and set the tag,
|
||||
* `models` which `ITEM_TYPES` this tag can be applied to, and
|
||||
|
||||
Whenever a tag is 'handled' by the server, it references this definition to determine that tag's behavior.
|
||||
|
||||
See [Plugin API Documentation](plugins-server.html#field-tags) for more information.
|
||||
|
||||
### Creating a Tag Definition
|
||||
|
||||
Tag Definitions must be created in order for the system to determine what tags are permitted on the server side.
|
||||
|
||||
Tag Definitions do not contain any logic themselves but provide information that other parts of the system can use to specify which models a tag can be applied to (models) and perform authorization logic (permissions).
|
||||
|
||||
Take the tag created by `coral-plugin-offtopic` as an example.
|
||||
|
||||
```
|
||||
// coral-plugin-offtopic/index.js
|
||||
module.exports = {
|
||||
tags: [
|
||||
{
|
||||
name: 'OFF_TOPIC',
|
||||
permissions: {
|
||||
public: true,
|
||||
self: true,
|
||||
roles: []
|
||||
},
|
||||
models: ['COMMENTS'],
|
||||
created_at: new Date()
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
This plugin allows users to self-report that their comment is "off topic" at the time of creation, then display a badge on those comments.
|
||||
|
||||
To accomplish this, the plugin creates the tag `OFF_TOPIC` with:
|
||||
|
||||
* `permissions.public: true` - will be sent over the wire to the client side
|
||||
* `permissions.self: true` - can be added by the active user to themselves or assets they own
|
||||
* `permissions.roles: []` - cannot be added by anyone based on their roles
|
||||
* `models: ['COMMENTS']` - can only be added to COMMENTS (not to users/assets/etc...)
|
||||
|
||||
And [viola](https://youtu.be/Q0O9gFf-tiI?t=23s)! This tag is something that can only be created by the logged in user on their own comments and is sent over the wire to the client so it can display the badge.
|
||||
|
||||
## Tag Links
|
||||
|
||||
When tags are stored on objects in the database, they are represented by [TagLinks](https://github.com/coralproject/talk/blob/master/models/schema/tag_link.js).
|
||||
|
||||
A TagLinks says that `tag` was `assigned_by` a specific user at a specific time (`created_at`).
|
||||
|
||||
Note that the `tag` field in the TagLinkSchema is the full TagSchema itself. This allows for another level of flexibility. Server code may generate Tags on the fly, complete with programmatically generated permissions and item behaviors.
|
||||
|
||||
If a Tag definitions exists in the global/asset context then that definition will be used regardless of what is stored here. This allows high level controls on the behavior of tags, ensuring that plugins cannot produce unexpected definitions for already defined tags.
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Architecture Overview
|
||||
keywords: architecture
|
||||
sidebar: talk_sidebar
|
||||
permalink: architecture.html
|
||||
summary:
|
||||
---
|
||||
|
||||
## Talk's Architecture
|
||||
|
||||
Talk consists of four distinct layers of code:
|
||||
|
||||
* Plugins
|
||||
* Plugin API
|
||||
* Core
|
||||
* cli
|
||||
|
||||
### Plugins
|
||||
|
||||
Talk plugins deliver the features and functionality that can be changed or removed. Much of the default functionality is delivered by plugins allowing developers to change behavior along product lines that we've found to be important.
|
||||
|
||||
### Plugin API
|
||||
|
||||
Talk plugins interact exclusively with the Plugin API. Maintaining this layer of separation between plugins and core allows us to consciously design the api that we want it publish to plugin authors. We can then expose just the elements of core that make sense and maintain that contract as core changes.
|
||||
|
||||
### Core
|
||||
|
||||
Talk core consists of architecture and functionality that deliver stability, security, scalability, extendability, etc... In addition, the Core contains features and functionality that is essential to the operation of Talk as a product.
|
||||
|
||||
Our goal is to continually extend our plugin infrastructure making the Core as pluggable as possible. Ultimately, a day may come where the Core of Talk is simply a framework for delivering a certain flavor of web applications.
|
||||
|
||||
### cli
|
||||
|
||||
Talk ships with [a cli tool](architecture-cli.html) that exposes functionality to the command line. We seek to provide cli functionality for all features that could need to be accomplished programmatically or prior to the server's startup.
|
||||
|
||||
## Thinking about Plugins, the Plugin API and Core?
|
||||
|
||||
The following is a template for a thought process that may help clarify your ideas against the backdrop of Talk's architecture.
|
||||
|
||||
Think of a feature or capability. It could be something that's already in Talk or not. It could be something you want to build, or something you'd think would be a terrible idea. The important part here is to have something to interrogate.
|
||||
|
||||
```
|
||||
wait(60000);
|
||||
```
|
||||
|
||||
Now, ask these questions:
|
||||
|
||||
### Is it a Plugin?
|
||||
|
||||
Most work for Talk happens in the Plugin space. If the answers to any of these questions is Yes, then you're thinking of a Plugin.
|
||||
|
||||
* Does Talk's existing Plugin APIs support the thing you want to build?
|
||||
* Is this something that only some users will want/need?
|
||||
* Is this something that we want devs to iterate on widely?
|
||||
|
||||
You should [build it as a plugin](plugins-quickstart.html). Feel free to explore here on your own or reach out to us. We love to advise on plugins, so please feel free to [file an issue](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md) and we will start a conversation. We will help you conceptualize, architect and promote your plugin if it is in line with our values.
|
||||
|
||||
### Does it need updates to the Plugin API?
|
||||
|
||||
If you answered yes above:
|
||||
|
||||
* Do I need to extend the Plugin API to support my plugin?
|
||||
|
||||
Often times all the functionality a plugin needs is in the Core, but the Plugin API doesn't expose it. In these cases, we seek to iteratively extend the Talk Plugin API. All Plugin API contributions from the community must begin by [filing an Issue](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md).
|
||||
|
||||
Note: we are stabilizing the process by which new Plugin API bindings are created, agreed upon and ultimately made part of our Plugins Contract. If you are interested in this process, please reach out to us.
|
||||
|
||||
### Does it require updates to the Plugin API _and_ Core?
|
||||
|
||||
What, if any, changes need to be made to Core so that the API can be extended?
|
||||
|
||||
Quite often the only things missing from Core are things like _events_, _slots_, _CSS classes_, etc... Adding these is a great way to become a Core Contributor and break new ground as a Plugin Developer.
|
||||
|
||||
We seek to keep Core as lean as possible.
|
||||
|
||||
### Is my idea really just Core?
|
||||
|
||||
Amazing! We are always looking to extend the capabilities of Talk. We look forward to discussing what you've got to bring!
|
||||
|
||||
Please see our [contributing guide](](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md)) for more information.
|
||||
@@ -215,7 +215,7 @@ It is important to realize that when you're writing a Talk plugin you are writin
|
||||
|
||||
### Publish to npm
|
||||
|
||||
In order to [register](http://localhost:4000/plugins.html#plugin-registration) your _published_ plugin, you will need to [publish it to npm](https://docs.npmjs.com/getting-started/publishing-npm-packages).
|
||||
In order to [register](plugins.html#plugin-registration) your _published_ plugin, you will need to [publish it to npm](https://docs.npmjs.com/getting-started/publishing-npm-packages).
|
||||
|
||||
Once the package is published, update `plugins.json` to use the published plugin:
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "talk",
|
||||
"version": "2.3.0",
|
||||
"version": "2.4.0",
|
||||
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
@@ -78,6 +78,7 @@
|
||||
"express": "^4.15.2",
|
||||
"express-session": "^1.15.1",
|
||||
"form-data": "^2.1.2",
|
||||
"fs-extra": "^3.0.1",
|
||||
"gql-merge": "^0.0.4",
|
||||
"graphql": "^0.9.1",
|
||||
"graphql-errors": "^2.1.0",
|
||||
|
||||
@@ -45,7 +45,9 @@ class LikeButton extends React.Component {
|
||||
className={cn(styles.button, {[styles.liked]: alreadyReacted}, `${plugin}-button`)}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<span>{t(alreadyReacted ? 'coral-plugin-like.liked' : 'coral-plugin-like.like')}</span>
|
||||
<span className={cn(`${plugin}-label`, styles.label)}>
|
||||
{t(alreadyReacted ? 'coral-plugin-like.liked' : 'coral-plugin-like.like')}
|
||||
</span>
|
||||
<Icon name="thumb_up" className={cn(`${plugin}-icon`, styles.icon)} />
|
||||
<span className={`${plugin}-count`}>{count > 0 && count}</span>
|
||||
</button>
|
||||
|
||||
@@ -27,3 +27,9 @@
|
||||
.icon {
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 425px) {
|
||||
.label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,9 @@ class LoveButton extends React.Component {
|
||||
className={cn(styles.button, {[styles.loved]: alreadyReacted}, `${plugin}-button`)}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<span>{t(alreadyReacted ? 'coral-plugin-love.loved' : 'coral-plugin-love.love')}</span>
|
||||
<span className={cn(`${plugin}-label`, styles.label)}>
|
||||
{t(alreadyReacted ? 'coral-plugin-love.loved' : 'coral-plugin-love.love')}
|
||||
</span>
|
||||
<Icon name="favorite" className={cn(`${plugin}-icon`, styles.icon)} />
|
||||
<span className={`${plugin}-count`}>{count > 0 && count}</span>
|
||||
</button>
|
||||
|
||||
@@ -27,3 +27,9 @@
|
||||
.icon {
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 425px) {
|
||||
.label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,11 +45,11 @@ class RespectButton extends React.Component {
|
||||
className={cn(styles.button, {[styles.respected]: alreadyReacted}, `${plugin}-button`)}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<span className={`${plugin}-button-text`}>
|
||||
<span className={cn(`${plugin}-label`, styles.label)}>
|
||||
{t(alreadyReacted ? 'coral-plugin-respect.respected' : 'coral-plugin-respect.respect')}
|
||||
</span>
|
||||
<Icon className={cn(styles.icon, `${plugin}-icon`)} />
|
||||
<span className={cn(styles.icon, `${plugin}-count`)}>{count > 0 && count}</span>
|
||||
<span className={cn(`${plugin}-count`)}>{count > 0 && count}</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -28,3 +28,9 @@
|
||||
.icon {
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 425px) {
|
||||
.label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="coralStream"></div>
|
||||
<div id="talk-embed-stream-container"></div>
|
||||
<script src="/client/embed/stream/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3290,6 +3290,14 @@ fs-extra@^0.26.4:
|
||||
path-is-absolute "^1.0.0"
|
||||
rimraf "^2.2.8"
|
||||
|
||||
fs-extra@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291"
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
jsonfile "^3.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
fs-promise@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/fs-promise/-/fs-promise-0.3.1.tgz#bf34050368f24d6dc9dfc6688ab5cead8f86842a"
|
||||
@@ -4614,6 +4622,10 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
|
||||
dependencies:
|
||||
jsonify "~0.0.0"
|
||||
|
||||
json-stringify-pretty-compact@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.0.4.tgz#d5161131be27fd9748391360597fcca250c6c5ce"
|
||||
|
||||
json-stringify-safe@~5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||
@@ -4632,6 +4644,12 @@ jsonfile@^2.1.0:
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsonfile@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66"
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsonify@~0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
|
||||
@@ -8263,6 +8281,10 @@ uniqs@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
|
||||
|
||||
universalify@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.0.tgz#9eb1c4651debcc670cc94f1a75762332bb967778"
|
||||
|
||||
unpipe@1.0.0, unpipe@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||
|
||||
Reference in New Issue
Block a user