mirror of
https://github.com/wassname/talk.git
synced 2026-09-13 13:10:43 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8cfd7c0f8 | ||
|
|
0e4cbf5f44 | ||
|
|
448b988281 | ||
|
|
7a688495f2 | ||
|
|
b9cf7084eb | ||
|
|
b99161f0cb | ||
|
|
6faa4862de | ||
|
|
eff3f1dfd8 | ||
|
|
73aa77d7da | ||
|
|
5580ab385f | ||
|
|
40eb0e97ec | ||
|
|
b7e1ce0205 | ||
|
|
bfde9bf8c7 | ||
|
|
54f9b28086 | ||
|
|
03a5eb7f9b | ||
|
|
43e60e6544 | ||
|
|
ebd16666e6 | ||
|
|
f2129082a9 | ||
|
|
1098c068f2 | ||
|
|
f0c95d0044 | ||
|
|
74226aa6fc | ||
|
|
b6ed5b792b | ||
|
|
d31969592c | ||
|
|
794487b1a9 | ||
|
|
39e45c30ba | ||
|
|
ed734ebb08 | ||
|
|
9f059e0c4a | ||
|
|
7d369f2d35 | ||
|
|
5fb5240f89 | ||
|
|
976931135c | ||
|
|
1e3d47eb87 | ||
|
|
2d804d783b | ||
|
|
3404578ecc | ||
|
|
48c5355a5b | ||
|
|
e3a300c643 | ||
|
|
a6653152e4 |
@@ -34,7 +34,7 @@
|
||||
color: #063b9a;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
letter-spacing: .5px;
|
||||
letter-spacing: 0.5px;
|
||||
margin-left: 10px;
|
||||
font-size: 13px;
|
||||
margin-left: 5px;
|
||||
@@ -109,7 +109,7 @@
|
||||
}
|
||||
|
||||
.external {
|
||||
font-size: .7em;
|
||||
font-size: 0.7em;
|
||||
text-decoration: none;
|
||||
color: #063b9a;
|
||||
cursor: pointer;
|
||||
@@ -119,7 +119,7 @@
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
opacity: .9;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
> i {
|
||||
@@ -139,3 +139,25 @@
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.bodyHistoryToggle {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
line-height: 1px;
|
||||
font-weight: 300;
|
||||
text-decoration: underline;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.editedCommentBody {
|
||||
padding: 0 5px 5px 5px;
|
||||
}
|
||||
|
||||
.editedComment {
|
||||
margin-top: 6px;
|
||||
font-weight: 300;
|
||||
font-size: 16px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import TimeAgo from 'coral-framework/components/TimeAgo';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
class UserDetailComment extends React.Component {
|
||||
state = { showingEditHistory: false };
|
||||
|
||||
approve = () =>
|
||||
this.props.comment.status === 'ACCEPTED'
|
||||
? null
|
||||
@@ -29,6 +31,29 @@ class UserDetailComment extends React.Component {
|
||||
? null
|
||||
: this.props.rejectComment({ commentId: this.props.comment.id });
|
||||
|
||||
getBodyHistory = () => {
|
||||
const bodyHistory = [];
|
||||
const comment = this.props.comment;
|
||||
for (let i = 0; i < comment.body_history.length - 1; i++) {
|
||||
bodyHistory.push(
|
||||
<div key={i} className={styles.editedComment}>
|
||||
<div>
|
||||
<TimeAgo className={styles.created} datetime={comment.created_at} />
|
||||
</div>
|
||||
<div className={styles.editedCommentBody}>
|
||||
{comment.body_history[i].body}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return bodyHistory;
|
||||
};
|
||||
|
||||
toggleEditHistory = () => {
|
||||
this.setState({ showingEditHistory: !this.state.showingEditHistory });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
comment,
|
||||
@@ -81,6 +106,14 @@ class UserDetailComment extends React.Component {
|
||||
<span className={styles.editedMarker}>
|
||||
({t('comment.edited')})
|
||||
</span>
|
||||
<span
|
||||
className={styles.bodyHistoryToggle}
|
||||
onClick={this.toggleEditHistory}
|
||||
>
|
||||
{this.state.showingEditHistory
|
||||
? t('comment.hide_edit_history')
|
||||
: t('comment.show_edit_history')}
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
@@ -142,6 +175,14 @@ class UserDetailComment extends React.Component {
|
||||
</div>
|
||||
</CommentAnimatedEdit>
|
||||
</div>
|
||||
|
||||
{this.state.showingEditHistory ? (
|
||||
<div className={styles.container}>
|
||||
{t('comment.edit_history')}
|
||||
{this.getBodyHistory()}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<CommentDetails root={root} comment={comment} />
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -42,6 +42,10 @@ export default withFragments({
|
||||
status_history {
|
||||
type
|
||||
}
|
||||
body_history {
|
||||
body
|
||||
created_at
|
||||
}
|
||||
${getSlotFragmentSpreads(slots, 'comment')}
|
||||
...${getDefinitionName(CommentLabels.fragments.comment)}
|
||||
...${getDefinitionName(CommentDetails.fragments.comment)}
|
||||
|
||||
@@ -14,6 +14,11 @@ class TechSettings extends React.Component {
|
||||
this.props.updatePending({ updater });
|
||||
};
|
||||
|
||||
updateCustomAdminCssUrl = event => {
|
||||
const updater = { customAdminCssUrl: { $set: event.target.value } };
|
||||
this.props.updatePending({ updater });
|
||||
};
|
||||
|
||||
updateDomainlist = (listName, list) => {
|
||||
this.props.updatePending({
|
||||
updater: {
|
||||
@@ -50,6 +55,14 @@ class TechSettings extends React.Component {
|
||||
onChange={this.updateCustomCssUrl}
|
||||
/>
|
||||
</ConfigureCard>
|
||||
<ConfigureCard title={t('configure.custom_admin_css_url')}>
|
||||
<p>{t('configure.custom_admin_css_url_desc')}</p>
|
||||
<input
|
||||
className={styles.customCSSInput}
|
||||
value={settings.customAdminCssUrl}
|
||||
onChange={this.updateCustomAdminCssUrl}
|
||||
/>
|
||||
</ConfigureCard>
|
||||
<Slot fill="adminTechSettings" passthrough={slotPassthrough} />
|
||||
</ConfigurePage>
|
||||
);
|
||||
|
||||
@@ -32,6 +32,7 @@ export default compose(
|
||||
settings: gql`
|
||||
fragment TalkAdmin_TechSettings_settings on Settings {
|
||||
customCssUrl
|
||||
customAdminCssUrl
|
||||
domains {
|
||||
whitelist
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
font-weight: 300;
|
||||
margin-bottom: 8px;
|
||||
overflow-wrap: break-word;
|
||||
word-break:break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.body {
|
||||
@@ -103,7 +103,7 @@
|
||||
color: #063b9a;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
letter-spacing: .5px;
|
||||
letter-spacing: 0.5px;
|
||||
margin-left: 10px;
|
||||
font-size: 13px;
|
||||
margin-left: 5px;
|
||||
@@ -111,14 +111,14 @@
|
||||
border-bottom: solid 1px;
|
||||
line-height: 16px;
|
||||
&:hover {
|
||||
opacity: .9;
|
||||
opacity: 0.9;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.username {
|
||||
color: #393B44;
|
||||
color: #393b44;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
@@ -127,12 +127,12 @@
|
||||
margin-left: -5px;
|
||||
transition: background-color 200ms ease;
|
||||
&:hover {
|
||||
background-color: #E0E0E0;
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
}
|
||||
|
||||
.external {
|
||||
font-size: .7em;
|
||||
font-size: 0.7em;
|
||||
text-decoration: none;
|
||||
color: #063b9a;
|
||||
cursor: pointer;
|
||||
@@ -140,7 +140,7 @@
|
||||
white-space: nowrap;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
opacity: .9;
|
||||
opacity: 0.9;
|
||||
}
|
||||
i {
|
||||
font-size: 12px;
|
||||
@@ -196,3 +196,25 @@
|
||||
.commentContentFooter {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.bodyHistoryToggle {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
line-height: 1px;
|
||||
font-weight: 300;
|
||||
text-decoration: underline;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.editedCommentBody {
|
||||
padding: 0 5px 5px 5px;
|
||||
}
|
||||
|
||||
.editedComment {
|
||||
margin-top: 6px;
|
||||
font-weight: 300;
|
||||
font-size: 16px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import t from 'coral-framework/services/i18n';
|
||||
class Comment extends React.Component {
|
||||
ref = null;
|
||||
|
||||
state = { showingEditHistory: false };
|
||||
|
||||
handleRef = ref => (this.ref = ref);
|
||||
|
||||
handleFocusOrClick = () => {
|
||||
@@ -45,6 +47,30 @@ class Comment extends React.Component {
|
||||
? null
|
||||
: this.props.rejectComment({ commentId: this.props.comment.id });
|
||||
|
||||
getBodyHistory = () => {
|
||||
const bodyHistory = [];
|
||||
const comment = this.props.comment;
|
||||
for (let i = 0; i < comment.body_history.length - 1; i++) {
|
||||
bodyHistory.push(
|
||||
<div key={i} className={styles.editedComment}>
|
||||
<div>
|
||||
<TimeAgo className={styles.created} datetime={comment.created_at} />
|
||||
</div>
|
||||
<div className={styles.editedCommentBody}>
|
||||
{comment.body_history[i].body}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return bodyHistory;
|
||||
};
|
||||
|
||||
toggleEditHistory = () => {
|
||||
this.setState({ showingEditHistory: !this.state.showingEditHistory });
|
||||
this.props.clearHeightCache && this.props.clearHeightCache();
|
||||
};
|
||||
|
||||
componentDidUpdate(prev) {
|
||||
if (!prev.selected && this.props.selected) {
|
||||
this.ref.focus();
|
||||
@@ -137,6 +163,14 @@ class Comment extends React.Component {
|
||||
<span className={styles.editedMarker}>
|
||||
({t('comment.edited')})
|
||||
</span>
|
||||
<span
|
||||
className={styles.bodyHistoryToggle}
|
||||
onClick={this.toggleEditHistory}
|
||||
>
|
||||
{this.state.showingEditHistory
|
||||
? t('comment.hide_edit_history')
|
||||
: t('comment.show_edit_history')}
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
<div className={styles.adminCommentInfoBar}>
|
||||
@@ -201,6 +235,14 @@ class Comment extends React.Component {
|
||||
</div>
|
||||
</CommentAnimatedEdit>
|
||||
</div>
|
||||
|
||||
{this.state.showingEditHistory ? (
|
||||
<div className={styles.container}>
|
||||
{t('comment.edit_history')}
|
||||
{this.getBodyHistory()}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<CommentDetails
|
||||
root={root}
|
||||
comment={comment}
|
||||
|
||||
@@ -52,6 +52,10 @@ export default withFragments({
|
||||
status_history {
|
||||
type
|
||||
}
|
||||
body_history {
|
||||
body
|
||||
created_at
|
||||
}
|
||||
hasParent
|
||||
${getSlotFragmentSpreads(slots, 'comment')}
|
||||
...${getDefinitionName(CommentLabels.fragments.comment)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import URLSearchParams from '@ungap/url-search-params';
|
||||
import pym from 'pym.js';
|
||||
import EventEmitter from 'eventemitter2';
|
||||
import { buildUrl } from 'coral-framework/utils/url';
|
||||
|
||||
import SnackBar from './SnackBar';
|
||||
import onIntersect from './onIntersect';
|
||||
import {
|
||||
@@ -92,6 +93,21 @@ function viewportDimensions() {
|
||||
};
|
||||
}
|
||||
|
||||
function parseAMPHash(opts) {
|
||||
const result = { ...opts };
|
||||
const query = window.location.hash.length && window.location.hash.substr(1);
|
||||
if (query) {
|
||||
const parsed = queryString.parse(query);
|
||||
if (parsed.asset_url && !result.asset_url) {
|
||||
result.asset_url = parsed.asset_url;
|
||||
}
|
||||
if (parsed.asset_id && !result.asset_id) {
|
||||
result.asset_id = parsed.asset_id;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export default class Bridge {
|
||||
constructor(
|
||||
element,
|
||||
@@ -110,19 +126,23 @@ export default class Bridge {
|
||||
lazy = process.env.TALK_DEFAULT_LAZY_RENDER === 'TRUE',
|
||||
// Any additional options are extracted to be sent to the embed via the
|
||||
// pym bridge.
|
||||
amp,
|
||||
...opts
|
||||
}
|
||||
) {
|
||||
this.pym = null;
|
||||
this.element = element;
|
||||
this.opts = opts;
|
||||
this.amp = amp;
|
||||
this.lazy = !amp && lazy;
|
||||
|
||||
// Parse amp hash.
|
||||
this.opts = amp ? parseAMPHash(opts) : opts;
|
||||
this.query = buildQuery(this.opts);
|
||||
this.emitter = new EventEmitter({ wildcard: true });
|
||||
this.snackBar = new SnackBar(snackBarStyles || {});
|
||||
this.snackBar = amp ? null : new SnackBar(snackBarStyles || {});
|
||||
this.onAuthChanged = onAuthChanged;
|
||||
this.talkBaseUrl = ensureEndSlash(talkBaseUrl);
|
||||
this.talkStaticUrl = ensureEndSlash(talkStaticUrl);
|
||||
this.lazy = lazy;
|
||||
|
||||
// Store queued operations in a queue that can be processed once the stream
|
||||
// is rendered.
|
||||
@@ -179,6 +199,16 @@ export default class Bridge {
|
||||
if (height !== cachedHeight) {
|
||||
this.pym.el.firstChild.style.height = `${height}px`;
|
||||
cachedHeight = height;
|
||||
if (this.amp) {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
sentinel: 'amp',
|
||||
type: 'embed-size',
|
||||
height,
|
||||
},
|
||||
'*'
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -267,8 +297,10 @@ export default class Bridge {
|
||||
// Setup Pym.
|
||||
this.setupPym();
|
||||
|
||||
// Attach the snackBar to the pym parent and to the body of the page.
|
||||
this.snackBar.attach(window.document.body, this.pym);
|
||||
if (this.snackBar) {
|
||||
// Attach the snackBar to the pym parent and to the body of the page.
|
||||
this.snackBar.attach(window.document.body, this.pym);
|
||||
}
|
||||
|
||||
// If the user clicks outside the embed, then tell the embed.
|
||||
document.addEventListener('click', this.handleClick.bind(this), true);
|
||||
@@ -315,7 +347,10 @@ export default class Bridge {
|
||||
this.emitter.removeAllListeners();
|
||||
|
||||
// Remove the snackbar.
|
||||
this.snackBar.remove();
|
||||
if (this.snackBar) {
|
||||
this.snackBar.remove();
|
||||
this.snackBar = null;
|
||||
}
|
||||
|
||||
// Remove the pym parent.
|
||||
this.pym.remove();
|
||||
|
||||
@@ -56,6 +56,7 @@ export const Talk = {
|
||||
* @param {String} [config.auth_token] - (optional) A jwt representing the session
|
||||
* @param {String} [config.lazy] - (optional) If set the stream will only render lazily
|
||||
* @param {String} [config.talkStaticUrl] - (optional) Static URL used to serve Talk
|
||||
* @param {Boolean} [config.amp] - (optional) Run Talk in AMP mode
|
||||
* @return {Object}
|
||||
*/
|
||||
render: (element, config) => {
|
||||
|
||||
@@ -43,6 +43,32 @@ const CONFIG = {
|
||||
process.env.TALK_WHITELISTED_LANGUAGES &&
|
||||
process.env.TALK_WHITELISTED_LANGUAGES.split(',').map(l => l.trim()),
|
||||
|
||||
// USERNAME_CAST_REGEXP defiles the regex expression that will be used to
|
||||
// strip characters from a username during a username cast operation.
|
||||
USERNAME_CAST_REGEXP: new RegExp(
|
||||
process.env.USERNAME_CAST_REGEXP || '[^a-zA-Z_]',
|
||||
'g'
|
||||
),
|
||||
|
||||
// USERNAME_REPLACEMENT_CAST_REGEXP defiles the regex expression that will be
|
||||
// used to replace characters with the replacement character during a username
|
||||
// cast operation. First duplicates will be replaced, then
|
||||
USERNAME_REPLACEMENT_CAST_REGEXP: new RegExp(
|
||||
process.env.USERNAME_REPLACEMENT_CAST_REGEXP || ' +',
|
||||
'g'
|
||||
),
|
||||
|
||||
// USERNAME_REPLACEMENT_CHARACTER is the character used to replace other
|
||||
// characters matching the USERNAME_REPLACEMENT_CAST_REGEXP.
|
||||
USERNAME_REPLACEMENT_CHARACTER:
|
||||
process.env.USERNAME_REPLACEMENT_CHARACTER || '_',
|
||||
|
||||
// USERNAME_VALIDATION_REGEX defines the allowed characters for a username in
|
||||
// Talk.
|
||||
USERNAME_VALIDATION_REGEX: new RegExp(
|
||||
process.env.USERNAME_VALIDATION_REGEX || '^[A-Za-z0-9_]+$'
|
||||
),
|
||||
|
||||
// When TRUE, it ensures that database indexes created in core will not add
|
||||
// indexes.
|
||||
CREATE_MONGO_INDEXES: process.env.DISABLE_CREATE_MONGO_INDEXES !== 'TRUE',
|
||||
|
||||
+3
-3
@@ -76,9 +76,7 @@ sidebar:
|
||||
- title: GitHub
|
||||
url: https://github.com/coralproject/
|
||||
- title: Docker
|
||||
url: https://hub.docker.com/r/coralproject/
|
||||
- title: Roadmap
|
||||
url: https://www.pivotaltracker.com/n/projects/1863625
|
||||
url: https://hub.docker.com/r/coralproject/talk/
|
||||
side:
|
||||
- title: Installation
|
||||
children:
|
||||
@@ -122,6 +120,8 @@ sidebar:
|
||||
url: /integrating/translations-i18n/
|
||||
- title: GDPR Compliance
|
||||
url: /integrating/gdpr/
|
||||
- title: Accelerated Mobile Page
|
||||
url: /integrating/amp/
|
||||
- title: Product Guide
|
||||
children:
|
||||
- title: How Talk Works
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"hexo": {
|
||||
"version": "3.7.1"
|
||||
"version": "3.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "hexo serve",
|
||||
|
||||
@@ -5,9 +5,18 @@ permalink: /
|
||||
|
||||
Online comments are broken. Our open-source Talk tool rethinks how moderation,
|
||||
comment display, and conversation function, creating the opportunity for safer,
|
||||
smarter discussions around your work. Read more about our product features and
|
||||
goals [here](https://coralproject.net/talk). The
|
||||
documentation available here is pertaining to the technical details for
|
||||
smarter discussions around your work.
|
||||
|
||||
More than 60 newsrooms use Talk to run their on-site communities, including the Washington Post, the Wall Street Journal, and IGN.
|
||||
|
||||
[Read more about our product features and
|
||||
goals here](https://coralproject.net/talk).
|
||||
|
||||
<div class="callout">
|
||||
We offer hosting and support packages for Talk. [Contact us for more information.](https://coralproject.net/pricing/)
|
||||
</div>
|
||||
|
||||
The documentation available here is pertaining to the technical details for
|
||||
installing, configuring, and deploying Talk.
|
||||
|
||||
Talk is a [Node](https://nodejs.org/) application with
|
||||
@@ -46,7 +55,7 @@ Start by making a new directory and create a file called `docker-compose.yml` an
|
||||
version: '2'
|
||||
services:
|
||||
talk:
|
||||
image: coralproject/talk:4.5
|
||||
image: coralproject/talk:4
|
||||
restart: always
|
||||
ports:
|
||||
- "3000:3000"
|
||||
|
||||
@@ -43,7 +43,7 @@ be used to setup Talk:
|
||||
version: '2'
|
||||
services:
|
||||
talk:
|
||||
image: coralproject/talk:4.5
|
||||
image: coralproject/talk:4
|
||||
restart: always
|
||||
ports:
|
||||
- "3000:3000"
|
||||
@@ -121,7 +121,7 @@ base installation with additional custom plugins. Images can be created with the
|
||||
most basic of `Dockerfile`'s:
|
||||
|
||||
```docker
|
||||
FROM coralproject/talk:4.5-onbuild
|
||||
FROM coralproject/talk:4-onbuild
|
||||
```
|
||||
|
||||
And running the following to build the docker image:
|
||||
@@ -153,7 +153,7 @@ your containerized infrastructure. The versioning of our Docker tags as well
|
||||
lets you do something like:
|
||||
|
||||
```docker
|
||||
FROM coralproject/talk:4.5-onbuild
|
||||
FROM coralproject/talk:4-onbuild
|
||||
```
|
||||
|
||||
Which would pin your image to `4.5.x release's.
|
||||
Which would pin your image to `4.x.x release's.
|
||||
|
||||
@@ -77,7 +77,8 @@ permalink: /pre-launch-checklist/
|
||||
- See [our blog for more information](https://coralproject.net/blog/slacking-on/)
|
||||
|
||||
|
||||
- [ ] Has your community team configured Talk to match your community strategy?
|
||||
- [ ] Have you configured Talk’s admin settings and determined your community strategy?
|
||||
- See [our tutorial for more information](https://docs.coralproject.net/talk/when-youve-installed-talk/)
|
||||
- See [Configuring Talk](/talk/configuring-talk/)
|
||||
|
||||
|
||||
|
||||
@@ -81,12 +81,17 @@ Usage: cli-assets [options] [command]
|
||||
Commands:
|
||||
|
||||
list [options] list all the assets in the database
|
||||
debug <url> prints the scraped metadata from that URL
|
||||
refresh [age] queues the assets that exceed the age requested
|
||||
update-url <assetID> <url> update the URL of an asset
|
||||
merge <srcID> <dstID> merges two assets together by moving comments from src to dst and deleting the src asset
|
||||
rewrite [options] <search> <replace> rewrites asset url's using the provided regex replacement pattern
|
||||
```
|
||||
|
||||
When using the `refresh` command, the `age` value specifies how far back in time to re-scrape assets; i.e. to re-scrape everything that was scraped in the last week use `1w` or `7d`. Supports ms (milliseconds), s (seconds), m (minutes), h (hours), d (days) and w (weeks). Assets that have not been scraped will also be queued for scraping.
|
||||
|
||||
See also, [Asset Scraping](/talk/integrating/asset-scraping/) for more details about asset scraping.
|
||||
|
||||
## Setting up the application
|
||||
You can also run a setup wizard to setup the wizard using `./bin/cli setup`. Below is a list of additional options available for this command:
|
||||
```
|
||||
|
||||
@@ -57,7 +57,7 @@ The timeframe in seconds in which commenters have to edit their comment.
|
||||
|
||||
#### Close Comments After
|
||||
|
||||
Default time after which all comment streams will close.
|
||||
Default time after which all comment streams will close. Applies to assets created in Talk after this configuration is saved, and does not update existing assets.
|
||||
|
||||
### Moderation Settings
|
||||
|
||||
|
||||
@@ -87,6 +87,8 @@ The most important predictors of the success of an online community are:
|
||||
|
||||
#### Effectively
|
||||
|
||||
* Keep conversations fresh and reduce moderation overhead by configuring Talk to automatically close commenting after a specified time window. Should comments be open for a day, a week, or a month? Set a manageable window and automatically close stories for commenting after that time period.
|
||||
|
||||
* If you're using the Toxic Comments plugin, make sure that its threshold is set at the level that catches most comments with fewest false positives (default is 80%). You can see the Likely to be Toxic level of every comment by clicking "More Details" on the comment card in the moderation view.
|
||||
|
||||
* Publicly discourage behavior in the comments that doesn't cross the line but suggests that the tone or focus could shift quickly in a direction you don't want. Point to relevant sections of your community guidelines. [Read more about defining and discouraging this kind of behavior here.](https://guides.coralproject.net/manage-a-successful-community/)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Accelerated Mobile Page
|
||||
permalink: /integrating/amp/
|
||||
---
|
||||
|
||||
[AMP](https://amp.dev/) is a light-weight, stripped down HTML page that aims to improve reader experience. _Talk v4.9.0+_ comes with [AMP](https://amp.dev/) support. The current caveat however is that _toast notifications_ are not being rendered when viewing inside AMP.
|
||||
|
||||
# How to integrate
|
||||
Put the following code into your _AMP_ page and replace `$TALK_URL` and `$ASSET_URL` with the
|
||||
corresponding values. You can also pass `asset_id` instead of `asset_url`.
|
||||
|
||||
```html
|
||||
<amp-iframe
|
||||
width=600 height=140
|
||||
layout="responsive"
|
||||
sandbox="allow-scripts allow-same-origin allow-modals allow-popups allow-forms"
|
||||
resizable
|
||||
src="https://$TALK_URL/embed/amp#asset_url=$ASSET_URL">
|
||||
<div placeholder></div>
|
||||
<div overflow tabindex=0 role=button aria-label="Read more">Read more</div>
|
||||
</amp-iframe>
|
||||
```
|
||||
|
||||
## Single Sign-On
|
||||
For SSO integration you need to create a page with the following output and replace `$TALK_URL` and `$AUTH_TOKEN` with the appropriate values. Inject your SSO auth scripts to get the `$AUTH_TOKEN` for the current user. Integrating with [amp-access](https://amp.dev/documentation/components/amp-access) is recommended which opens a 1st-party popup to not have browsers block your cookies. This page is then used in `src` of `<amp-iframe>` above. It must be accessed over `https` and live in a different domain than the `amp` page.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
<title>Coral Talk Amp Embed</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id='coralStreamEmbed'></div>
|
||||
<script src="https://$TALK_URL/static/embed.js"></script>
|
||||
<script>
|
||||
window.TalkEmbed = Coral.Talk.render(document.getElementById('coralStreamEmbed'), {
|
||||
talk: '$TALK_URL',
|
||||
auth_token: '$AUTH_TOKEN',
|
||||
amp: true,
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -9,6 +9,9 @@ in a simple way. We use the following
|
||||
[meta tags](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta) on
|
||||
the target pages that allow us to extract some properties.
|
||||
|
||||
Asset scraping is performed by the `scraper` job which is enabled by default when you launch Talk. If your production site is behind a paywall or otherwise prevents scraping, you might need to confiugre a [TALK_SCRAPER_PROXY_URL](/talk/advanced-configuration/#talk-scraper-proxy-url) or custom [TALK_SCRAPER_HEADERS](/talk/advanced-configuration/#talk-scraper-headers).
|
||||
|
||||
|
||||
| Asset Property | Selector |
|
||||
|--------------------|----------|
|
||||
| `title` | See [`metascraper-title`](https://github.com/microlinkhq/metascraper/blob/dc664c37ea1b238b1e3e9d5342edfacc9027892c/packages/metascraper-title/index.js) |
|
||||
@@ -19,7 +22,7 @@ the target pages that allow us to extract some properties.
|
||||
| `modified_date` | `meta[property="article:modified"]` |
|
||||
| `section` | `meta[property="article:section"]` |
|
||||
|
||||
You can use the `./bin/cli assets debug <url>` to print the scraped metadata
|
||||
You can use the `./bin/cli assets debug <url>` command to print the scraped metadata
|
||||
from that URL. For example:
|
||||
|
||||
```bash
|
||||
@@ -41,4 +44,8 @@ from that URL. For example:
|
||||
├──────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ section │ │
|
||||
└──────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
|
||||
You can use the `./bin/cli assets refresh [age]` to trigger scraping or rescrape assets where the scraper job was unsuccessful.
|
||||
@@ -111,7 +111,7 @@ If you deploy using Docker, you can extend from the `*-onbuild` image, an
|
||||
example `Dockerfile` for your project could be:
|
||||
|
||||
```Dockerfile
|
||||
FROM coralproject/talk:4.5-onbuild
|
||||
FROM coralproject/talk:4-onbuild
|
||||
```
|
||||
|
||||
Establish a private repository for your instance that includes the following:
|
||||
|
||||
+19
@@ -465,3 +465,22 @@ a.brand {
|
||||
background: rgb(0, 102, 176);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.callout {
|
||||
background-color: $coral_button_background;
|
||||
color: white;
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
@extend .coral-link;
|
||||
color: #FFF;
|
||||
border-color: #FFF;
|
||||
|
||||
&:hover {
|
||||
color: #FFF;
|
||||
border-color: darken(#FFF, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,13 +64,13 @@ const wrapCheck = (
|
||||
|
||||
/**
|
||||
* checkPermissions checks that the current user has all the required
|
||||
* permissions.
|
||||
* permissions. It will return true if that's the case.
|
||||
*
|
||||
* @param {Object} ctx graph context
|
||||
* @param {Array<String>} permissions permissions that the user must have
|
||||
*/
|
||||
const checkPermissions = (ctx, permissions) =>
|
||||
!ctx.user || !ctx.user.can(...permissions);
|
||||
ctx.user && ctx.user.can(...permissions);
|
||||
|
||||
/**
|
||||
* wrapCheckPermissions will wrap a specific field with a permission check.
|
||||
@@ -89,7 +89,7 @@ const wrapCheckPermissions = (
|
||||
wrapCheck(
|
||||
typeResolver,
|
||||
field,
|
||||
(obj, args, ctx) => !checkPermissions(ctx, permissions),
|
||||
(obj, args, ctx) => checkPermissions(ctx, permissions),
|
||||
skipFieldResolver
|
||||
);
|
||||
|
||||
|
||||
@@ -879,6 +879,9 @@ type Settings {
|
||||
# customCssUrl is the URL of the custom CSS used to display on the frontend.
|
||||
customCssUrl: String
|
||||
|
||||
# customAdminCssUrl is the URL of the custom CSS used to display on the admin panel.
|
||||
customAdminCssUrl: String
|
||||
|
||||
# closedTimeout is the amount of seconds from the created_at timestamp that a
|
||||
# given asset will be considered closed.
|
||||
closedTimeout: Int
|
||||
@@ -1353,6 +1356,9 @@ input UpdateSettingsInput {
|
||||
# customCssUrl is the URL of the custom CSS used to display on the frontend.
|
||||
customCssUrl: String
|
||||
|
||||
# customAdminCssUrl is the URL of the custom CSS used to display on the admin panel.
|
||||
customAdminCssUrl: String
|
||||
|
||||
# closedTimeout is the amount of seconds from the created_at timestamp that a
|
||||
# given asset will be considered closed.
|
||||
closedTimeout: Int
|
||||
|
||||
+6
-1
@@ -111,7 +111,12 @@ const processJob = transport => async ({ id, data }, done) => {
|
||||
const { message } = data;
|
||||
|
||||
// Get the email address from the job data.
|
||||
message.to = await getEmailAddress(data);
|
||||
try {
|
||||
message.to = await getEmailAddress(data);
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Failed to get user email address to send mail');
|
||||
return done(err);
|
||||
}
|
||||
|
||||
const log = logger.child({ jobID: id });
|
||||
log.info('Starting to send mail');
|
||||
|
||||
@@ -35,6 +35,9 @@ en:
|
||||
flagged: flagged
|
||||
undo_reject: Undo
|
||||
view_context: 'View context'
|
||||
edit_history: 'Edit history'
|
||||
show_edit_history: 'Show edit history'
|
||||
hide_edit_history: 'Hide edit history'
|
||||
comment_box:
|
||||
cancel: Cancel
|
||||
characters_remaining: 'characters remaining'
|
||||
@@ -124,6 +127,8 @@ en:
|
||||
copy_and_paste: 'Copy and paste code below into your CMS to embed your comment box in your articles'
|
||||
custom_css_url: 'Custom CSS URL'
|
||||
custom_css_url_desc: 'URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external.'
|
||||
custom_admin_css_url: 'Custom Admin Panel CSS URL'
|
||||
custom_admin_css_url_desc: 'URL of a CSS stylesheet that will override default admin panel styles. Can be internal or external.'
|
||||
days: Days
|
||||
description: 'Change the comment settings on this story.'
|
||||
disable_commenting_desc: 'Write a message that will be displayed while commenting is deactivated.'
|
||||
|
||||
@@ -35,6 +35,9 @@ es:
|
||||
flagged: Reportado
|
||||
undo_reject: Deshacer
|
||||
view_context: 'Ver contexto'
|
||||
edit_history: 'Historial de ediciones'
|
||||
show_edit_history: 'Mostrar historial de ediciones'
|
||||
hide_edit_history: 'Ocultar historial de ediciones'
|
||||
comment_box:
|
||||
cancel: Cancelar
|
||||
characters_remaining: 'caracteres restantes'
|
||||
|
||||
@@ -12,6 +12,8 @@ const {
|
||||
STATIC_ORIGIN,
|
||||
} = require('../url');
|
||||
|
||||
const { PORT } = require('../config');
|
||||
|
||||
const { RECAPTCHA_PUBLIC, WEBSOCKET_LIVE_URI } = require('../config');
|
||||
|
||||
// Grab TALK_CLIENT_* environment variables.
|
||||
@@ -42,6 +44,7 @@ const TEMPLATE_LOCALS = {
|
||||
MOUNT_PATH,
|
||||
STATIC_URL,
|
||||
TALK_CLIENT_ENV,
|
||||
PORT,
|
||||
data: TALK_CLIENT_ENV,
|
||||
};
|
||||
|
||||
@@ -96,12 +99,18 @@ const createResolveFactory = (() => {
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
try {
|
||||
// Attach the custom css url and organization name.
|
||||
const { customCssUrl, organizationName } = await SettingsService.select(
|
||||
// Attach the custom css urls and organization name.
|
||||
const {
|
||||
customCssUrl,
|
||||
customAdminCssUrl,
|
||||
organizationName,
|
||||
} = await SettingsService.select(
|
||||
'customCssUrl',
|
||||
'customAdminCssUrl',
|
||||
'organizationName'
|
||||
);
|
||||
res.locals.customCssUrl = customCssUrl;
|
||||
res.locals.customAdminCssUrl = customAdminCssUrl;
|
||||
res.locals.organizationName = organizationName;
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
|
||||
@@ -28,6 +28,10 @@ const Setting = new Schema(
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
customAdminCssUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
infoBoxContent: {
|
||||
type: String,
|
||||
default: '',
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "talk",
|
||||
"version": "4.8.4",
|
||||
"version": "4.9.0",
|
||||
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
|
||||
"main": "app.js",
|
||||
"private": true,
|
||||
|
||||
@@ -11,25 +11,44 @@ plugin:
|
||||
- Client
|
||||
---
|
||||
|
||||
Enables sign-in via Facebook via the server side passport middleware.
|
||||
Enables sign-in via Facebook via the server side passport middleware. Requires creating and registering a login app with Facebook.
|
||||
|
||||
Configuration:
|
||||
**Configuration:**
|
||||
|
||||
- `TALK_FACEBOOK_APP_ID` (**required**) - The Facebook App ID for your Facebook
|
||||
Login enabled app. You can learn more about getting a Facebook App ID at the
|
||||
[Facebook Developers Portal](https://developers.facebook.com) or by visiting
|
||||
the [Creating an App ID](https://developers.facebook.com/docs/apps/register)
|
||||
guide. This is only required while the `talk-plugin-facebook-auth` plugin is
|
||||
Login enabled app. This is only required while the `talk-plugin-facebook-auth` plugin is
|
||||
enabled.
|
||||
- `TALK_FACEBOOK_APP_SECRET` (**required**) - The Facebook App Secret for your
|
||||
Facebook Login enabled app. You can learn more about getting a Facebook App
|
||||
Secret at the [Facebook Developers Portal](https://developers.facebook.com)
|
||||
or by visiting the
|
||||
[Creating an App ID](https://developers.facebook.com/docs/apps/register)
|
||||
guide. This is only required while the `talk-plugin-facebook-auth` plugin is
|
||||
Facebook Login enabled app. This is only required while the `talk-plugin-facebook-auth` plugin is
|
||||
enabled.
|
||||
|
||||
You can learn more about getting a Facebook App ID at the
|
||||
[Facebook Developers Portal](https://developers.facebook.com) or by visiting
|
||||
their [Creating an App ID](https://developers.facebook.com/docs/apps/register)
|
||||
guide.
|
||||
|
||||
_NOTE: FabceBook auth requires your site to use `https` (SSL) not `http`. If your site is not `https` you can not use this plugin!_
|
||||
**Setting up your Facebook app:**
|
||||
* Go to [Facebook Developers Portal](https://developers.facebook.com) and click on Getting Started or My Apps
|
||||
* Create a new app > set the App Name and Email to create an app id
|
||||
* Confirm that you are not a robot, then configure the app as follows:
|
||||
* In Settings > Basic:
|
||||
* add app domains (your Talk domain)
|
||||
* add a link to your privacy policy
|
||||
* add a link to your terms of service
|
||||
* In Settings > Advanced:
|
||||
* turn on "Require App Secret"
|
||||
* Add a "Product" (Under "Products" click + to add a Product):
|
||||
* Setup "Facebook Login"
|
||||
* choose `www`
|
||||
* enter your Talk domain url
|
||||
* click _Next_ several times to get through the add code steps (You do not need to modify any code, the plugin takes care of this part for you.)
|
||||
* Under Product Settings:
|
||||
* set Valid OAuth Redirect URIs to your callback url (Use your Talk domain with this endpoint: `/api/v1/auth/facebook/callback`)
|
||||
* Locate your App Id and App Secret, set these as config vars on your instance of Talk
|
||||
* Toggle the "Live" button on the top bar to make app live
|
||||
|
||||
|
||||
_NOTE: Facebook auth requires your site to use `https` (SSL) not `http`. If your site is not `https` you can not use this plugin!_
|
||||
|
||||
## GDPR Compliance
|
||||
|
||||
|
||||
@@ -42,19 +42,10 @@
|
||||
text-align: left;
|
||||
letter-spacing: 0.1px;
|
||||
margin: 0;
|
||||
quotes: '\201c' '\201d';
|
||||
margin-bottom: 10px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.quote:before {
|
||||
content: open-quote;
|
||||
}
|
||||
|
||||
.quote:after {
|
||||
content: close-quote;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ type CancelAccountDeletionResponse implements Response {
|
||||
errors: [UserError!]
|
||||
}
|
||||
|
||||
# DownloadUserResponse contaisn the account download archiveURL that can be used
|
||||
# DownloadUserResponse contains the account download archiveURL that can be used
|
||||
# to directly download a zip file containing the user data.
|
||||
type DownloadUserResponse implements Response {
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class Editor extends React.Component {
|
||||
};
|
||||
|
||||
getHTML(props = this.props) {
|
||||
if (props.input.richTextBody) {
|
||||
if (props.input.richTextBody !== undefined) {
|
||||
return props.input.richTextBody;
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
return res.render('dev/amp.njk', {
|
||||
title: 'Coral Talk AMP',
|
||||
asset_url: '',
|
||||
asset_id: '',
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/button', (req, res) => {
|
||||
return res.render('dev/amp-button.njk', {
|
||||
title: 'Coral Talk AMP',
|
||||
asset_url: '',
|
||||
asset_id: '',
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -19,5 +19,6 @@ router.get('/', staticTemplate, async (req, res) => {
|
||||
});
|
||||
}
|
||||
});
|
||||
router.use('/amp', staticTemplate, require('./amp'));
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -5,4 +5,8 @@ router.use('/stream', (req, res) => {
|
||||
res.render('embed/stream.njk');
|
||||
});
|
||||
|
||||
router.use('/amp', (req, res) => {
|
||||
res.render('embed/amp.njk');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
+14
-5
@@ -22,6 +22,10 @@ const {
|
||||
ROOT_URL,
|
||||
RECAPTCHA_WINDOW,
|
||||
RECAPTCHA_INCORRECT_TRIGGER,
|
||||
USERNAME_CAST_REGEXP,
|
||||
USERNAME_REPLACEMENT_CAST_REGEXP,
|
||||
USERNAME_REPLACEMENT_CHARACTER,
|
||||
USERNAME_VALIDATION_REGEX,
|
||||
} = require('../config');
|
||||
const { jwt: JWT_SECRET } = require('../secrets');
|
||||
const debug = require('debug')('talk:services:users');
|
||||
@@ -525,7 +529,10 @@ class Users {
|
||||
}
|
||||
|
||||
static castUsername(username) {
|
||||
return username.replace(/ /g, '_').replace(/[^a-zA-Z_]/g, '');
|
||||
return username
|
||||
.trim()
|
||||
.replace(USERNAME_REPLACEMENT_CAST_REGEXP, USERNAME_REPLACEMENT_CHARACTER)
|
||||
.replace(USERNAME_CAST_REGEXP, '');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -554,7 +561,11 @@ class Users {
|
||||
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
||||
// Generate `GROUP_ATTEMPTS` guesses for the username.
|
||||
const usernameGuesses = Array.from(Array(GROUP_ATTEMPTS)).map(
|
||||
() => `${castedName}_${random(0, END_NUMBER_MAX)}`
|
||||
() =>
|
||||
`${castedName}${USERNAME_REPLACEMENT_CHARACTER}${random(
|
||||
0,
|
||||
END_NUMBER_MAX
|
||||
)}`
|
||||
);
|
||||
|
||||
// Map them all to lowercase.
|
||||
@@ -684,13 +695,11 @@ class Users {
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async isValidUsername(username, checkAgainstWordlist = true) {
|
||||
const onlyLettersNumbersUnderscore = /^[A-Za-z0-9_]+$/;
|
||||
|
||||
if (!username) {
|
||||
throw new ErrMissingUsername();
|
||||
}
|
||||
|
||||
if (!onlyLettersNumbersUnderscore.test(username)) {
|
||||
if (!USERNAME_VALIDATION_REGEX.test(username)) {
|
||||
throw new ErrSpecialChars();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ describe('graph.queries.settings', () => {
|
||||
questionBoxIcon
|
||||
autoCloseStream
|
||||
customCssUrl
|
||||
customAdminCssUrl
|
||||
closedTimeout
|
||||
closedMessage
|
||||
charCountEnable
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
|
||||
<link href="https://code.getmdl.io/1.2.1/material.min.css" rel="stylesheet">
|
||||
<link href="{{ resolve('coral-admin/bundle.css') }}" rel="stylesheet">
|
||||
|
||||
{# Custom CSS is included after the CSS block so that its overrides will apply #}
|
||||
{% include "partials/custom-admin-css.njk" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<!-- ## Introduction -->
|
||||
<!--
|
||||
This is a sample showing how to use Talk with AMP. You need to access
|
||||
this using an URL other than localhost. You can use ngrok to achieve that.
|
||||
-->
|
||||
<!-- -->
|
||||
<!doctype html>
|
||||
<html ⚡>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script async src="https://cdn.ampproject.org/v0.js"></script>
|
||||
<link rel="canonical" href="/dev">
|
||||
|
||||
<!-- ## Setup -->
|
||||
<script async custom-element="amp-iframe" src="https://cdn.ampproject.org/v0/amp-iframe-0.1.js"></script>
|
||||
<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
|
||||
|
||||
<title>Coral Talk AMP</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
|
||||
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
|
||||
|
||||
<style amp-custom>
|
||||
.container {
|
||||
width: auto;
|
||||
max-width: 680px;
|
||||
padding: 0 15px;
|
||||
margin: auto;
|
||||
}
|
||||
.title {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: .5rem;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
|
||||
}
|
||||
.hide{
|
||||
display:none
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 class="title">Coral Talk AMP</h1>
|
||||
<p>
|
||||
Ask to go outside and ask to come inside and ask to go outside and ask to
|
||||
come inside the dog smells bad. Lick butt and make a weird face. Toilet
|
||||
paper attack claws fluff everywhere meow miao french ciao litterbox. Shake
|
||||
treat bag immediately regret falling into bathtub or white cat sleeps on a
|
||||
black shirt so what a cat-ass-trophy! eat owner's food spit up on light
|
||||
gray carpet instead of adjacent linoleum. Warm up laptop with butt lick
|
||||
butt fart rainbows until owner yells pee in litter box hiss at cats
|
||||
scratch the box so loved it, hated it, loved it, hated it but need to
|
||||
check on human, have not seen in an hour might be dead oh look, human is
|
||||
alive, hiss at human, feed me.
|
||||
</p>
|
||||
<button id=menu on="tap:AMP.setState({visible: !visible})">Show Comments</button>
|
||||
<div [class]=visible?"show":"hide" class="hide">
|
||||
<amp-iframe
|
||||
width=600 height=140
|
||||
layout="responsive"
|
||||
sandbox="allow-scripts allow-same-origin allow-modals allow-popups allow-forms"
|
||||
resizable
|
||||
src="http://localhost:3000/embed/amp">
|
||||
<div placeholder></div>
|
||||
<div overflow tabindex=0 role=button aria-label="Read more">Read more</div>
|
||||
</amp-iframe>
|
||||
</div<
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<!-- ## Introduction -->
|
||||
<!--
|
||||
This is a sample showing how to use Talk with AMP. You need to access
|
||||
this using an URL other than localhost. You can use ngrok to achieve that.
|
||||
-->
|
||||
<!-- -->
|
||||
<!doctype html>
|
||||
<html ⚡>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script async src="https://cdn.ampproject.org/v0.js"></script>
|
||||
<link rel="canonical" href="/dev">
|
||||
|
||||
<!-- ## Setup -->
|
||||
<script async custom-element="amp-iframe" src="https://cdn.ampproject.org/v0/amp-iframe-0.1.js"></script>
|
||||
|
||||
<title>Coral Talk AMP</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
|
||||
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
|
||||
|
||||
<style amp-custom>
|
||||
.container {
|
||||
width: auto;
|
||||
max-width: 680px;
|
||||
padding: 0 15px;
|
||||
margin: auto;
|
||||
}
|
||||
.title {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: .5rem;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 class="title">Coral Talk AMP</h1>
|
||||
<p>
|
||||
Dismember a mouse and then regurgitate parts of it on the family room
|
||||
floor. Dont wait for the storm to pass, dance in the rain stand in front
|
||||
of the computer screen, so stares at human while pushing stuff off a table
|
||||
chew the plant meow hiss at vacuum cleaner. Terrorize the
|
||||
hundred-and-twenty-pound rottweiler and steal his bed, not sorry chew the
|
||||
plant. Litter kitter kitty litty little kitten big roar roar feed me rub
|
||||
whiskers on bare skin act innocent sleep on keyboard, so give me attention
|
||||
or face the wrath of my claws for demand to be let outside at once, and
|
||||
expect owner to wait for me as i think about it spread kitty litter all
|
||||
over house so nya nya nyan. Catty ipsum massacre a bird in the living room
|
||||
and then look like the cutest and most innocent animal on the planet you
|
||||
have cat to be kitten me right meow. Hiss and stare at nothing then run
|
||||
suddenly away refuse to come home when humans are going to bed; stay out
|
||||
all night then yowl like i am dying at 4am and lick plastic bags. Chase
|
||||
dog then run away purrr purr littel cat, little cat purr purr and step on
|
||||
your keyboard while you're gaming and then turn in a circle . Twitch tail
|
||||
in permanent irritation put butt in owner's face and the dog smells bad
|
||||
yet attempt to leap between furniture but woefully miscalibrate and
|
||||
bellyflop onto the floor; what's your problem? i meant to do that now i
|
||||
shall wash myself intently. Sniff all the things groom forever, stretch
|
||||
tongue and leave it slightly out, blep, but bring your owner a dead bird
|
||||
decide to want nothing to do with my owner today for lay on arms while
|
||||
you're using the keyboard meow meow, i tell my human or scratch. Sleep on
|
||||
my human's head then cats take over the world bleghbleghvomit my furball
|
||||
really tie the room together sleep more napping, more napping all the
|
||||
napping is exhausting. When in doubt, wash drink water out of the faucet,
|
||||
cats are fats i like to pets them they like to meow back and cat dog hate
|
||||
mouse eat string barf pillow no baths hate everything yet swat at dog
|
||||
kitty kitty but you call this cat food. Cough furball into food bowl then
|
||||
scratch owner for a new one flex claws on the human's belly and purr like
|
||||
a lawnmower for has closed eyes but still sees you groom yourself 4 hours
|
||||
- checked, have your beauty sleep 18 hours - checked, be fabulous for the
|
||||
rest of the day - checked. Freak human out make funny noise mow mow mow
|
||||
mow mow mow success now attack human flex claws on the human's belly and
|
||||
purr like a lawnmower or meowwww. Terrorize the hundred-and-twenty-pound
|
||||
rottweiler and steal his bed, not sorry paw at your fat belly so yowling
|
||||
nonstop the whole night small kitty warm kitty little balls of fur or eat
|
||||
owner's food reward the chosen human with a slow blink. Gate keepers of
|
||||
hell plan steps for world domination for more napping, more napping all
|
||||
the napping is exhausting give me some of your food give me some of your
|
||||
food give me some of your food meh, i don't want it so flop over. Make
|
||||
meme, make cute face ears back wide eyed so sit and stare. Dead stare with
|
||||
ears cocked furrier and even more furrier hairball. Stand in front of the
|
||||
computer screen demand to have some of whatever the human is cooking, then
|
||||
sniff the offering and walk away for catasstrophe, kitty scratches couch
|
||||
bad kitty. Wack the mini furry mouse intrigued by the shower, and pooping
|
||||
rainbow while flying in a toasted bread costume in space. Mesmerizing
|
||||
birds love me! shake treat bag, yet lies down where is my slave? I'm
|
||||
getting hungry so lick face hiss at owner, pee a lot, and meow repeatedly
|
||||
scratch at fence purrrrrr eat muffins and poutine until owner comes back.
|
||||
You have cat to be kitten me right meow sniff other cat's butt and hang
|
||||
jaw half open thereafter but run outside as soon as door open so munch on
|
||||
tasty moths or munch on tasty moths, for paw at beetle and eat it before
|
||||
it gets away. Sit on human. Gnaw the corn cob massacre a bird in the
|
||||
living room and then look like the cutest and most innocent animal on the
|
||||
planet for sit on the laptop. Meow scratch leg; meow for can opener to
|
||||
feed me cat fur is the new black but hide when guests come over, and Gate
|
||||
keepers of hell. Refuse to come home when humans are going to bed; stay
|
||||
out all night then yowl like i am dying at 4am cat slap dog in face or eat
|
||||
a rug and furry furry hairs everywhere oh no human coming lie on counter
|
||||
don't get off counter for i like fish sit on human they not getting up
|
||||
ever but meow meow but cuddle no cuddle cuddle love scratch scratch.
|
||||
</p>
|
||||
<p>
|
||||
I show my fluffy belly but it's a trap! if you pet it i will tear up your
|
||||
hand refuse to drink water except out of someone's glass mice, so cough
|
||||
hairball, eat toilet paper or curl into a furry donut lick sellotape but
|
||||
wack the mini furry mouse. When owners are asleep, cry for no apparent
|
||||
reason. Chase imaginary bugs. Stinky cat reward the chosen human with a
|
||||
slow blink, or chase dog then run away. Chew on cable scratch the
|
||||
furniture for you are a captive audience while sitting on the toilet, pet
|
||||
me for i like cats because they are fat and fluffy and spend all night
|
||||
ensuring people don't sleep sleep all day. Scoot butt on the rug need to
|
||||
check on human, have not seen in an hour might be dead oh look, human is
|
||||
alive, hiss at human, feed me, leave fur on owners clothes, so instantly
|
||||
break out into full speed gallop across the house for no reason play
|
||||
riveting piece on synthesizer keyboard and scoot butt on the rug yet meow
|
||||
meow. Attack dog, run away and pretend to be victim annoy the old grumpy
|
||||
cat, start a fight and then retreat to wash when i lose or meow go back to
|
||||
sleep owner brings food and water tries to pet on head, so scratch get
|
||||
sprayed by water because bad cat. Meowwww pelt around the house and up and
|
||||
down stairs chasing phantoms drink water out of the faucet meow meow, i
|
||||
tell my human. Destroy couch.
|
||||
</p>
|
||||
<p>
|
||||
Ask to go outside and ask to come inside and ask to go outside and ask to
|
||||
come inside the dog smells bad. Lick butt and make a weird face. Toilet
|
||||
paper attack claws fluff everywhere meow miao french ciao litterbox. Shake
|
||||
treat bag immediately regret falling into bathtub or white cat sleeps on a
|
||||
black shirt so what a cat-ass-trophy! eat owner's food spit up on light
|
||||
gray carpet instead of adjacent linoleum. Warm up laptop with butt lick
|
||||
butt fart rainbows until owner yells pee in litter box hiss at cats
|
||||
scratch the box so loved it, hated it, loved it, hated it but need to
|
||||
check on human, have not seen in an hour might be dead oh look, human is
|
||||
alive, hiss at human, feed me.
|
||||
</p>
|
||||
<amp-iframe
|
||||
width=600 height=140
|
||||
layout="responsive"
|
||||
sandbox="allow-scripts allow-same-origin allow-modals allow-popups allow-forms"
|
||||
resizable
|
||||
src="http://localhost:{{ PORT }}/embed/amp">
|
||||
<div placeholder></div>
|
||||
<div overflow tabindex=0 role=button aria-label="Read more">Read more</div>
|
||||
</amp-iframe>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
<title>Coral Talk Amp Embed</title>
|
||||
<style>body { margin: 0; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='coralStreamEmbed'></div>
|
||||
<script src="{{ resolve('embed.js') }}"></script>
|
||||
<script>
|
||||
window.TalkEmbed = Coral.Talk.render(document.getElementById('coralStreamEmbed'), {
|
||||
talk: '{{ BASE_URL }}',
|
||||
auth_token: '',
|
||||
amp: true,
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
{% if customAdminCssUrl %}
|
||||
<link nonce="{{ nonce }}" href="{{ customAdminCssUrl }}" rel="stylesheet">
|
||||
{% endif %}
|
||||
Reference in New Issue
Block a user