mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 05:37:34 +03:00
Migrated Admin code into monorepo package
refs https://github.com/TryGhost/Toolbox/issues/365 - this migrates all the Admin code and history into the monorepo 🚀
This commit is contained in:
commit
b29cd27792
240
ghost/admin/.csscomb.json
Normal file
240
ghost/admin/.csscomb.json
Normal file
@ -0,0 +1,240 @@
|
||||
{
|
||||
"remove-empty-rulesets": true,
|
||||
"always-semicolon": true,
|
||||
"color-case": "lower",
|
||||
"block-indent": " ",
|
||||
"color-shorthand": true,
|
||||
"element-case": "lower",
|
||||
"eof-newline": true,
|
||||
"leading-zero": true,
|
||||
"quotes": "double",
|
||||
"space-before-colon": "",
|
||||
"space-after-colon": " ",
|
||||
"space-before-combinator": " ",
|
||||
"space-after-combinator": " ",
|
||||
"space-between-declarations": "\n",
|
||||
"space-before-opening-brace": " ",
|
||||
"space-after-opening-brace": "\n",
|
||||
"space-after-selector-delimiter": "\n",
|
||||
"space-before-selector-delimiter": "",
|
||||
"space-before-closing-brace": "\n",
|
||||
"strip-spaces": true,
|
||||
"tab-size": 4,
|
||||
"unitless-zero": true,
|
||||
"sort-order": [ [
|
||||
"content",
|
||||
"visibility",
|
||||
"position",
|
||||
"top",
|
||||
"right",
|
||||
"bottom",
|
||||
"left",
|
||||
"z-index",
|
||||
"order",
|
||||
"flex",
|
||||
"flex-grow",
|
||||
"flex-shrink",
|
||||
"flex-basis",
|
||||
"align-self",
|
||||
"display",
|
||||
"flex-flow",
|
||||
"flex-direction",
|
||||
"justify-content",
|
||||
"align-items",
|
||||
"align-content",
|
||||
"flex-wrap",
|
||||
"flex-order",
|
||||
"flex-pack",
|
||||
"flex-align",
|
||||
"float",
|
||||
"clear",
|
||||
"box-sizing",
|
||||
"width",
|
||||
"height",
|
||||
"min-width",
|
||||
"min-height",
|
||||
"max-width",
|
||||
"max-height",
|
||||
"overflow",
|
||||
"overflow-x",
|
||||
"overflow-y",
|
||||
"clip",
|
||||
"margin",
|
||||
"margin-top",
|
||||
"margin-right",
|
||||
"margin-bottom",
|
||||
"margin-left",
|
||||
"padding",
|
||||
"padding-top",
|
||||
"padding-right",
|
||||
"padding-bottom",
|
||||
"padding-left",
|
||||
"outline",
|
||||
"outline-width",
|
||||
"outline-style",
|
||||
"outline-color",
|
||||
"outline-offset",
|
||||
"border",
|
||||
"border-spacing",
|
||||
"border-collapse",
|
||||
"border-width",
|
||||
"border-style",
|
||||
"border-color",
|
||||
"border-top",
|
||||
"border-top-width",
|
||||
"border-top-style",
|
||||
"border-top-color",
|
||||
"border-right",
|
||||
"border-right-width",
|
||||
"border-right-style",
|
||||
"border-right-color",
|
||||
"border-bottom",
|
||||
"border-bottom-width",
|
||||
"border-bottom-style",
|
||||
"border-bottom-color",
|
||||
"border-left",
|
||||
"border-left-width",
|
||||
"border-left-style",
|
||||
"border-left-color",
|
||||
"border-image",
|
||||
"border-image-source",
|
||||
"border-image-slice",
|
||||
"border-image-width",
|
||||
"border-image-outset",
|
||||
"border-image-repeat",
|
||||
"border-top-image",
|
||||
"border-right-image",
|
||||
"border-bottom-image",
|
||||
"border-left-image",
|
||||
"border-corner-image",
|
||||
"border-top-left-image",
|
||||
"border-top-right-image",
|
||||
"border-bottom-right-image",
|
||||
"border-bottom-left-image",
|
||||
"table-layout",
|
||||
"caption-side",
|
||||
"empty-cells",
|
||||
"list-style",
|
||||
"list-style-position",
|
||||
"list-style-type",
|
||||
"list-style-image",
|
||||
"quotes",
|
||||
"counter-increment",
|
||||
"counter-reset",
|
||||
"vertical-align",
|
||||
"stroke",
|
||||
"fill",
|
||||
"stroke-width",
|
||||
"stroke-opacity",
|
||||
"color",
|
||||
"font",
|
||||
"font-family",
|
||||
"font-size",
|
||||
"line-height",
|
||||
"font-weight",
|
||||
"font-style",
|
||||
"font-variant",
|
||||
"font-size-adjust",
|
||||
"font-stretch",
|
||||
"text-rendering",
|
||||
"font-feature-settings",
|
||||
"letter-spacing",
|
||||
"hyphens",
|
||||
"text-align",
|
||||
"text-align-last",
|
||||
"text-decoration",
|
||||
"text-emphasis",
|
||||
"text-emphasis-position",
|
||||
"text-emphasis-style",
|
||||
"text-emphasis-color",
|
||||
"text-indent",
|
||||
"text-justify",
|
||||
"text-outline",
|
||||
"text-transform",
|
||||
"text-wrap",
|
||||
"text-overflow",
|
||||
"text-overflow-ellipsis",
|
||||
"text-overflow-mode",
|
||||
"text-shadow",
|
||||
"white-space",
|
||||
"word-spacing",
|
||||
"word-wrap",
|
||||
"word-break",
|
||||
"tab-size",
|
||||
"user-select",
|
||||
"src",
|
||||
"resize",
|
||||
"cursor",
|
||||
"nav-index",
|
||||
"nav-up",
|
||||
"nav-right",
|
||||
"nav-down",
|
||||
"nav-left",
|
||||
"background",
|
||||
"filter:progid:DXImageTransform.Microsoft.AlphaImageLoader",
|
||||
"background-color",
|
||||
"background-image",
|
||||
"background-size",
|
||||
"background-attachment",
|
||||
"background-position",
|
||||
"background-position-x",
|
||||
"background-position-y",
|
||||
"background-clip",
|
||||
"background-origin",
|
||||
"background-repeat",
|
||||
"border-radius",
|
||||
"border-top-left-radius",
|
||||
"border-top-right-radius",
|
||||
"border-bottom-right-radius",
|
||||
"border-bottom-left-radius",
|
||||
"box-decoration-break",
|
||||
"box-shadow",
|
||||
"opacity",
|
||||
"filter:progid:DXImageTransform.Microsoft.Alpha(Opacity",
|
||||
"filter",
|
||||
"transition",
|
||||
"transition-delay",
|
||||
"transition-timing-function",
|
||||
"transition-duration",
|
||||
"transition-property",
|
||||
"transform",
|
||||
"transform-origin",
|
||||
"animation",
|
||||
"animation-name",
|
||||
"animation-duration",
|
||||
"animation-play-state",
|
||||
"animation-timing-function",
|
||||
"animation-delay",
|
||||
"animation-iteration-count",
|
||||
"animation-direction",
|
||||
"animation-fill-mode",
|
||||
"pointer-events",
|
||||
"unicode-bidi",
|
||||
"direction",
|
||||
"columns",
|
||||
"column-span",
|
||||
"column-width",
|
||||
"column-count",
|
||||
"column-fill",
|
||||
"column-gap",
|
||||
"column-rule",
|
||||
"column-rule-width",
|
||||
"column-rule-style",
|
||||
"column-rule-color",
|
||||
"break-before",
|
||||
"break-inside",
|
||||
"break-after",
|
||||
"page-break-before",
|
||||
"page-break-inside",
|
||||
"page-break-after",
|
||||
"orphans",
|
||||
"widows",
|
||||
"zoom",
|
||||
"max-zoom",
|
||||
"min-zoom",
|
||||
"user-zoom",
|
||||
"orientation",
|
||||
"-webkit-overflow-scrolling",
|
||||
"-ms-overflow-scrolling"
|
||||
] ]
|
||||
}
|
26
ghost/admin/.editorconfig
Normal file
26
ghost/admin/.editorconfig
Normal file
@ -0,0 +1,26 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.hbs]
|
||||
insert_final_newline = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_size = 2
|
||||
|
||||
[*.{diff,md}]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
11
ghost/admin/.ember-cli
Normal file
11
ghost/admin/.ember-cli
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"component-structure": "flat",
|
||||
"component-class": "@glimmer/component",
|
||||
/**
|
||||
Ember CLI sends analytics information by default. The data is completely
|
||||
anonymous, but there are times when you might want to disable this behavior.
|
||||
|
||||
Setting `disableAnalytics` to true will prevent any data from being sent.
|
||||
*/
|
||||
"disableAnalytics": true
|
||||
}
|
20
ghost/admin/.eslintignore
Normal file
20
ghost/admin/.eslintignore
Normal file
@ -0,0 +1,20 @@
|
||||
# unconventional js
|
||||
/blueprints/*/files/
|
||||
/vendor/
|
||||
|
||||
# compiled output
|
||||
/dist/
|
||||
/tmp/
|
||||
|
||||
# dependencies
|
||||
/bower_components/
|
||||
/node_modules/
|
||||
|
||||
# misc
|
||||
/coverage/
|
||||
.eslintcache
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
54
ghost/admin/.eslintrc.js
Normal file
54
ghost/admin/.eslintrc.js
Normal file
@ -0,0 +1,54 @@
|
||||
/* eslint-env node */
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: '@babel/eslint-parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
allowImportExportEverywhere: false,
|
||||
ecmaFeatures: {
|
||||
globalReturn: false,
|
||||
legacyDecorators: true,
|
||||
jsx: true
|
||||
},
|
||||
requireConfigFile: false,
|
||||
babelOptions: {
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
['@babel/plugin-proposal-decorators', {legacy: true}],
|
||||
'babel-plugin-transform-react-jsx'
|
||||
]
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
'ghost',
|
||||
'react'
|
||||
],
|
||||
extends: [
|
||||
'plugin:ghost/ember'
|
||||
],
|
||||
rules: {
|
||||
'no-shadow': ['error'],
|
||||
|
||||
// TODO: migrate away from accessing controller in routes
|
||||
'ghost/ember/no-controller-access-in-routes': 'off',
|
||||
|
||||
// TODO: enable once we're fully on octane 🏎
|
||||
'ghost/ember/no-assignment-of-untracked-properties-used-in-tracking-contexts': 'off',
|
||||
'ghost/ember/no-actions-hash': 'off',
|
||||
'ghost/ember/no-classic-classes': 'off',
|
||||
'ghost/ember/no-classic-components': 'off',
|
||||
'ghost/ember/require-tagless-components': 'off',
|
||||
'ghost/ember/no-component-lifecycle-hooks': 'off',
|
||||
|
||||
// disable linting of `this.get` until there's a reliable autofix
|
||||
'ghost/ember/use-ember-get-and-set': 'off',
|
||||
|
||||
// disable linting of mixins until we migrate away
|
||||
'ghost/ember/no-mixins': 'off',
|
||||
'ghost/ember/no-new-mixins': 'off',
|
||||
|
||||
'react/jsx-uses-react': 'error',
|
||||
'react/jsx-uses-vars': 'error'
|
||||
}
|
||||
};
|
72
ghost/admin/.github/CONTRIBUTING.md
vendored
Normal file
72
ghost/admin/.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
# Contributing to Ghost
|
||||
|
||||
For **help**, **support**, **questions** and **ideas** please use **[our forum](https://forum.ghost.org)** 🚑.
|
||||
|
||||
---
|
||||
|
||||
## Where to Start
|
||||
|
||||
If you're a developer looking to contribute, but you're not sure where to begin: Check out the [good first issue](https://github.com/TryGhost/Ghost/labels/good%20first%20issue) label on Github, which contains small piece of work that have been specifically flagged as being friendly to new contributors.
|
||||
|
||||
After that, if you're looking for something a little more challenging to sink your teeth into, there's a broader [help wanted](https://github.com/TryGhost/Ghost/labels/help%20wanted) label encompassing issues which need some love.
|
||||
|
||||
If you've got an idea for a new feature, please start by suggesting it in the [forum](https://forum.ghost.org), as adding new features to Ghost first requires generating consensus around a design and spec.
|
||||
|
||||
|
||||
## Working on Ghost Core
|
||||
|
||||
If you're going to work on Ghost core you'll need to go through a slightly more involved install and setup process than the usual Ghost CLI version.
|
||||
|
||||
First you'll need to fork both [Ghost](https://github.com/tryghost/ghost) and [Ghost-Admin](https://github.com/tryghost/ghost-admin) to your personal Github account, and then follow the detailed [install from source](https://ghost.org/docs/install/source/) setup guide.
|
||||
|
||||
|
||||
### Branching Guide
|
||||
|
||||
`master` on the main repository always contains the latest changes. This means that it is WIP for the next minor version and should NOT be considered stable. Stable versions are tagged using [semantic versioning](http://semver.org/).
|
||||
|
||||
On your local repository, you should always work on a branch to make keeping up-to-date and submitting pull requests easier, but in most cases you should submit your pull requests to `master`. Where necessary, for example if multiple people are contributing on a large feature, or if a feature requires a database change, we make use of feature branches.
|
||||
|
||||
|
||||
### Commit Messages
|
||||
|
||||
We have a handful of simple standards for commit messages which help us to generate readable changelogs. Please follow this wherever possible and mention the associated issue number.
|
||||
|
||||
- **1st line:** Max 80 character summary written in past tense
|
||||
- **2nd line:** [Always blank]
|
||||
- **3rd line:** `refs/closes #000` or `no issue`
|
||||
- **4th line:** Whatever you want. Any extra details can be included from here
|
||||
|
||||
If your change is **user-facing** please prepend the first line of your commit with **an emoji key**.
|
||||
We are following [gitmoji](https://gitmoji.carloscuesta.me/).
|
||||
|
||||
**Main emojis we are using:**
|
||||
|
||||
- ✨ Feature
|
||||
- 🎨 Improvement / change
|
||||
- 🐛 Bug Fix
|
||||
- 💡 Anything else flagged to users or whoever is writing release notes
|
||||
|
||||
Good commit message examples: [one](https://github.com/TryGhost/Ghost/commit/61db6defde3b10a4022c86efac29cf15ae60983f), [two](https://github.com/TryGhost/Ghost/commit/b392d1925a9f961d7b4bf781ee86393a7773ed4b) and [three](https://github.com/TryGhost/Ghost/commit/e4807a779c28a754e3f8ae871a26a8aad12ca9a9).
|
||||
|
||||
|
||||
|
||||
### Submitting Pull Requests
|
||||
|
||||
We aim to merge any straightforward, well-understood bug fixes or improvements immediately, as long as they pass our tests (run `yarn test` to check locally). We generally don’t merge new features and larger changes without prior discussion with the core product team for tech/design specification.
|
||||
|
||||
Please provide plenty of context and reasoning around your changes, to help us merge quickly. Closing an already open issue is our preferred workflow. If your PR gets out of date, we may ask you to rebase as you are more familiar with your changes than we will be.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
By contributing your code to Ghost you grant the Ghost Foundation a non-exclusive, irrevocable, worldwide, royalty-free, sublicenseable, transferable license under all of Your relevant intellectual property rights (including copyright, patent, and any other rights), to use, copy, prepare derivative works of, distribute and publicly perform and display the Contributions on any licensing terms, including without limitation:
|
||||
(a) open source licenses like the MIT license; and (b) binary, proprietary, or commercial licenses. Except for the licenses granted herein, You reserve all right, title, and interest in and to the Contribution.
|
||||
|
||||
You confirm that you are able to grant us these rights. You represent that You are legally entitled to grant the above license. If Your employer has rights to intellectual property that You create, You represent that You have received permission to make the Contributions on behalf of that employer, or that Your employer has waived such rights for the Contributions.
|
||||
|
||||
You represent that the Contributions are Your original works of authorship, and to Your knowledge, no other person claims, or has the right to claim, any right in any invention or patent related to the Contributions. You also represent that You are not legally obligated, whether by entering into an agreement or otherwise, in any way that conflicts with the terms of this license.
|
||||
|
||||
The Ghost Foundation acknowledges that, except as explicitly described in this Agreement, any Contribution which you provide is on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
|
9
ghost/admin/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
9
ghost/admin/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
Got some code for us? Awesome 🎊!
|
||||
|
||||
Please include a description of your change & check your PR against this list, thanks!
|
||||
|
||||
- [ ] There's a clear use-case for this code change
|
||||
- [ ] Commit message has a short title & references relevant issues
|
||||
- [ ] The build will pass (run `ember test` from the repo root - will be `core/admin` if working from the submodule in Ghost).
|
||||
|
||||
More info can be found by clicking the "guidelines for contributing" link above.
|
96
ghost/admin/.github/workflows/test.yml
vendored
Normal file
96
ghost/admin/.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
name: Test Suite
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- "v4.*"
|
||||
- "5.0"
|
||||
- "multiple-newsletters"
|
||||
- main
|
||||
- "renovate/*"
|
||||
jobs:
|
||||
prod-build:
|
||||
runs-on: ubuntu-18.04
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))
|
||||
name: Production Build
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "14.17.0"
|
||||
|
||||
- run: yarn
|
||||
- run: grunt shell:ember:prod
|
||||
|
||||
- uses: daniellockyer/action-slack-build@master
|
||||
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
lint:
|
||||
runs-on: ubuntu-18.04
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))
|
||||
name: Lint
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "14.17.0"
|
||||
|
||||
- run: yarn
|
||||
- run: yarn lint:js
|
||||
- run: yarn lint:hbs
|
||||
|
||||
- uses: daniellockyer/action-slack-build@master
|
||||
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
test:
|
||||
runs-on: ubuntu-18.04
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))
|
||||
strategy:
|
||||
matrix:
|
||||
browser: ["Firefox", "Chrome"]
|
||||
name: ${{ matrix.browser }}
|
||||
env:
|
||||
MOZ_HEADLESS: 1
|
||||
JOBS: 1
|
||||
CI: true
|
||||
COVERAGE: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "14.17.0"
|
||||
|
||||
- run: yarn
|
||||
- run: yarn test
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
|
||||
# Merge coverage reports and upload
|
||||
- run: yarn ember coverage-merge
|
||||
- uses: codecov/codecov-action@v3
|
||||
|
||||
- uses: daniellockyer/action-slack-build@master
|
||||
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
canary:
|
||||
runs-on: ubuntu-18.04
|
||||
needs: [lint, test, prod-build]
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
name: Canary
|
||||
steps:
|
||||
- name: Invoke Canary Build
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
workflow: Canary Build
|
||||
repo: TryGhost/Ghost
|
||||
token: ${{ secrets.WORKFLOW_TOKEN }}
|
63
ghost/admin/.gitignore
vendored
Normal file
63
ghost/admin/.gitignore
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
b-cov
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
|
||||
pids
|
||||
logs
|
||||
results
|
||||
|
||||
npm-debug.log*
|
||||
yarn-error.log
|
||||
.nvmrc
|
||||
.bowerrc
|
||||
.idea/*
|
||||
*.iml
|
||||
*.sublime-*
|
||||
projectFilesBackup
|
||||
|
||||
.DS_Store
|
||||
|
||||
# vim-related
|
||||
[._]*.s[a-w][a-z]
|
||||
[._]s[a-w][a-z]
|
||||
*.un~
|
||||
Session.vim
|
||||
.netrwhist
|
||||
.vimrc
|
||||
*~
|
||||
|
||||
# TernJS
|
||||
.tern-project
|
||||
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/dist-test
|
||||
/tmp
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/bower_components
|
||||
|
||||
# IDE
|
||||
.idea/*
|
||||
*.iml
|
||||
*.sublime-*
|
||||
.vscode/*
|
||||
|
||||
# misc
|
||||
/.env*
|
||||
/.pnp*
|
||||
/.eslintcache
|
||||
/connect.lock
|
||||
/coverage/
|
||||
/libpeerconnection.log
|
||||
/testem.log
|
||||
/concat-stats-for
|
||||
jsconfig.json
|
947
ghost/admin/.lint-todo
Normal file
947
ghost/admin/.lint-todo
Normal file
@ -0,0 +1,947 @@
|
||||
add|ember-template-lint|no-action|21|19|21|19|1cf358e2bf9d6fbb6abdc967c327ac9236e817dc|1658102400000|1668474000000|1673658000000|app/components/gh-benefit-item.hbs
|
||||
add|ember-template-lint|no-action|22|22|22|22|b05a3bfe0536cd1cacb3a5803246be6f16857425|1658102400000|1668474000000|1673658000000|app/components/gh-benefit-item.hbs
|
||||
add|ember-template-lint|no-action|24|23|24|23|cb8946eb596d861488d6f957a8cb9d8be47d6dbd|1658102400000|1668474000000|1673658000000|app/components/gh-benefit-item.hbs
|
||||
add|ember-template-lint|no-action|35|49|35|49|3b24461a9c5dc4b6f39e0fcce071fcfde7944812|1658102400000|1668474000000|1673658000000|app/components/gh-benefit-item.hbs
|
||||
add|ember-template-lint|no-action|39|52|39|52|2c005989f1aa2542a49c3a89330e445d0016efef|1658102400000|1668474000000|1673658000000|app/components/gh-benefit-item.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|21|12|21|12|4069dec45ac2a31d440780c914b2307ad1c6ea5f|1658102400000|1668474000000|1673658000000|app/components/gh-benefit-item.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|22|12|22|12|e53f64794fdd0fe8c8b027d1831942d7c78c503b|1658102400000|1668474000000|1673658000000|app/components/gh-benefit-item.hbs
|
||||
add|ember-template-lint|require-iframe-title|1|0|1|0|d1c9631d150af53ca33b16c8c280c9d815bf43da|1658102400000|1668474000000|1673658000000|app/components/gh-billing-iframe.hbs
|
||||
add|ember-template-lint|no-action|2|54|2|54|8618d17e29821f45d8809ad2d6cf6053b825f7fe|1658102400000|1668474000000|1673658000000|app/components/gh-billing-update-button.hbs
|
||||
add|ember-template-lint|no-triple-curlies|1|0|1|0|26e1d2a3fcc165e3eb94cec903dc0fbff0e26e0e|1658102400000|1668474000000|1673658000000|app/components/gh-blog-url.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|72|72|72|72|431bdbf005b9372c2c174698d2f33914cb5797ab|1658102400000|1668474000000|1673658000000|app/components/gh-brand-settings-form.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|107|76|107|76|7079e498c9add301f177d9056312b9c5b0848a30|1658102400000|1668474000000|1673658000000|app/components/gh-brand-settings-form.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|143|83|143|83|25547784a8d412e4463149020bbf91db815057fe|1658102400000|1668474000000|1673658000000|app/components/gh-brand-settings-form.hbs
|
||||
add|ember-template-lint|require-input-label|22|20|22|20|a9cd381b8ac601bc9cbf7a4eace7bcefa67ecae6|1658102400000|1668474000000|1673658000000|app/components/gh-brand-settings-form.hbs
|
||||
add|ember-template-lint|require-input-label|40|24|40|24|8aec13864a8e34e86eea56eced36e8271d3bc4ea|1658102400000|1668474000000|1673658000000|app/components/gh-brand-settings-form.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|107|24|107|24|7079e498c9add301f177d9056312b9c5b0848a30|1658102400000|1668474000000|1673658000000|app/components/gh-brand-settings-form.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|143|24|143|24|25547784a8d412e4463149020bbf91db815057fe|1658102400000|1668474000000|1673658000000|app/components/gh-brand-settings-form.hbs
|
||||
add|ember-template-lint|no-action|5|11|5|11|8eaebb48eca1563c6e0b18581df84ab59188d971|1658102400000|1668474000000|1673658000000|app/components/gh-cm-editor.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|5|4|5|4|3a763e253744b070633bb8bd424b6c8e55f6b20a|1658102400000|1668474000000|1673658000000|app/components/gh-cm-editor.hbs
|
||||
add|ember-template-lint|no-action|5|18|5|18|7c796afb78a976ab2411eab292c02c4250e429c7|1658102400000|1668474000000|1673658000000|app/components/gh-date-time-picker.hbs
|
||||
add|ember-template-lint|no-down-event-binding|17|25|17|25|e82f6aa36fd44bb3dccff09770613eee19380f9b|1658102400000|1668474000000|1673658000000|app/components/gh-date-time-picker.hbs
|
||||
add|ember-template-lint|require-input-label|11|16|11|16|36ecd3af7ac0e4bbafbf933cc20ff9f3ff8f0947|1658102400000|1668474000000|1673658000000|app/components/gh-date-time-picker.hbs
|
||||
add|ember-template-lint|require-input-label|29|8|29|8|334a3ea0fb16fabf58967294e125af8437504bdb|1658102400000|1668474000000|1673658000000|app/components/gh-date-time-picker.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|3|4|3|4|66accf6cbc192fd9273063ef67798572309ff1bb|1658102400000|1668474000000|1673658000000|app/components/gh-editor-feature-image.hbs
|
||||
add|ember-template-lint|require-input-label|48|20|48|20|213bf91395d670b2af5fc667fca46d3d20238465|1658102400000|1668474000000|1673658000000|app/components/gh-editor-feature-image.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|41|16|41|16|079fc89fa5c7c47f6b0b219820cdda3819c44e26|1658102400000|1668474000000|1673658000000|app/components/gh-editor-feature-image.hbs
|
||||
add|ember-template-lint|no-action|9|21|9|21|4287fc3d450426e99d8f6b7f06b7e8bf5853ca27|1658102400000|1668474000000|1673658000000|app/components/gh-editor.hbs
|
||||
add|ember-template-lint|no-action|10|18|10|18|268f66f239be50906625635120971fc67a7fefd7|1658102400000|1668474000000|1673658000000|app/components/gh-editor.hbs
|
||||
add|ember-template-lint|no-action|11|22|11|22|e3540dcab854d420bb1a1f9dd3435421f97ceac2|1658102400000|1668474000000|1673658000000|app/components/gh-editor.hbs
|
||||
add|ember-template-lint|no-action|12|19|12|19|d12bcf1144bfb2fe70e7ab0f66836f1c6207a589|1658102400000|1668474000000|1673658000000|app/components/gh-editor.hbs
|
||||
add|ember-template-lint|no-action|13|20|13|20|16d94650de2ffbe8ee3f2ce3ba5ca97a6304b739|1658102400000|1668474000000|1673658000000|app/components/gh-editor.hbs
|
||||
add|ember-template-lint|no-action|14|17|14|17|30d64b1bf8990e2f84c52665690739fcc726e9f7|1658102400000|1668474000000|1673658000000|app/components/gh-editor.hbs
|
||||
add|ember-template-lint|no-action|1|150|1|150|9eb2aa69ff4ec506e54d133fa8c91e6b9953d04c|1658102400000|1668474000000|1673658000000|app/components/gh-feature-flag.hbs
|
||||
add|ember-template-lint|no-triple-curlies|3|0|3|0|7ceb921960f42e312847d43c601bc98f2eced9f4|1658102400000|1668474000000|1673658000000|app/components/gh-feature-flag.hbs
|
||||
add|ember-template-lint|no-action|2|111|2|111|7e581bf2ffd5254ae851201e1f23cb9eaa9b198b|1658102400000|1668474000000|1673658000000|app/components/gh-file-upload.hbs
|
||||
add|ember-template-lint|require-input-label|1|0|1|0|80e39573b35eef3b8f874fbc35bdd2bfd65695b1|1658102400000|1668474000000|1673658000000|app/components/gh-file-upload.hbs
|
||||
add|ember-template-lint|no-action|6|77|6|77|76726a13a086d82dab219df12e86db1773a9de32|1658102400000|1668474000000|1673658000000|app/components/gh-file-uploader.hbs
|
||||
add|ember-template-lint|no-action|18|73|18|73|bbc5d9a459cd07e56d8c79dde619775b89d7cc89|1658102400000|1668474000000|1673658000000|app/components/gh-file-uploader.hbs
|
||||
add|ember-template-lint|no-action|2|45|2|45|a9d29fae15800842d0e7b8f32b035ee22a23f4cb|1658102400000|1668474000000|1673658000000|app/components/gh-fullscreen-modal.hbs
|
||||
add|ember-template-lint|no-action|10|29|10|29|ebbd89a393bcec7f537f2104ace2a6b1941a19a7|1658102400000|1668474000000|1673658000000|app/components/gh-fullscreen-modal.hbs
|
||||
add|ember-template-lint|no-action|11|32|11|32|ab89b6f10c519be1271386203e9439d261eecd67|1658102400000|1668474000000|1673658000000|app/components/gh-fullscreen-modal.hbs
|
||||
add|ember-template-lint|no-action|13|36|13|36|3776877637b49b65deef537c68ae490b2fb081a9|1658102400000|1668474000000|1673658000000|app/components/gh-fullscreen-modal.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|2|45|2|45|b6693259da29d433264b59abc9a7c7ac846a4bf6|1658102400000|1668474000000|1673658000000|app/components/gh-fullscreen-modal.hbs
|
||||
add|ember-template-lint|require-iframe-title|6|4|6|4|62bc251294f2cbea568fcc8d8e46bb5fe1915bc9|1658102400000|1668474000000|1673658000000|app/components/gh-html-iframe.hbs
|
||||
add|ember-template-lint|require-iframe-title|7|4|7|4|62bc251294f2cbea568fcc8d8e46bb5fe1915bc9|1658102400000|1668474000000|1673658000000|app/components/gh-html-iframe.hbs
|
||||
add|ember-template-lint|link-href-attributes|4|8|4|8|a9119f612d27ee1fc92e9bb2c77b6fd30cab622a|1658102400000|1668474000000|1673658000000|app/components/gh-image-uploader-with-preview.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|4|47|4|47|a9119f612d27ee1fc92e9bb2c77b6fd30cab622a|1658102400000|1668474000000|1673658000000|app/components/gh-image-uploader-with-preview.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|3|13|3|13|079fc89fa5c7c47f6b0b219820cdda3819c44e26|1658102400000|1668474000000|1673658000000|app/components/gh-image-uploader-with-preview.hbs
|
||||
add|ember-template-lint|no-action|12|58|12|58|031d04576149d3034549aa3d14bc26da704cd70c|1658102400000|1668474000000|1673658000000|app/components/gh-image-uploader.hbs
|
||||
add|ember-template-lint|no-action|17|75|17|75|bbc5d9a459cd07e56d8c79dde619775b89d7cc89|1658102400000|1668474000000|1673658000000|app/components/gh-image-uploader.hbs
|
||||
add|ember-template-lint|no-action|22|52|22|52|b1bd53a513ad82434d5a0a9a96441a45d416d7ad|1658102400000|1668474000000|1673658000000|app/components/gh-image-uploader.hbs
|
||||
add|ember-template-lint|no-action|31|16|31|16|4c64ddeaf795ee831d0d7346667a305814ca9855|1658102400000|1668474000000|1673658000000|app/components/gh-image-uploader.hbs
|
||||
add|ember-template-lint|no-action|32|15|32|15|b1bd53a513ad82434d5a0a9a96441a45d416d7ad|1658102400000|1668474000000|1673658000000|app/components/gh-image-uploader.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|22|52|22|52|74dea739bef284d6557987ff3da53fa1278030e2|1658102400000|1668474000000|1673658000000|app/components/gh-image-uploader.hbs
|
||||
add|ember-template-lint|no-down-event-binding|5|13|5|13|a158be60fde14211f6abfbac329c94fa5a3b4a46|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|5|8|5|8|94046126dd697b080aa16222b00a3d5a545ee001|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|6|8|6|8|94046126dd697b080aa16222b00a3d5a545ee001|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|26|12|26|12|4ad1176b9a4bb23d79114735b478240166113502|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|28|12|28|12|7ffe0a55c81efcfcd46880a930fa69bee73835b3|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|6|17|6|17|4958fce0dda72cfdd5d49359244c65d37e804763|1658102400000|1668474000000|1673658000000|app/components/gh-markdown-editor.hbs
|
||||
add|ember-template-lint|no-action|7|16|7|16|91d44afd854e83acf8ffa4ed94b9c8cbaab5c637|1658102400000|1668474000000|1673658000000|app/components/gh-markdown-editor.hbs
|
||||
add|ember-template-lint|no-action|8|15|8|15|432c540e6c2d193c6de2590afcdaeca15c8bedbd|1658102400000|1668474000000|1673658000000|app/components/gh-markdown-editor.hbs
|
||||
add|ember-template-lint|no-action|9|21|9|21|5cae535312c5bb6bd16943db8003819b3cdfc4f6|1658102400000|1668474000000|1673658000000|app/components/gh-markdown-editor.hbs
|
||||
add|ember-template-lint|no-action|13|10|13|10|cd0da3fe1ab45eeef8705bff73b49199eabeb12c|1658102400000|1668474000000|1673658000000|app/components/gh-markdown-editor.hbs
|
||||
add|ember-template-lint|no-action|19|16|19|16|770eeb17fc003a2f7a88e53c530684441f1325b3|1658102400000|1668474000000|1673658000000|app/components/gh-markdown-editor.hbs
|
||||
add|ember-template-lint|no-action|25|16|25|16|c6bbe24d9b26dcec3a60bf7a1f4a8c7879aae806|1658102400000|1668474000000|1673658000000|app/components/gh-markdown-editor.hbs
|
||||
add|ember-template-lint|no-action|26|15|26|15|342b42844723d115bff978b6ff1327958929a087|1658102400000|1668474000000|1673658000000|app/components/gh-markdown-editor.hbs
|
||||
add|ember-template-lint|no-action|31|15|31|15|90b03a30c6e96b204b86ce170890fc6849e6f82d|1658102400000|1668474000000|1673658000000|app/components/gh-markdown-editor.hbs
|
||||
add|ember-template-lint|simple-unless|19|34|19|34|ca177565b6e1c5a6175982047708732f5dde7e59|1658102400000|1668474000000|1673658000000|app/components/gh-member-details-activity.hbs
|
||||
add|ember-template-lint|simple-unless|18|30|18|30|ca177565b6e1c5a6175982047708732f5dde7e59|1658102400000|1668474000000|1673658000000|app/components/gh-member-details.hbs
|
||||
add|ember-template-lint|no-action|10|16|10|16|8d8dd8c2cb5f9910c2de5ca6acc76ee4262a876e|1658102400000|1668474000000|1673658000000|app/components/gh-members-import-mapping-input.hbs
|
||||
add|ember-template-lint|no-action|9|36|9|36|05358b6ec6e9afbaa47416266c49f40a3ffb4490|1658102400000|1668474000000|1673658000000|app/components/gh-members-import-table.hbs
|
||||
add|ember-template-lint|no-action|10|36|10|36|c5ce93cf577ec47970f715ed83ed11a63adb7a63|1658102400000|1668474000000|1673658000000|app/components/gh-members-import-table.hbs
|
||||
add|ember-template-lint|no-action|11|35|11|35|6f79b46dcaccf8061b341b369ec20f6e0f0805bd|1658102400000|1668474000000|1673658000000|app/components/gh-members-payments-setting.hbs
|
||||
add|ember-template-lint|no-action|20|35|20|35|c84840bfbe762e604dc56cf4df06ab19d38a4777|1658102400000|1668474000000|1673658000000|app/components/gh-members-payments-setting.hbs
|
||||
add|ember-template-lint|no-action|71|78|71|78|09238ca0047aba9705befa8bb94c6d577d7b78cf|1658102400000|1668474000000|1673658000000|app/components/gh-members-payments-setting.hbs
|
||||
add|ember-template-lint|no-action|82|85|82|85|8d227d609618ed70d583fd1c1ecec3638f03138d|1658102400000|1668474000000|1673658000000|app/components/gh-members-payments-setting.hbs
|
||||
add|ember-template-lint|no-action|83|123|83|123|d5b4c9ae3e5c4e36db552abdbc83280f9e6f8d0b|1658102400000|1668474000000|1673658000000|app/components/gh-members-payments-setting.hbs
|
||||
add|ember-template-lint|no-action|93|35|93|35|1808b6250616df5c80cd814986955242d49e70c0|1658102400000|1668474000000|1673658000000|app/components/gh-members-payments-setting.hbs
|
||||
add|ember-template-lint|no-action|131|17|131|17|79c12d2474c1fd8c0ff5ab8cf1fdb3952ab1825b|1658102400000|1668474000000|1673658000000|app/components/gh-members-payments-setting.hbs
|
||||
add|ember-template-lint|no-action|132|15|132|15|c7db9d737e3f06cc754d7a49a3e35897117e5777|1658102400000|1668474000000|1673658000000|app/components/gh-members-payments-setting.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|11|28|11|28|02c81dfb804e41f5b3c10730e431d1e4b0958e6f|1658102400000|1668474000000|1673658000000|app/components/gh-members-payments-setting.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|20|28|20|28|1591adfb7d0dbab4321126ada2e2c5a4a8c66516|1658102400000|1668474000000|1673658000000|app/components/gh-members-payments-setting.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|93|28|93|28|55dadf0e7dc5e2ed57771f46ca3cb82607d1799c|1658102400000|1668474000000|1673658000000|app/components/gh-members-payments-setting.hbs
|
||||
add|ember-template-lint|no-duplicate-attributes|9|4|9|4|6b5f76f812df2b84f2ed9ee5a557ca1bf98710df|1658102400000|1668474000000|1673658000000|app/components/gh-members-segment-select.hbs
|
||||
add|ember-template-lint|no-action|38|107|38|107|79d2eeaed67e929e989261416abd3cd2dbbf4861|1658102400000|1668474000000|1673658000000|app/components/gh-membership-tiers-alpha.hbs
|
||||
add|ember-template-lint|no-action|8|50|8|50|de589665046a78748832e8f93d2e495d1d259265|1658102400000|1668474000000|1673658000000|app/components/gh-mobile-nav-bar.hbs
|
||||
add|ember-template-lint|no-action|20|19|20|19|1cf358e2bf9d6fbb6abdc967c327ac9236e817dc|1658102400000|1668474000000|1673658000000|app/components/gh-navitem.hbs
|
||||
add|ember-template-lint|no-action|21|22|21|22|b05a3bfe0536cd1cacb3a5803246be6f16857425|1658102400000|1668474000000|1673658000000|app/components/gh-navitem.hbs
|
||||
add|ember-template-lint|no-action|22|23|22|23|29e3a04f98d37252bdd540ec2adcda840bd6f929|1658102400000|1668474000000|1673658000000|app/components/gh-navitem.hbs
|
||||
add|ember-template-lint|no-action|38|20|38|20|3c723798059c71aedfa95cd70507bc0308728544|1658102400000|1668474000000|1673658000000|app/components/gh-navitem.hbs
|
||||
add|ember-template-lint|no-action|39|25|39|25|e27fd2ebfccd2eed3d401b8854fc4d5d2f74cfc2|1658102400000|1668474000000|1673658000000|app/components/gh-navitem.hbs
|
||||
add|ember-template-lint|no-action|47|49|47|49|18f748b67f9fe570b08c7a1c1d99114f31b41598|1658102400000|1668474000000|1673658000000|app/components/gh-navitem.hbs
|
||||
add|ember-template-lint|no-action|51|52|51|52|4d50f3b33a672c365969169a4c0ca14cf83fe39c|1658102400000|1668474000000|1673658000000|app/components/gh-navitem.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|20|12|20|12|4069dec45ac2a31d440780c914b2307ad1c6ea5f|1658102400000|1668474000000|1673658000000|app/components/gh-navitem.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|21|12|21|12|e53f64794fdd0fe8c8b027d1831942d7c78c503b|1658102400000|1668474000000|1673658000000|app/components/gh-navitem.hbs
|
||||
add|ember-template-lint|no-action|39|50|39|50|317b0cce151252b8cb54fb112fef62cf3c285ad7|1658102400000|1668474000000|1673658000000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|69|50|69|50|0b06c9d99b2e88f3e5c134fa9452f3a3ee107649|1658102400000|1668474000000|1673658000000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|96|50|96|50|542d2c922b1ddd938aabf03ce17fe33053749ec7|1658102400000|1668474000000|1673658000000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|125|48|125|48|01f579966c325cf66d3a87799e8cea7dd1fded81|1658102400000|1668474000000|1673658000000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|148|54|148|54|884d0df74365d794c99bb098e72dc267bd0fbc46|1658102400000|1668474000000|1673658000000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|175|54|175|54|b51c80e0c3de828988f74017ab7e383552f48648|1658102400000|1668474000000|1673658000000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|202|54|202|54|7f4e772017b88e0a2f39ecf899f0c506639bc675|1658102400000|1668474000000|1673658000000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|230|54|230|54|eca87a98b0e8ad5a623de88a16aadedc9591ac22|1658102400000|1668474000000|1673658000000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|257|54|257|54|e877a0feccc071327b56a0d7b8a8fcb84ee6d840|1658102400000|1668474000000|1673658000000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|284|54|284|54|7f4e772017b88e0a2f39ecf899f0c506639bc675|1658102400000|1668474000000|1673658000000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|315|50|315|50|1d7de8deaee7bfe5bd051e900e98817fedabc529|1658102400000|1668474000000|1673658000000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|342|50|342|50|df19e5858021b80de54052c953e70e3b4606a8f5|1658102400000|1668474000000|1673658000000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|369|50|369|50|3afa41e4d86dd7e5c049a762f0f761c2464a5f96|1658102400000|1668474000000|1673658000000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|35|35|35|35|66cebfc8448eced0bccf3faa57b4af0cd633e65e|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|36|39|36|39|70f3e9aa9a9aa52142c4e0c015a8f173d12b05ed|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|55|33|55|33|43cc094c1a6b50ecd24c8af325dfdfcac863bb14|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|56|33|56|33|e7f429eefd04a55d4ef1875fe601da1b69964364|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|86|42|86|42|0e827c1770073f988535ceb9d6c49808a153d896|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|103|31|103|31|5cbc9d2abf29e108bfc207579df1206485e2a74d|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|104|35|104|35|1f2dd4961e9757ede9706bb0aa9b8baf9c49b22b|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|114|97|114|97|90dbb84741c72aa86a601e1cb95c066bd53898bd|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|121|46|121|46|97acfb2045b33a97621258596b631362ad4c56d5|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|128|47|128|47|358336432fcb413c522c5f1ff3062a68ef9f449f|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|135|46|135|46|78a45cbb7eafdda134d96f6035b0026f339e75ad|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|143|50|143|50|91a3281547c8446529685240c77aaccb5c7d69bd|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|151|46|151|46|9f1c3c88187e5db774f97704269e372f8331eba5|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|161|59|161|59|b07bf9d50af5f00861571521b150cf6504b90704|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|166|36|166|36|38755451c044c56040684339301c21b2aab49ecb|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|177|38|177|38|56ca074eb0236b8becc4084423056a8ffa4453e9|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|180|114|180|114|de5b68b49193c72f85c0e478f151a00180321d91|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|192|163|192|163|0145b67f0faef0aad141c6a4269c35c6ef8f0a47|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|198|34|198|34|3d44a2ad21d60d18bed6f79265caa4887361aaa4|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|207|47|207|47|67551960e57b2ad06d24289e0d6f256dc5635cae|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|208|51|208|51|50e3029f4c845049ff10130cf0dc2ea06c7a3d2d|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|223|47|223|47|0d852775b4028d61c2c91b5d821c79de3cdbd1de|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|224|51|224|51|194439ec4cb10e176eecb1f55c2995c00f6c67b4|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|238|47|238|47|3be93048908ab43d74726c3983982fcb9a3570e3|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|239|51|239|51|40871d259cc307345b8096e4215ae58e5886b724|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|265|166|265|166|0145b67f0faef0aad141c6a4269c35c6ef8f0a47|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|272|34|272|34|3d44a2ad21d60d18bed6f79265caa4887361aaa4|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|277|44|277|44|d2abbb4bb55b6cb1131ca0bc56c31632b32b980c|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|278|44|278|44|3ede83bd64d206d665edb4f1b73f03b4122edfad|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|288|47|288|47|13b7064b520a924d9e57a4a680621ce979d23923|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|289|51|289|51|b0fc38b818f4ca2613684588e4122e12da0090b9|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|304|47|304|47|f34f084dc72c079be9ba3eb93c255bd8853612bd|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|305|51|305|51|c5c09d001fa96a623649cc88ef13b3c6c164f05b|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|335|35|335|35|0145b67f0faef0aad141c6a4269c35c6ef8f0a47|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|341|167|341|167|0145b67f0faef0aad141c6a4269c35c6ef8f0a47|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|347|34|347|34|3d44a2ad21d60d18bed6f79265caa4887361aaa4|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|352|44|352|44|32101c0834d9e8d2caa87dee9cb1a92fe633cfde|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|353|44|353|44|c8a82286cfe4220cb2d8a059b1daf7f6c8d54cf5|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|363|47|363|47|31800fb83f1797de5d91b7007c48432cf2fae803|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|364|51|364|51|5ce0788874489341385f031810e9f7f62724aaf8|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|377|47|377|47|1cbd7d922a9d202772a9dca214d97bed18777d6e|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|378|51|378|51|16fb2c7c87dc282c6ee49a032aaaf22628b88084|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|409|168|409|168|0145b67f0faef0aad141c6a4269c35c6ef8f0a47|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|415|34|415|34|3d44a2ad21d60d18bed6f79265caa4887361aaa4|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|422|50|422|50|d03e7bbf2b6de94b08fae1c33bd5a2f16874e03a|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|424|48|424|48|bca167f0250b40d56ff1ed40ec21bc88f8e27ec3|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|435|50|435|50|93ae447fb1054de4dbe501fc60167a6ff3273687|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|437|48|437|48|ecb9ed2577be7ea0300563f22d8e9fde2fed12a6|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|35|28|35|28|fed655605208b290a18b9f7da51a6cf7b40c0e9a|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|103|24|103|24|187e85e14470b453a1e6df8c5f18549d11c441a0|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|207|40|207|40|f290a04fd56f613d80d61244d161631f630185a8|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|223|40|223|40|c1af88109f705acc93fab4ee7b4d89096136ffe1|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|238|40|238|40|852cbb2c63e19a28d700d3b18e6f887edef303fa|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|288|40|288|40|58185c92e8b6261ea1483a70296d9fa837d3f2f5|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|304|40|304|40|c4353acd715c396564c69389edd0a52246d3b966|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|363|40|363|40|29f26e46559dc40d9724a05b7516cb52f1481aaa|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|377|40|377|40|42e0617f832585eaa056c9c66dffe1a126318faf|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|422|40|422|40|d1dccfeee5b103fac3b14d5b4567bc65ee08fd5a|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|435|40|435|40|055c4b70aa8daba6b97077eefa95ebed7f7f5315|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|simple-unless|64|30|64|30|ca177565b6e1c5a6175982047708732f5dde7e59|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|simple-unless|188|62|188|62|5d9203e10703908836b537481cf012f167ded239|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu.hbs
|
||||
add|ember-template-lint|no-action|4|14|4|14|c8f6146f9286ec1b289e28983ab446386f390f01|1658102400000|1668474000000|1673658000000|app/components/gh-psm-authors-input.hbs
|
||||
add|ember-template-lint|no-action|5|14|5|14|a90edd9a99596008f60bfcdbc6befe7fe8d26321|1658102400000|1668474000000|1673658000000|app/components/gh-psm-tags-input.hbs
|
||||
add|ember-template-lint|no-action|6|14|6|14|3924d7cfb394cbcfd6b1bf9cf13a5040ac8f94b5|1658102400000|1668474000000|1673658000000|app/components/gh-psm-tags-input.hbs
|
||||
add|ember-template-lint|no-action|10|20|10|20|8b7921c0514cfd3fec8c3a474187048bc26faf7f|1658102400000|1668474000000|1673658000000|app/components/gh-psm-tags-input.hbs
|
||||
add|ember-template-lint|no-action|11|28|11|28|2e612be5e2b8e0b792f951c3c75b2f71df880365|1658102400000|1668474000000|1673658000000|app/components/gh-psm-template-select.hbs
|
||||
add|ember-template-lint|no-action|7|16|7|16|86819b20fcc42bc1d4607519c1d45532ee337884|1658102400000|1668474000000|1673658000000|app/components/gh-psm-visibility-input.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|8|80|8|80|bb724c391e9e56cc994322717d453c488ca5ae28|1658102400000|1668474000000|1673658000000|app/components/gh-role-selection.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|36|75|36|75|7eff29b73ce875c9b087ff1340647bab0d21ac19|1658102400000|1668474000000|1673658000000|app/components/gh-role-selection.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|72|75|72|75|d541b87fe28c3dc1c1fce306c988bb09f777b1bc|1658102400000|1668474000000|1673658000000|app/components/gh-role-selection.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|108|82|108|82|f77e549a4fa7232cea39feff73f3e1c5bc98f467|1658102400000|1668474000000|1673658000000|app/components/gh-role-selection.hbs
|
||||
add|ember-template-lint|no-autofocus-attribute|13|12|13|12|fa0ffb960072633b72117849e3927673be0059af|1658102400000|1668474000000|1673658000000|app/components/gh-search-input.hbs
|
||||
add|ember-template-lint|no-yield-only|1|0|1|0|a5fa6e8c1e0f03fb31b5cd17770e4368d44932e4|1658102400000|1668474000000|1673658000000|app/components/gh-simplemde.hbs
|
||||
add|ember-template-lint|require-iframe-title|1|0|1|0|956ab219134ac63aec3fd2d35582076c21631b75|1658102400000|1668474000000|1673658000000|app/components/gh-site-iframe.hbs
|
||||
add|ember-template-lint|no-action|14|39|14|39|913b0cf6525ac677112a899b2b0f15e07fbf204a|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|65|35|65|35|39b616c69eeb0dc420dc1430e0ecd38668f4d3fa|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|79|35|79|35|9b8af5e8622a324a4f80d12a88e2af649e742fe6|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|91|28|91|28|0b3589b667ef29b9e01bb49bd2645db31eceed1a|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|92|28|92|28|be80faed6c7a41a2ac377b4f6d3eece8b858408c|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|106|63|106|63|3cbb83b28eaf63d37b15aa99151bac2a301a4b41|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|122|43|122|43|4a1f665e2bd695f7db25f2a126670a524a3a112b|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|137|43|137|43|763dced49c969df89ec39d7258b70c1e91c74c67|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|149|43|149|43|b0ec7092966648e6ba28055946b1fff735f8c08d|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|180|63|180|63|afe185fdf8e3368a25e965330d4fca4da08b63a7|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|194|40|194|40|d2abbb4bb55b6cb1131ca0bc56c31632b32b980c|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|195|40|195|40|3ede83bd64d206d665edb4f1b73f03b4122edfad|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|206|43|206|43|c742c00638da1ba8d067d21307f96e57df44c2b7|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|221|43|221|43|bd00aa5009b99f82b02b9ea6ddf65ba8fa74d1ad|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|274|63|274|63|4842445c912ca9073389537718b6fe02c3e621e3|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|288|40|288|40|32101c0834d9e8d2caa87dee9cb1a92fe633cfde|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|289|40|289|40|c8a82286cfe4220cb2d8a059b1daf7f6c8d54cf5|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|300|43|300|43|fd3e947561e280e8a04867bfb9143df32dec0aa9|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|315|43|315|43|b8cf39b2313c6eaa9d4e9042eaf133079b6a5743|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|369|63|369|63|fad67fa01283f9b309c508725105158c3243b324|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|381|34|381|34|74ae86c6b029ad523f821e7385eeb58bc2ab4975|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|383|32|383|32|cc59d0154028a8f107aaaea52ea6d9e8710b3f83|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|394|34|394|34|406ce271cdc31541e8199b7729098b4392dc133e|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-action|396|32|396|32|2b3c6732bdd27bdef2d535007c670f1f4286db0e|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|381|24|381|24|d8b5d7c4140d1dc6151eb7486382da38928232b0|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|394|24|394|24|3e1dad0fb19c62adea14c5534d52cd95cfad280a|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|require-input-label|29|28|29|28|af62541280c3dc86d2ca7a4be8c980ee164fba5a|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|require-input-label|46|32|46|32|8e299cf5bf0e93e054410239451997a196933d25|1658102400000|1668474000000|1673658000000|app/components/gh-tag-settings-form.hbs
|
||||
add|ember-template-lint|no-yield-only|1|0|1|0|a5fa6e8c1e0f03fb31b5cd17770e4368d44932e4|1658102400000|1668474000000|1673658000000|app/components/gh-text-input.hbs
|
||||
add|ember-template-lint|no-action|1|51|1|51|674a942db62e014b25156b02d014f6c63b2f1cb2|1658102400000|1668474000000|1673658000000|app/components/gh-theme-error-li.hbs
|
||||
add|ember-template-lint|no-triple-curlies|5|12|5|12|5ccce366ce8389f55dc57b1d684c310e65eb26a6|1658102400000|1668474000000|1673658000000|app/components/gh-theme-error-li.hbs
|
||||
add|ember-template-lint|no-triple-curlies|19|8|19|8|87b1676e19127a8136984abf3f6b3947569afc25|1658102400000|1668474000000|1673658000000|app/components/gh-theme-error-li.hbs
|
||||
add|ember-template-lint|no-unused-block-params|2|48|2|48|93a49e8b39e3073babdfb7d6b9f8d061d712564d|1658102400000|1668474000000|1673658000000|app/components/gh-theme-table.hbs
|
||||
add|ember-template-lint|no-action|12|16|12|16|40e7337ccf8501ef3b2fea94812d4de8435286da|1658102400000|1668474000000|1673658000000|app/components/gh-tiers-price-billingperiod.hbs
|
||||
add|ember-template-lint|no-action|9|16|9|16|0232e593042bb48fcc89bf9ab0b91b242f264fba|1658102400000|1668474000000|1673658000000|app/components/gh-timezone-select.hbs
|
||||
add|ember-template-lint|no-action|2|11|2|11|2b1317e72b94ec31bf2700acc3f041e6be974b72|1658102400000|1668474000000|1673658000000|app/components/gh-uploader.hbs
|
||||
add|ember-template-lint|no-action|7|13|7|13|add4b57d48167f809045245535d45bedb00cb753|1658102400000|1668474000000|1673658000000|app/components/gh-uploader.hbs
|
||||
add|ember-template-lint|no-action|8|22|8|22|ce1eba2b791c0f120baf10871fd3949c1b9f6a79|1658102400000|1668474000000|1673658000000|app/components/gh-uploader.hbs
|
||||
add|ember-template-lint|no-action|9|22|9|22|0209f233628ec084b87894b4be9073c29f2a4f47|1658102400000|1668474000000|1673658000000|app/components/gh-uploader.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|4|4|4|4|3dedc53191768d81765eac4a7a049a9aba7df442|1658102400000|1668474000000|1673658000000|app/components/gh-url-input.hbs
|
||||
add|ember-template-lint|no-action|1|71|1|71|2e6351f546807d88cc8eb9dbe8baa149468b5cb9|1658102400000|1668474000000|1673658000000|app/components/gh-view-title.hbs
|
||||
add|ember-template-lint|no-invalid-role|1|0|1|0|3e651d38e0110e1be20e5082075db1b879b59a36|1658102400000|1668474000000|1673658000000|app/components/gh-view-title.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-all.hbs
|
||||
add|ember-template-lint|no-action|11|41|11|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-all.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-integration.hbs
|
||||
add|ember-template-lint|no-action|11|41|11|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-integration.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-member.hbs
|
||||
add|ember-template-lint|no-action|19|37|19|37|56d2776c5303897b2f18863de273e2534e988a86|1658102400000|1668474000000|1673658000000|app/components/modal-delete-member.hbs
|
||||
add|ember-template-lint|no-action|33|41|33|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-member.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-snippet.hbs
|
||||
add|ember-template-lint|no-action|13|41|13|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-snippet.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-tag.hbs
|
||||
add|ember-template-lint|no-action|14|41|14|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-tag.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-user.hbs
|
||||
add|ember-template-lint|no-action|23|79|23|79|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-user.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-webhook.hbs
|
||||
add|ember-template-lint|no-action|13|41|13|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-delete-webhook.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-disconnect-stripe.hbs
|
||||
add|ember-template-lint|no-action|15|41|15|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-disconnect-stripe.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-early-access.hbs
|
||||
add|ember-template-lint|no-action|11|41|11|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-early-access.hbs
|
||||
add|ember-template-lint|no-action|12|56|12|56|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-early-access.hbs
|
||||
add|ember-template-lint|no-action|6|62|6|62|7234f2e9e2cd7dc3a2532ce2003ab3bf7b79f479|1658102400000|1668474000000|1673658000000|app/components/modal-email-design-settings.hbs
|
||||
add|ember-template-lint|no-down-event-binding|8|21|8|21|a158be60fde14211f6abfbac329c94fa5a3b4a46|1658102400000|1668474000000|1673658000000|app/components/modal-email-design-settings.hbs
|
||||
add|ember-template-lint|no-triple-curlies|235|32|235|32|f62f2356fb4ed0c7b896b8a97921586b5665f812|1658102400000|1668474000000|1673658000000|app/components/modal-email-design-settings.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|46|44|46|44|23f4d5a694823e4caeefe34f28ae1a0f56604d27|1658102400000|1668474000000|1673658000000|app/components/modal-email-design-settings.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|205|32|205|32|97fa01f3f8879263268614e635b5c0f5bb66fa30|1658102400000|1668474000000|1673658000000|app/components/modal-email-design-settings.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|211|36|211|36|902a4532f0b313f8b929d779f0f7c9058549f850|1658102400000|1668474000000|1673658000000|app/components/modal-email-design-settings.hbs
|
||||
add|ember-template-lint|no-action|4|55|4|55|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-free-membership-settings.hbs
|
||||
add|ember-template-lint|no-action|4|79|4|79|d465b362b15b90cf42a093e72895155f49cdf6f2|1658102400000|1668474000000|1673658000000|app/components/modal-free-membership-settings.hbs
|
||||
add|ember-template-lint|no-action|16|27|16|27|d0065f512527491cf1e2532437e84b9cf867c925|1658102400000|1668474000000|1673658000000|app/components/modal-free-membership-settings.hbs
|
||||
add|ember-template-lint|no-action|26|27|26|27|b5a468ed19153bfd25303ca31bc45aeaf65c358f|1658102400000|1668474000000|1673658000000|app/components/modal-free-membership-settings.hbs
|
||||
add|ember-template-lint|no-action|37|31|37|31|085289ac1e1263eed8737dc26c8774a9ea90598b|1658102400000|1668474000000|1673658000000|app/components/modal-free-membership-settings.hbs
|
||||
add|ember-template-lint|no-action|38|33|38|33|bf202ad54f2ff2a7e57d2e657d4d68f7814eba84|1658102400000|1668474000000|1673658000000|app/components/modal-free-membership-settings.hbs
|
||||
add|ember-template-lint|no-action|55|71|55|71|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-free-membership-settings.hbs
|
||||
add|ember-template-lint|no-down-event-binding|4|112|4|112|b3e87879e52edc06bb07f1ad0becc6ec762bbb39|1658102400000|1668474000000|1673658000000|app/components/modal-free-membership-settings.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|16|20|16|20|5fc3de620138bb4bee07640a9114b5e2be172f59|1658102400000|1668474000000|1673658000000|app/components/modal-free-membership-settings.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|26|20|26|20|8a899292e274791646c6c820fc584ac3aa4b1d83|1658102400000|1668474000000|1673658000000|app/components/modal-free-membership-settings.hbs
|
||||
add|ember-template-lint|no-action|5|50|5|50|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-impersonate-member.hbs
|
||||
add|ember-template-lint|no-action|5|74|5|74|d465b362b15b90cf42a093e72895155f49cdf6f2|1658102400000|1668474000000|1673658000000|app/components/modal-impersonate-member.hbs
|
||||
add|ember-template-lint|no-down-event-binding|5|107|5|107|b3e87879e52edc06bb07f1ad0becc6ec762bbb39|1658102400000|1668474000000|1673658000000|app/components/modal-impersonate-member.hbs
|
||||
add|ember-template-lint|no-action|44|57|44|57|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|51|56|51|56|190532b9954beb4e7e9e0f1c51d170e64d4a5315|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|57|34|57|34|ff609af61e9159dfc5386543d559f6d217d39531|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|58|29|58|29|9a0a72738b6e1a4f46415b2328c37d1814562717|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|111|89|111|89|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|117|91|117|91|031d04576149d3034549aa3d14bc26da704cd70c|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|120|116|120|116|7e581bf2ffd5254ae851201e1f23cb9eaa9b198b|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|130|120|130|120|031d04576149d3034549aa3d14bc26da704cd70c|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|133|103|133|103|7e581bf2ffd5254ae851201e1f23cb9eaa9b198b|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|144|112|144|112|031d04576149d3034549aa3d14bc26da704cd70c|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|148|110|148|110|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|154|97|154|97|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|157|112|157|112|031d04576149d3034549aa3d14bc26da704cd70c|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|161|99|161|99|031d04576149d3034549aa3d14bc26da704cd70c|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|164|110|164|110|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|172|91|172|91|031d04576149d3034549aa3d14bc26da704cd70c|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|175|102|175|102|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|182|95|182|95|031d04576149d3034549aa3d14bc26da704cd70c|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|186|102|186|102|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|simple-unless|84|30|84|30|5d9203e10703908836b537481cf012f167ded239|1658102400000|1668474000000|1673658000000|app/components/modal-import-members.hbs
|
||||
add|ember-template-lint|no-action|2|50|2|50|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-invite-new-user.hbs
|
||||
add|ember-template-lint|no-action|2|74|2|74|d465b362b15b90cf42a093e72895155f49cdf6f2|1658102400000|1668474000000|1673658000000|app/components/modal-invite-new-user.hbs
|
||||
add|ember-template-lint|no-action|26|27|26|27|1709109776bf3fc47aaecc21e6d3ec8a0489ad6b|1658102400000|1668474000000|1673658000000|app/components/modal-invite-new-user.hbs
|
||||
add|ember-template-lint|no-action|28|30|28|30|60f8b21893815d9e7a53a2fd75d89d5eb02516d2|1658102400000|1668474000000|1673658000000|app/components/modal-invite-new-user.hbs
|
||||
add|ember-template-lint|no-action|37|37|37|37|67e49e01cecb20912daabd71037b7bba8ae35d53|1658102400000|1668474000000|1673658000000|app/components/modal-invite-new-user.hbs
|
||||
add|ember-template-lint|no-action|38|37|38|37|e9ede6075ef60dc141631856eb197cfc4458ad81|1658102400000|1668474000000|1673658000000|app/components/modal-invite-new-user.hbs
|
||||
add|ember-template-lint|no-down-event-binding|2|107|2|107|b3e87879e52edc06bb07f1ad0becc6ec762bbb39|1658102400000|1668474000000|1673658000000|app/components/modal-invite-new-user.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|26|20|26|20|ce0d7e2e732b22ce3643fb9b1ac6ecef07c275ff|1658102400000|1668474000000|1673658000000|app/components/modal-invite-new-user.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-leave-settings.hbs
|
||||
add|ember-template-lint|no-action|15|63|15|63|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-leave-settings.hbs
|
||||
add|ember-template-lint|no-action|16|75|16|75|ebbd89a393bcec7f537f2104ace2a6b1941a19a7|1658102400000|1668474000000|1673658000000|app/components/modal-leave-settings.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-markdown-help.hbs
|
||||
add|ember-template-lint|no-whitespace-within-word|80|20|80|20|5ec988f82d06bb76be61f23dbb9cb65f94326f34|1658102400000|1668474000000|1673658000000|app/components/modal-markdown-help.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|28|24|28|24|42a29ae16e22270f0590c9ce5caa7bfec541ca0b|1658102400000|1668474000000|1673658000000|app/components/modal-member-tier.hbs
|
||||
add|ember-template-lint|no-action|5|57|5|57|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-members-label-form.hbs
|
||||
add|ember-template-lint|no-action|14|45|14|45|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-members-label-form.hbs
|
||||
add|ember-template-lint|no-action|23|59|23|59|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-members-label-form.hbs
|
||||
add|ember-template-lint|no-action|23|83|23|83|d465b362b15b90cf42a093e72895155f49cdf6f2|1658102400000|1668474000000|1673658000000|app/components/modal-members-label-form.hbs
|
||||
add|ember-template-lint|no-action|34|31|34|31|a8ad062b8379233b7970fe5ea74296fdf5011567|1658102400000|1668474000000|1673658000000|app/components/modal-members-label-form.hbs
|
||||
add|ember-template-lint|no-action|47|82|47|82|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-members-label-form.hbs
|
||||
add|ember-template-lint|no-action|49|16|49|16|d465b362b15b90cf42a093e72895155f49cdf6f2|1658102400000|1668474000000|1673658000000|app/components/modal-members-label-form.hbs
|
||||
add|ember-template-lint|no-action|55|113|55|113|c259009ff744c2e9f7bb273e0eb3a3879036ba85|1658102400000|1668474000000|1673658000000|app/components/modal-members-label-form.hbs
|
||||
add|ember-template-lint|no-down-event-binding|23|116|23|116|b3e87879e52edc06bb07f1ad0becc6ec762bbb39|1658102400000|1668474000000|1673658000000|app/components/modal-members-label-form.hbs
|
||||
add|ember-template-lint|no-down-event-binding|49|49|49|49|b3e87879e52edc06bb07f1ad0becc6ec762bbb39|1658102400000|1668474000000|1673658000000|app/components/modal-members-label-form.hbs
|
||||
add|ember-template-lint|no-down-event-binding|56|21|56|21|a158be60fde14211f6abfbac329c94fa5a3b4a46|1658102400000|1668474000000|1673658000000|app/components/modal-members-label-form.hbs
|
||||
add|ember-template-lint|no-duplicate-landmark-elements|19|4|19|4|75d318fd9d711332a80f28848acc874ec14b0f4f|1658102400000|1668474000000|1673658000000|app/components/modal-members-label-form.hbs
|
||||
add|ember-template-lint|no-action|13|112|13|112|decaf649532f1148f5071c2070b99d4e28986150|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|18|160|18|160|d7344cff0074353f10626fbe36707360e8ffd52c|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|20|150|20|150|d6149e8bd18677704c261b7d3e9afeaf8be9f6a6|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|38|69|38|69|b5e82dc8693610f3eb52006adac6456b86fb5ff1|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|65|69|65|69|fc5db5638fa960219dcaac666df85bd49ae5ba39|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|87|73|87|73|437199817818c66e4539bbc8501bd73148b521fd|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|115|73|115|73|4a9c1575e2a51a19d342f234cc41b045ea419ae3|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|136|73|136|73|40f0dc3732c554bb3f69d3d126263eb0a24f7738|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|150|112|150|112|d4947b819970cbee74170cd33c3067f75dde86d2|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|155|85|155|85|d7344cff0074353f10626fbe36707360e8ffd52c|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|169|64|169|64|6cb8968d67fbe25c46f1f963ad4f5564f51746f0|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|192|64|192|64|4432d73cb43005a6c5ab22362c8773f0f21d0ac4|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|203|68|203|68|3ae31e5d2f432b152768be816a2fc40803434382|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|210|183|210|183|b84f11a781130328997f76bd3ef2f3faa57a31d0|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|224|80|224|80|7fab1dccd9502d972b0b6f3fbbea8340483ada89|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|229|150|229|150|22c6e583ac81ee6f218414ee5f5271469891c114|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|234|141|234|141|69e5f19c951bf70e9e7eaa72ea2b784ab97c3dff|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|261|59|261|59|bb06a1edb1abab80686af224b1e77ff4cba9d604|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|272|112|272|112|044d2c50ef7e4765d1130fb40d620b90bae19174|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|277|85|277|85|d0601ba765735657d724123264ad81ccb959cd6b|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|283|55|283|55|b7fa34dcfcdeab84175766fab6106fe6659f53e7|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|306|32|306|32|b7ca157f62b1295d8f5839c8f5d33a6020bc98a8|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|313|57|313|57|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-down-event-binding|315|29|315|29|a158be60fde14211f6abfbac329c94fa5a3b4a46|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|261|52|261|52|825f0e3d4a33b20b5154b1fb4905e9ef5b1944d9|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|283|48|283|48|f182f9c53280940bba6c3153bd676cce9f4b09b5|1658102400000|1668474000000|1673658000000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-re-authenticate.hbs
|
||||
add|ember-template-lint|no-action|8|78|8|78|e65375f48d69b9bad0b967445b0f204783f7352a|1658102400000|1668474000000|1673658000000|app/components/modal-re-authenticate.hbs
|
||||
add|ember-template-lint|no-action|16|23|16|23|6756e119daad4aa143724e9b56a6aef744d65251|1658102400000|1668474000000|1673658000000|app/components/modal-re-authenticate.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|16|16|16|16|80681fcec2258c3d81a231bbd635aa1f03ff1452|1658102400000|1668474000000|1673658000000|app/components/modal-re-authenticate.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-regenerate-key.hbs
|
||||
add|ember-template-lint|no-action|20|41|20|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-regenerate-key.hbs
|
||||
add|ember-template-lint|no-autofocus-attribute|16|27|16|27|0d2b98a9093686a223ccc82022c3db762739720f|1658102400000|1668474000000|1673658000000|app/components/modal-regenerate-token.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-regenerate-token.hbs
|
||||
add|ember-template-lint|no-action|16|51|16|51|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-regenerate-token.hbs
|
||||
add|ember-template-lint|no-action|17|64|17|64|ebbd89a393bcec7f537f2104ace2a6b1941a19a7|1658102400000|1668474000000|1673658000000|app/components/modal-regenerate-token.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-reset-all-passwords.hbs
|
||||
add|ember-template-lint|no-action|11|79|11|79|e36addc82ad99ca2e46fce288afd43c8a56cc904|1658102400000|1668474000000|1673658000000|app/components/modal-reset-all-passwords.hbs
|
||||
add|ember-template-lint|no-action|23|41|23|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-reset-all-passwords.hbs
|
||||
add|ember-template-lint|no-action|4|55|4|55|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-stripe-connect.hbs
|
||||
add|ember-template-lint|no-action|4|79|4|79|d465b362b15b90cf42a093e72895155f49cdf6f2|1658102400000|1668474000000|1673658000000|app/components/modal-stripe-connect.hbs
|
||||
add|ember-template-lint|no-action|21|91|21|91|ebbd89a393bcec7f537f2104ace2a6b1941a19a7|1658102400000|1668474000000|1673658000000|app/components/modal-stripe-connect.hbs
|
||||
add|ember-template-lint|no-action|22|12|22|12|d465b362b15b90cf42a093e72895155f49cdf6f2|1658102400000|1668474000000|1673658000000|app/components/modal-stripe-connect.hbs
|
||||
add|ember-template-lint|no-down-event-binding|4|112|4|112|b3e87879e52edc06bb07f1ad0becc6ec762bbb39|1658102400000|1668474000000|1673658000000|app/components/modal-stripe-connect.hbs
|
||||
add|ember-template-lint|no-down-event-binding|22|45|22|45|b3e87879e52edc06bb07f1ad0becc6ec762bbb39|1658102400000|1668474000000|1673658000000|app/components/modal-stripe-connect.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-suspend-user.hbs
|
||||
add|ember-template-lint|no-action|11|41|11|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-suspend-user.hbs
|
||||
add|ember-template-lint|no-action|21|39|21|39|c1c116347fcaf9f69d60fa7ae58d746e9994cdbd|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|36|39|36|39|7307d2c5d78fa732b959ced6823b3d82dcc07446|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|45|39|45|39|7307d2c5d78fa732b959ced6823b3d82dcc07446|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|73|52|73|52|fa36149ee581ce634903cef877ccfdc133aa67ec|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|85|47|85|47|dcc09bb23a476d5b83b273b693cd8cb2aba68365|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|95|47|95|47|a80dd18e18dda6fb6f1f97d87bef2b8c2ce3d847|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|133|47|133|47|73ac7d3892fcbcf15c3d5c44fca14dd21016daea|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|139|53|139|53|c76b92d7bdb6ed498238b647928748aa4146dc24|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|140|55|140|55|02efc45b808f4cfdbe5f7b72b784337aef4e98a7|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|141|56|141|56|4227b643fe44a6c343b819bbee7eecdfb7916ccc|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|142|57|142|57|115cc8adc4c0f98e2d93a59ff8efbee711541336|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|150|41|150|41|c76b92d7bdb6ed498238b647928748aa4146dc24|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|151|44|151|44|4227b643fe44a6c343b819bbee7eecdfb7916ccc|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|152|45|152|45|115cc8adc4c0f98e2d93a59ff8efbee711541336|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|267|71|267|71|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|269|8|269|8|d465b362b15b90cf42a093e72895155f49cdf6f2|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-down-event-binding|269|41|269|41|b3e87879e52edc06bb07f1ad0becc6ec762bbb39|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|21|32|21|32|f69395e36c890a23e3f603ad3fd2cb384932af93|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|36|32|36|32|86b5983929a27ca8d458ff051c95a50a406fbe57|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|45|32|45|32|86b5983929a27ca8d458ff051c95a50a406fbe57|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|85|40|85|40|dcb4785647a50814bcfce82f8d68ac8dd8f54ec2|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|95|40|95|40|70487c008d7dda453fef82f0140699ee93c0055c|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|style-concatenation|203|54|203|54|23293f0c3838b23432d2b2daaf04b34504896d91|1658102400000|1668474000000|1673658000000|app/components/modal-tier.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-transfer-owner.hbs
|
||||
add|ember-template-lint|no-action|14|41|14|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-transfer-owner.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-unsubscribe-members.hbs
|
||||
add|ember-template-lint|no-action|50|89|50|89|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-unsubscribe-members.hbs
|
||||
add|ember-template-lint|no-action|54|71|54|71|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-unsubscribe-members.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-unsuspend-user.hbs
|
||||
add|ember-template-lint|no-action|11|41|11|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-unsuspend-user.hbs
|
||||
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-update-snippet.hbs
|
||||
add|ember-template-lint|no-action|15|41|15|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-update-snippet.hbs
|
||||
add|ember-template-lint|no-action|18|99|18|99|ed187c5c166b534cdd6e8c36a61ab1be5c2e259b|1658102400000|1668474000000|1673658000000|app/components/modal-upgrade-host-limit.hbs
|
||||
add|ember-template-lint|no-action|17|99|17|99|ed187c5c166b534cdd6e8c36a61ab1be5c2e259b|1658102400000|1668474000000|1673658000000|app/components/modal-upgrade-unsuspend-user-host-limit.hbs
|
||||
add|ember-template-lint|link-href-attributes|5|12|5|12|9a8d7ab37ceac5d53197768699f596ac2c5642de|1658102400000|1668474000000|1673658000000|app/components/modal-upload-image.hbs
|
||||
add|ember-template-lint|no-action|5|51|5|51|772866626d8e0cff8ddfe113040a2bbd27a2e282|1658102400000|1668474000000|1673658000000|app/components/modal-upload-image.hbs
|
||||
add|ember-template-lint|no-action|14|20|14|20|6df7472410762190612f82c70a94a954046e2007|1658102400000|1668474000000|1673658000000|app/components/modal-upload-image.hbs
|
||||
add|ember-template-lint|no-action|15|27|15|27|8618e8ea6dd0bea8b895ac0c2af021760d87b37e|1658102400000|1668474000000|1673658000000|app/components/modal-upload-image.hbs
|
||||
add|ember-template-lint|no-action|16|28|16|28|8618e8ea6dd0bea8b895ac0c2af021760d87b37e|1658102400000|1668474000000|1673658000000|app/components/modal-upload-image.hbs
|
||||
add|ember-template-lint|no-action|26|41|26|41|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-upload-image.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|5|51|5|51|9a8d7ab37ceac5d53197768699f596ac2c5642de|1658102400000|1668474000000|1673658000000|app/components/modal-upload-image.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|4|17|4|17|7fc4a97a1c85df0febb29bec1ba348b8808533d3|1658102400000|1668474000000|1673658000000|app/components/modal-upload-image.hbs
|
||||
add|ember-template-lint|no-action|4|55|4|55|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-action|4|79|4|79|d465b362b15b90cf42a093e72895155f49cdf6f2|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-action|14|23|14|23|616c6bc12166ed980b9deb2ffddb7ba609b2937b|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-action|15|27|15|27|676e64c434e820dde7c21b5edda3c5aab42e5888|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-action|39|28|39|28|3aa7d43ab2337652cbf63e9757c7faa11ef25145|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-action|53|23|53|23|7f08ac4e0471f57368848fc50f851dcce4a03657|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-action|54|27|54|27|a85c312488046bec6b27be0107bfb558a7666976|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-action|72|29|72|29|9ad7747dc8552fb38b69f68671439515bacfb215|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-action|73|31|73|31|783636572e0ed93e11919c197d6eecf1c8c7ee01|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-action|92|71|92|71|141d456b03124abca146e58e4ae15825fdd040bb|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-action|94|8|94|8|d465b362b15b90cf42a093e72895155f49cdf6f2|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-down-event-binding|4|112|4|112|b3e87879e52edc06bb07f1ad0becc6ec762bbb39|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-down-event-binding|94|41|94|41|b3e87879e52edc06bb07f1ad0becc6ec762bbb39|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|14|16|14|16|6bcbd72f8170ee033d443381f42149f02fe4e3a3|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|53|16|53|16|7b5b565dd7c0feea2efc926c9acd32060678c547|1658102400000|1668474000000|1673658000000|app/components/modal-webhook-form.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|4|12|4|12|8369d1b06deac93e8c8e05444670c15182aea434|1658102400000|1668474000000|1673658000000|app/templates/application-error.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|34|36|34|36|55f7b92a44dc897758496ba1560e34a1d53fb946|1658102400000|1668474000000|1673658000000|app/templates/offer.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|76|52|76|52|37bf29e93ffc35c71cdddd0ab98edeb60097e826|1658102400000|1668474000000|1673658000000|app/templates/offer.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|87|52|87|52|37bf29e93ffc35c71cdddd0ab98edeb60097e826|1658102400000|1668474000000|1673658000000|app/templates/offer.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|142|44|142|44|eb97a902a644abbe2df5bc2e57f7affa0a96fdac|1658102400000|1668474000000|1673658000000|app/templates/offer.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|163|40|163|40|5c7497369e17fd561d43dd7edad9c0887c21e9ff|1658102400000|1668474000000|1673658000000|app/templates/offer.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|177|40|177|40|f6273c4d79a992751e950f5793ddf17bcf03e649|1658102400000|1668474000000|1673658000000|app/templates/offer.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|192|36|192|36|5257e566aba16d7d9a28cf1df37a625c1eebfb25|1658102400000|1668474000000|1673658000000|app/templates/offer.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|81|36|81|36|fc4eb64cc0ad0cc9c500c7ef026e44649bc4778b|1658102400000|1668474000000|1673658000000|app/templates/offers.hbs
|
||||
add|ember-template-lint|table-groups|29|12|29|12|49e0fad85b643b6129963e49b6b5a37ccbd9796c|1658102400000|1668474000000|1673658000000|app/templates/offers.hbs
|
||||
add|ember-template-lint|no-action|1|40|1|40|180177e5e700535031498faa0ea4f1715555bfad|1658102400000|1668474000000|1673658000000|app/templates/pages-loading.hbs
|
||||
add|ember-template-lint|no-action|10|30|10|30|796968d082daba388ca19e97d45c7278d1076f04|1658102400000|1668474000000|1673658000000|app/templates/pages-loading.hbs
|
||||
add|ember-template-lint|no-action|13|36|13|36|796968d082daba388ca19e97d45c7278d1076f04|1658102400000|1668474000000|1673658000000|app/templates/pages-loading.hbs
|
||||
add|ember-template-lint|no-action|16|32|16|32|796968d082daba388ca19e97d45c7278d1076f04|1658102400000|1668474000000|1673658000000|app/templates/pages-loading.hbs
|
||||
add|ember-template-lint|no-action|19|29|19|29|796968d082daba388ca19e97d45c7278d1076f04|1658102400000|1668474000000|1673658000000|app/templates/pages-loading.hbs
|
||||
add|ember-template-lint|no-action|22|31|22|31|796968d082daba388ca19e97d45c7278d1076f04|1658102400000|1668474000000|1673658000000|app/templates/pages-loading.hbs
|
||||
add|ember-template-lint|no-action|10|30|10|30|7f6ae6475e94edfe22b59577ca52d10ec66835af|1658102400000|1668474000000|1673658000000|app/templates/pages.hbs
|
||||
add|ember-template-lint|no-action|13|36|13|36|addf24b8fbd6518060e412ec8b95309a05254988|1658102400000|1668474000000|1673658000000|app/templates/pages.hbs
|
||||
add|ember-template-lint|no-action|16|32|16|32|5bf020bb7cafdcfbcf809310232be927a9f1b2e4|1658102400000|1668474000000|1673658000000|app/templates/pages.hbs
|
||||
add|ember-template-lint|no-action|19|29|19|29|e3023c525990c1f8d1a30aa6ff20eb3bad8ec53b|1658102400000|1668474000000|1673658000000|app/templates/pages.hbs
|
||||
add|ember-template-lint|no-action|22|31|22|31|cbf4e21da2010b9af11294f458ef2ad40374d4f0|1658102400000|1668474000000|1673658000000|app/templates/pages.hbs
|
||||
add|ember-template-lint|no-action|1|40|1|40|180177e5e700535031498faa0ea4f1715555bfad|1658102400000|1668474000000|1673658000000|app/templates/posts-loading.hbs
|
||||
add|ember-template-lint|no-action|9|30|9|30|796968d082daba388ca19e97d45c7278d1076f04|1658102400000|1668474000000|1673658000000|app/templates/posts-loading.hbs
|
||||
add|ember-template-lint|no-action|12|36|12|36|796968d082daba388ca19e97d45c7278d1076f04|1658102400000|1668474000000|1673658000000|app/templates/posts-loading.hbs
|
||||
add|ember-template-lint|no-action|15|32|15|32|796968d082daba388ca19e97d45c7278d1076f04|1658102400000|1668474000000|1673658000000|app/templates/posts-loading.hbs
|
||||
add|ember-template-lint|no-action|18|29|18|29|796968d082daba388ca19e97d45c7278d1076f04|1658102400000|1668474000000|1673658000000|app/templates/posts-loading.hbs
|
||||
add|ember-template-lint|no-action|21|31|21|31|796968d082daba388ca19e97d45c7278d1076f04|1658102400000|1668474000000|1673658000000|app/templates/posts-loading.hbs
|
||||
add|ember-template-lint|no-action|10|30|10|30|7f6ae6475e94edfe22b59577ca52d10ec66835af|1658102400000|1668474000000|1673658000000|app/templates/posts.hbs
|
||||
add|ember-template-lint|no-action|13|36|13|36|addf24b8fbd6518060e412ec8b95309a05254988|1658102400000|1668474000000|1673658000000|app/templates/posts.hbs
|
||||
add|ember-template-lint|no-action|16|32|16|32|5bf020bb7cafdcfbcf809310232be927a9f1b2e4|1658102400000|1668474000000|1673658000000|app/templates/posts.hbs
|
||||
add|ember-template-lint|no-action|19|29|19|29|e3023c525990c1f8d1a30aa6ff20eb3bad8ec53b|1658102400000|1668474000000|1673658000000|app/templates/posts.hbs
|
||||
add|ember-template-lint|no-action|22|31|22|31|cbf4e21da2010b9af11294f458ef2ad40374d4f0|1658102400000|1668474000000|1673658000000|app/templates/posts.hbs
|
||||
add|ember-template-lint|no-action|4|85|4|85|ef910eebe8656965ebc73588c6f34a6260006b96|1658102400000|1668474000000|1673658000000|app/templates/reset.hbs
|
||||
add|ember-template-lint|no-action|19|35|19|35|8b04fb9251c6a34b6bfc2995b527539bb4106c37|1658102400000|1668474000000|1673658000000|app/templates/reset.hbs
|
||||
add|ember-template-lint|no-action|31|35|31|35|ddfad6e48c0df2368eed5dd9faf83f2a96dc182e|1658102400000|1668474000000|1673658000000|app/templates/reset.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|19|28|19|28|ea0378c5df53f2be82f0fd59e0c538efd7176851|1658102400000|1668474000000|1673658000000|app/templates/reset.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|31|28|31|28|06311a82a9589d4c126863466d56c7c4a76409d0|1658102400000|1668474000000|1673658000000|app/templates/reset.hbs
|
||||
add|ember-template-lint|no-action|91|17|91|17|c09dcb3dfe6ae3f5802c4ede66c922ed7b6a94e5|1658102400000|1668474000000|1673658000000|app/templates/settings.hbs
|
||||
add|ember-template-lint|no-action|92|15|92|15|4327a1a496a49bd7460b2972a934a3bfbfb40ce0|1658102400000|1668474000000|1673658000000|app/templates/settings.hbs
|
||||
add|ember-template-lint|no-action|20|31|20|31|3aa834e53af871a821ebb1b47f7a04f925c2b593|1658102400000|1668474000000|1673658000000|app/templates/setup.hbs
|
||||
add|ember-template-lint|no-action|21|35|21|35|ae65e93ed2b79a307e0eba50b154fe84ee1ae210|1658102400000|1668474000000|1673658000000|app/templates/setup.hbs
|
||||
add|ember-template-lint|no-action|37|31|37|31|eefdee43411bdd45c2786b9e826e6b550fd6212d|1658102400000|1668474000000|1673658000000|app/templates/setup.hbs
|
||||
add|ember-template-lint|no-action|38|35|38|35|a8e4bd4c57a8f2df65749b0cfa4349da22b2a0aa|1658102400000|1668474000000|1673658000000|app/templates/setup.hbs
|
||||
add|ember-template-lint|no-action|55|31|55|31|1709109776bf3fc47aaecc21e6d3ec8a0489ad6b|1658102400000|1668474000000|1673658000000|app/templates/setup.hbs
|
||||
add|ember-template-lint|no-action|56|35|56|35|8000241a81189d3876d264331ec0c9b6476df076|1658102400000|1668474000000|1673658000000|app/templates/setup.hbs
|
||||
add|ember-template-lint|no-action|73|31|73|31|6756e119daad4aa143724e9b56a6aef744d65251|1658102400000|1668474000000|1673658000000|app/templates/setup.hbs
|
||||
add|ember-template-lint|no-action|74|35|74|35|26b1d89c0e5bbcd629a215903c0819d35f798aa6|1658102400000|1668474000000|1673658000000|app/templates/setup.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|20|24|20|24|f0b7babba7593639d68dadae2f19e7144dd8c63e|1658102400000|1668474000000|1673658000000|app/templates/setup.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|37|24|37|24|2692530760cb1f7156dcf9570aacf0f8baca2770|1658102400000|1668474000000|1673658000000|app/templates/setup.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|55|24|55|24|ce0d7e2e732b22ce3643fb9b1ac6ecef07c275ff|1658102400000|1668474000000|1673658000000|app/templates/setup.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|73|24|73|24|80681fcec2258c3d81a231bbd635aa1f03ff1452|1658102400000|1668474000000|1673658000000|app/templates/setup.hbs
|
||||
add|ember-template-lint|no-action|15|85|15|85|5f277699a01bbbcf2e74101096f9c9bff189af74|1658102400000|1668474000000|1673658000000|app/templates/signin.hbs
|
||||
add|ember-template-lint|no-action|33|35|33|35|85790bc3673715479aa2678f29d8a61e47281bc7|1658102400000|1668474000000|1673658000000|app/templates/signin.hbs
|
||||
add|ember-template-lint|no-action|34|39|34|39|e65f48edccba27e52c1f8358a9795dc8ff20d5ef|1658102400000|1668474000000|1673658000000|app/templates/signin.hbs
|
||||
add|ember-template-lint|no-action|50|35|50|35|7432725bd18c48f69bf22dc9487d14d25dc6c1b7|1658102400000|1668474000000|1673658000000|app/templates/signin.hbs
|
||||
add|ember-template-lint|no-duplicate-landmark-elements|16|16|16|16|1661d2edb187b634c8187e5ecb0db15a4c7262fc|1658102400000|1668474000000|1673658000000|app/templates/signin.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|33|28|33|28|5b371baf419f247953b91b626611cb831c524af3|1658102400000|1668474000000|1673658000000|app/templates/signin.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|50|28|50|28|40caf07c7cebf6f4321c5b7e7f2f426b5c30217b|1658102400000|1668474000000|1673658000000|app/templates/signin.hbs
|
||||
add|ember-template-lint|no-action|17|25|17|25|fc6f0949f991c50fdeb7e2b42d25cd1169cf311c|1658102400000|1668474000000|1673658000000|app/templates/tag.hbs
|
||||
add|ember-template-lint|no-action|22|85|22|85|8024e7e42cf37a5954ea4db401cbf123931da388|1658102400000|1668474000000|1673658000000|app/templates/tag.hbs
|
||||
add|ember-template-lint|no-action|32|17|32|17|9fa650a0ca3325ef8182634ad5d34c5db548287a|1658102400000|1668474000000|1673658000000|app/templates/tag.hbs
|
||||
add|ember-template-lint|no-action|33|15|33|15|2fe6989da74d54cbdbaef0ae0a7cc68f22825a1d|1658102400000|1668474000000|1673658000000|app/templates/tag.hbs
|
||||
add|ember-template-lint|no-action|41|17|41|17|712a767fa44489687284ef2aa32c1acc5221afce|1658102400000|1668474000000|1673658000000|app/templates/tag.hbs
|
||||
add|ember-template-lint|no-action|42|15|42|15|d3fea3e454ca345739d46caf51afd85a3eea1041|1658102400000|1668474000000|1673658000000|app/templates/tag.hbs
|
||||
add|ember-template-lint|no-action|1|40|1|40|573086f09d13f9bd362afc216e57873538ba48bd|1658102400000|1668474000000|1673658000000|app/templates/tags.hbs
|
||||
add|ember-template-lint|no-action|6|108|6|108|ccc38f66549f9baedaa3b9943ae6634ea8f99e69|1658102400000|1668474000000|1673658000000|app/templates/tags.hbs
|
||||
add|ember-template-lint|no-action|7|110|7|110|c3819ce2b6989e8596be570ed0c9fb82b5012521|1658102400000|1668474000000|1673658000000|app/templates/tags.hbs
|
||||
add|ember-template-lint|no-nested-interactive|23|28|23|28|5cf783a5684dda036706ff7438472e99c60a88e7|1658102400000|1668474000000|1673658000000|app/templates/whatsnew.hbs
|
||||
add|ember-template-lint|no-triple-curlies|25|28|25|28|9f944c7207ff6f368ea28436c94ff67977fb6823|1658102400000|1668474000000|1673658000000|app/templates/whatsnew.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|18|28|18|28|80c1ce6724481312363dc4e1db42bf28b41909f2|1658102400000|1668474000000|1673658000000|app/templates/whatsnew.hbs
|
||||
add|ember-template-lint|require-input-label|28|20|28|20|dc08ecbcb2f3358fe94e7e2b8f24f7ca17bf77b5|1658102400000|1668474000000|1673658000000|app/components/custom-theme-settings/color.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|19|71|19|71|c1a223c4a0c265ed64685dbfbdd39581ba237083|1658102400000|1668474000000|1673658000000|app/components/custom-theme-settings/image.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|19|24|19|24|c1a223c4a0c265ed64685dbfbdd39581ba237083|1658102400000|1668474000000|1673658000000|app/components/custom-theme-settings/image.hbs
|
||||
add|ember-template-lint|no-autofocus-attribute|14|4|14|4|bc0a12a01d16038dd3224726d5c4fe81b2d458b6|1658102400000|1668474000000|1673658000000|app/components/gh-input-with-select/trigger.hbs
|
||||
add|ember-template-lint|no-down-event-binding|8|9|8|9|e82f6aa36fd44bb3dccff09770613eee19380f9b|1658102400000|1668474000000|1673658000000|app/components/gh-input-with-select/trigger.hbs
|
||||
add|ember-template-lint|require-input-label|4|0|4|0|ce9c488b3c6a110afe45e2242fb068fd4ed61f22|1658102400000|1668474000000|1673658000000|app/components/gh-input-with-select/trigger.hbs
|
||||
add|ember-template-lint|no-action|63|39|63|39|dcc09bb23a476d5b83b273b693cd8cb2aba68365|1658102400000|1668474000000|1673658000000|app/components/gh-launch-wizard/set-pricing.hbs
|
||||
add|ember-template-lint|no-action|78|39|78|39|a80dd18e18dda6fb6f1f97d87bef2b8c2ce3d847|1658102400000|1668474000000|1673658000000|app/components/gh-launch-wizard/set-pricing.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|63|32|63|32|dcb4785647a50814bcfce82f8d68ac8dd8f54ec2|1658102400000|1668474000000|1673658000000|app/components/gh-launch-wizard/set-pricing.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|78|32|78|32|70487c008d7dda453fef82f0140699ee93c0055c|1658102400000|1668474000000|1673658000000|app/components/gh-launch-wizard/set-pricing.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|6|32|6|32|508e64575a985432d0588f3291a126c4b62e68d8|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/design.hbs
|
||||
add|ember-template-lint|no-action|97|83|97|83|40a33b8afd29e93eebeaccdb49d729c06f755e1f|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/footer.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|97|83|97|83|91111b837d1217ec9988076f5263b0e32df72604|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/footer.hbs
|
||||
add|ember-template-lint|no-unknown-arguments-for-builtin-components|29|104|29|104|156670ca427c49c51f0a94f862b286ccc9466d92|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/footer.hbs
|
||||
add|ember-template-lint|no-unknown-arguments-for-builtin-components|39|107|39|107|156670ca427c49c51f0a94f862b286ccc9466d92|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/footer.hbs
|
||||
add|ember-template-lint|no-unknown-arguments-for-builtin-components|49|121|49|121|156670ca427c49c51f0a94f862b286ccc9466d92|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/footer.hbs
|
||||
add|ember-template-lint|no-unknown-arguments-for-builtin-components|84|93|84|93|156670ca427c49c51f0a94f862b286ccc9466d92|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/footer.hbs
|
||||
add|ember-template-lint|require-context-role|34|89|34|89|0be75355d0dd43dafc60091285ba906c43350d19|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/footer.hbs
|
||||
add|ember-template-lint|require-context-role|57|57|57|57|0be75355d0dd43dafc60091285ba906c43350d19|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/footer.hbs
|
||||
add|ember-template-lint|require-context-role|62|57|62|57|0be75355d0dd43dafc60091285ba906c43350d19|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/footer.hbs
|
||||
add|ember-template-lint|require-context-role|73|131|73|131|0be75355d0dd43dafc60091285ba906c43350d19|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/footer.hbs
|
||||
add|ember-template-lint|no-action|10|110|10|110|4bbb6ad1f623335866ac715f34f2ce9829b1d55b|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/main.hbs
|
||||
add|ember-template-lint|no-action|26|30|26|30|8eee88dbd40609f8ddc00330f4a47b1d30c483f0|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/main.hbs
|
||||
add|ember-template-lint|no-action|121|110|121|110|c8306856104d54d12e3db50acab26094d0793fb3|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/main.hbs
|
||||
add|ember-template-lint|no-forbidden-elements|147|20|147|20|966511a5bc154d46999a9594e3d39eb6ee5028a8|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/main.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|26|30|26|30|929269f70336deb9f640bc0b5d54a0bd5336d27a|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/main.hbs
|
||||
add|ember-template-lint|no-invalid-link-title|22|24|22|24|17a357b69040eb9e19a79fe08468c698eab84939|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/main.hbs
|
||||
add|ember-template-lint|no-triple-curlies|146|20|146|20|9a305980cd2c3469773e4adafa4e02a949dbc919|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/main.hbs
|
||||
add|ember-template-lint|no-unknown-arguments-for-builtin-components|22|51|22|51|b8aae2daed1c14cf280800b3d282d11fb14851a4|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/main.hbs
|
||||
add|ember-template-lint|no-unknown-arguments-for-builtin-components|27|103|27|103|571c3d774ed33480f528b54b323b23bfd7148eee|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/main.hbs
|
||||
add|ember-template-lint|no-unknown-arguments-for-builtin-components|39|110|39|110|b8aae2daed1c14cf280800b3d282d11fb14851a4|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/main.hbs
|
||||
add|ember-template-lint|no-unknown-arguments-for-builtin-components|111|56|111|56|b8aae2daed1c14cf280800b3d282d11fb14851a4|1658102400000|1668474000000|1673658000000|app/components/gh-nav-menu/main.hbs
|
||||
add|ember-template-lint|no-action|96|14|96|14|3d44a2ad21d60d18bed6f79265caa4887361aaa4|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu/email.hbs
|
||||
add|ember-template-lint|no-action|105|27|105|27|5ac8ef12ae9cda0ec21b12a86319e34a870277de|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu/email.hbs
|
||||
add|ember-template-lint|no-action|106|31|106|31|134228426e26bc169dc65108012ffef70c0bb46a|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu/email.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|105|20|105|20|cd7c5bd8b1c45775a6dff5c502055d5522929b32|1658102400000|1668474000000|1673658000000|app/components/gh-post-settings-menu/email.hbs
|
||||
add|ember-template-lint|no-down-event-binding|8|56|8|56|a158be60fde14211f6abfbac329c94fa5a3b4a46|1658102400000|1668474000000|1673658000000|app/components/gh-power-select/trigger.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|8|51|8|51|66a27dbed218d15e49e91c72a93215ad0d90f778|1658102400000|1668474000000|1673658000000|app/components/gh-power-select/trigger.hbs
|
||||
add|ember-template-lint|no-yield-only|1|0|1|0|a5fa6e8c1e0f03fb31b5cd17770e4368d44932e4|1658102400000|1668474000000|1673658000000|app/components/gh-token-input/label-token.hbs
|
||||
add|ember-template-lint|no-yield-only|1|0|1|0|a5fa6e8c1e0f03fb31b5cd17770e4368d44932e4|1658102400000|1668474000000|1673658000000|app/components/gh-token-input/tag-token.hbs
|
||||
add|ember-template-lint|no-action|8|19|8|19|73ac7d3892fcbcf15c3d5c44fca14dd21016daea|1658102400000|1668474000000|1673658000000|app/components/gh-token-input/trigger.hbs
|
||||
add|ember-template-lint|no-down-event-binding|31|25|31|25|a158be60fde14211f6abfbac329c94fa5a3b4a46|1658102400000|1668474000000|1673658000000|app/components/gh-token-input/trigger.hbs
|
||||
add|ember-template-lint|no-down-event-binding|61|17|61|17|e82f6aa36fd44bb3dccff09770613eee19380f9b|1658102400000|1668474000000|1673658000000|app/components/gh-token-input/trigger.hbs
|
||||
add|ember-template-lint|no-positive-tabindex|44|8|44|8|6118264a9a0599fab6ad4da7264c1bfffa88687e|1658102400000|1668474000000|1673658000000|app/components/gh-token-input/trigger.hbs
|
||||
add|ember-template-lint|no-down-event-binding|6|86|6|86|a158be60fde14211f6abfbac329c94fa5a3b4a46|1658102400000|1668474000000|1673658000000|app/components/modals/custom-view-form.hbs
|
||||
add|ember-template-lint|no-down-event-binding|55|21|55|21|a158be60fde14211f6abfbac329c94fa5a3b4a46|1658102400000|1668474000000|1673658000000|app/components/modals/custom-view-form.hbs
|
||||
add|ember-template-lint|no-down-event-binding|62|21|62|21|a158be60fde14211f6abfbac329c94fa5a3b4a46|1658102400000|1668474000000|1673658000000|app/components/modals/custom-view-form.hbs
|
||||
add|ember-template-lint|require-iframe-title|27|20|27|20|94e58d11848d5613900c2188ba63dac41f4c03bb|1658102400000|1668474000000|1673658000000|app/components/modals/email-preview.hbs
|
||||
add|ember-template-lint|require-iframe-title|42|16|42|16|a3292b469dc37f2f4791e7f224b0b65c8ecf5d18|1658102400000|1668474000000|1673658000000|app/components/modals/email-preview.hbs
|
||||
add|ember-template-lint|no-autofocus-attribute|21|20|21|20|942419d05c04ded6716f09faecd6b1ab55418121|1658102400000|1668474000000|1673658000000|app/components/modals/new-custom-integration.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|2|37|2|37|e21ba31f54b631a428c28a1c9f88d0dc66f2f5fc|1658102400000|1668474000000|1673658000000|app/components/modals/search.hbs
|
||||
add|ember-template-lint|no-action|31|26|31|26|0e827c1770073f988535ceb9d6c49808a153d896|1658102400000|1668474000000|1673658000000|app/components/settings/members-default-post-access.hbs
|
||||
add|ember-template-lint|no-action|15|21|15|21|df631bb317cb737790ce8fd9a9b107f5a196a10d|1658102400000|1668474000000|1673658000000|app/templates/settings/code-injection.hbs
|
||||
add|ember-template-lint|no-action|16|19|16|19|35178fb4b4e564116d2012fda7d3977a471fc8e7|1658102400000|1668474000000|1673658000000|app/templates/settings/code-injection.hbs
|
||||
add|ember-template-lint|no-action|32|190|32|190|ed86b04ad5f38f1a14076d74e0021268c98b3927|1658102400000|1668474000000|1673658000000|app/templates/settings/code-injection.hbs
|
||||
add|ember-template-lint|no-action|40|190|40|190|2164918a2fce3dd29c513fd604fe36e3cef2b7c3|1658102400000|1668474000000|1673658000000|app/templates/settings/code-injection.hbs
|
||||
add|ember-template-lint|no-action|15|21|15|21|df631bb317cb737790ce8fd9a9b107f5a196a10d|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|16|19|16|19|35178fb4b4e564116d2012fda7d3977a471fc8e7|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|30|57|30|57|55c24e7e54f416f3d86061f4f1c1ccaba8fbfc27|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|38|39|38|39|03e3188d8e2dfe0b2937d5b71883f016d07a4946|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|39|43|39|43|1c60e378fb19511c2cac50a7a42b6aee20605994|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|49|39|49|39|03463f0fb55cc878bd4c913e47d55f41e2947661|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|50|43|50|43|6cdbcde69f3d62415e293a78dbcf26b96fc36f45|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|67|57|67|57|f3fb07d2afa3090b0f97067437413284ed58f6c5|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|75|36|75|36|0232e593042bb48fcc89bf9ab0b91b242f264fba|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|87|57|87|57|6128361491f571a637abfda97ad687f73ab667c3|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|95|39|95|39|ba45af3e814e72ad4d95a8f317bb9cc9ad9215c8|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|96|43|96|43|2c1cc5f0c3aeddb4838344241fd5ad1706fa7f98|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|118|57|118|57|bf49560351459e5d6c9fde8cb2f052ce17b1b657|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|132|51|132|51|0a0ef4f73dbe59f49337893fdc92d2a55728ed07|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|145|51|145|51|c7ba1dbb7596398deeed5fb7a7fe3f7e57929ddb|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|180|57|180|57|d26ee22129821fa02604d8e2a93e9ae0a916639f|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|193|48|193|48|3732114c07810566f79f1afa68845ee20b92fa18|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|194|48|194|48|bc30430315dac2132453165473d30c5b2d80ba92|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|204|47|204|47|4345c59c19cd5b007ab34318b7fa11f34cca667a|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|215|47|215|47|44ee454f126f973a6a74fb5541a028ec8c35c670|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|268|57|268|57|84ed8480d5bf5a95046ccc582c64bc97f705018e|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|281|52|281|52|c6cdf101b8a83665bceda4c602acf68cdf3ef67b|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|282|52|282|52|1c05a27faf63c386a263092a5c7e44034a773719|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|292|51|292|51|3ca4850a38d0a79f31de327e4f0619cb4bd61305|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|303|51|303|51|97850bdbac4ce72904bbac5af258e64ccbeab822|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|357|57|357|57|e5c846bf3deb14ba03604a3d324a61d16248b453|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|368|39|368|39|1f32c13702a8b46410e9e10f9988fa2e0df41968|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|369|43|369|43|69b8d823159491abd67494eba728616ac0a928a5|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|381|39|381|39|226e659be4c2c10c0e5fbebb40f712f3016c9b90|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|382|43|382|43|d746c5532ce38d23d8537195a3afa6ce7235f577|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|413|40|413|40|3f4324c899d78620bab1b84fd1824e93c737c15c|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|431|47|431|47|866af142283d04b707df59f73fb539df98728301|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|432|43|432|43|674aac6769471bd8c724fab4dbd566cf220e1166|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|38|32|38|32|cfeb8caa552eac5ebbb6d91854cb74f89cdf6e1a|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|49|32|49|32|83c111cfc155ec3b6832e60e2d1a9414f6f75623|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|95|32|95|32|c34c2e482b61c7ccd2b1103b5dee4b291eb2ec47|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|132|44|132|44|615e13dcb9f6e380efec5f70a1bd4619c0ad1426|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|145|44|145|44|f25458443d396a354c2971d0c90c2ddccf964fb1|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|204|40|204|40|b48c77bffa2c66754ecbbb20b4d59b8ee317ae0f|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|215|40|215|40|46e9644476d397c95936c4f2173ca0c4a214dd9e|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|292|44|292|44|c6a6197b290f6b1e41cdc0b88a2571db7c025ddd|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|303|44|303|44|3c119ddbb78d06c18da5b1e812440b28881a9448|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|368|32|368|32|c14e20b2b090706f9af9fa416c4300c06e206894|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|381|32|381|32|e286f50c92a17a31581207e4c65054f146cbd533|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|432|36|432|36|60888a79c6c68107f859a65663f794408bc434f4|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-quoteless-attributes|372|123|372|123|7c9b71d827d85d0ffd6dbe85c9f2793c321f744a|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-quoteless-attributes|385|121|385|121|939e14038426a56465b8768f3b25f179400c3eee|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-quoteless-attributes|435|127|435|127|cf74da438ca6d134c28c9b80fecebd105f18f0ed|1658102400000|1668474000000|1673658000000|app/templates/settings/general.hbs
|
||||
add|ember-template-lint|no-action|27|42|27|42|b60a8d95bc6d0e8c9f57d46647ab18947415d9dc|1658102400000|1668474000000|1673658000000|app/templates/settings/labs.hbs
|
||||
add|ember-template-lint|no-action|66|61|66|61|aea6df58f8bc5e92ec7e059aff378cc785db5733|1658102400000|1668474000000|1673658000000|app/templates/settings/labs.hbs
|
||||
add|ember-template-lint|no-action|76|82|76|82|81c7e8a314b8c2d62ceb49a48b20664192cdef22|1658102400000|1668474000000|1673658000000|app/templates/settings/labs.hbs
|
||||
add|ember-template-lint|no-action|106|44|106|44|69e5f19c951bf70e9e7eaa72ea2b784ab97c3dff|1658102400000|1668474000000|1673658000000|app/templates/settings/labs.hbs
|
||||
add|ember-template-lint|no-action|119|49|119|49|03987b6ed09575e15d2bac6a91e1d93d56125c51|1658102400000|1668474000000|1673658000000|app/templates/settings/labs.hbs
|
||||
add|ember-template-lint|no-action|155|44|155|44|69e5f19c951bf70e9e7eaa72ea2b784ab97c3dff|1658102400000|1668474000000|1673658000000|app/templates/settings/labs.hbs
|
||||
add|ember-template-lint|no-action|168|49|168|49|cc3eebe4d4988160eb63e445ccf7b62bcf122531|1658102400000|1668474000000|1673658000000|app/templates/settings/labs.hbs
|
||||
add|ember-template-lint|no-action|237|15|237|15|81c7e8a314b8c2d62ceb49a48b20664192cdef22|1658102400000|1668474000000|1673658000000|app/templates/settings/labs.hbs
|
||||
add|ember-template-lint|no-action|15|21|15|21|df631bb317cb737790ce8fd9a9b107f5a196a10d|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|16|19|16|19|35178fb4b4e564116d2012fda7d3977a471fc8e7|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|31|45|31|45|812038f28c7626209e8ae622703ea8c4e75b7f60|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|32|48|32|48|930d7fb74c5abe804a6b02736914e835c1b25777|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|33|47|33|47|3c723798059c71aedfa95cd70507bc0308728544|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|34|49|34|49|115cc8adc4c0f98e2d93a59ff8efbee711541336|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|42|33|42|33|812038f28c7626209e8ae622703ea8c4e75b7f60|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|43|35|43|35|3c723798059c71aedfa95cd70507bc0308728544|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|44|37|44|37|115cc8adc4c0f98e2d93a59ff8efbee711541336|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|60|45|60|45|812038f28c7626209e8ae622703ea8c4e75b7f60|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|61|48|61|48|930d7fb74c5abe804a6b02736914e835c1b25777|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|62|47|62|47|3c723798059c71aedfa95cd70507bc0308728544|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|63|49|63|49|115cc8adc4c0f98e2d93a59ff8efbee711541336|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|71|33|71|33|812038f28c7626209e8ae622703ea8c4e75b7f60|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|72|35|72|35|3c723798059c71aedfa95cd70507bc0308728544|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-action|73|37|73|37|115cc8adc4c0f98e2d93a59ff8efbee711541336|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|no-duplicate-landmark-elements|53|16|53|16|f2740bc03b393e8708035d4952a2ab630472bd22|1658102400000|1668474000000|1673658000000|app/templates/settings/navigation.hbs
|
||||
add|ember-template-lint|simple-unless|7|16|7|16|02b279bc993a1ee6632c7e6edf341a2aabc519cb|1658102400000|1668474000000|1673658000000|app/components/modals/design/theme-errors.hbs
|
||||
add|ember-template-lint|no-action|17|21|17|21|df631bb317cb737790ce8fd9a9b107f5a196a10d|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/amp.hbs
|
||||
add|ember-template-lint|no-action|18|19|18|19|35178fb4b4e564116d2012fda7d3977a471fc8e7|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/amp.hbs
|
||||
add|ember-template-lint|no-action|53|52|53|52|2b83cff3852a6f3cb1f2d2613e1bb611e1af4996|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/amp.hbs
|
||||
add|ember-template-lint|no-action|73|54|73|54|2fc1814780dbf5fab6c8d588de3644dce925acbc|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/amp.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|26|20|26|20|91e015c567087d47025b0e457150eb9d6a3a8f1c|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/amp.hbs
|
||||
add|ember-template-lint|no-action|17|21|17|21|df631bb317cb737790ce8fd9a9b107f5a196a10d|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/firstpromoter.hbs
|
||||
add|ember-template-lint|no-action|18|19|18|19|35178fb4b4e564116d2012fda7d3977a471fc8e7|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/firstpromoter.hbs
|
||||
add|ember-template-lint|no-action|53|48|53|48|2b83cff3852a6f3cb1f2d2613e1bb611e1af4996|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/firstpromoter.hbs
|
||||
add|ember-template-lint|no-action|73|50|73|50|2fc1814780dbf5fab6c8d588de3644dce925acbc|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/firstpromoter.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|26|20|26|20|6cfcbac2f373de035261f93c852c6d6d372b6ce4|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/firstpromoter.hbs
|
||||
add|ember-template-lint|no-action|17|21|17|21|df631bb317cb737790ce8fd9a9b107f5a196a10d|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/slack.hbs
|
||||
add|ember-template-lint|no-action|18|19|18|19|35178fb4b4e564116d2012fda7d3977a471fc8e7|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/slack.hbs
|
||||
add|ember-template-lint|no-action|39|94|39|94|3230630f00a597aa98dfb1dd3533375d55516667|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/slack.hbs
|
||||
add|ember-template-lint|no-action|52|55|52|55|2c1e9f8787439af0488bbeeed6adc276a525e585|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/slack.hbs
|
||||
add|ember-template-lint|no-action|53|59|53|59|126db4f8e2a36d157b01c874381f548fd8d4a79e|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/slack.hbs
|
||||
add|ember-template-lint|no-action|55|58|55|58|2fc1814780dbf5fab6c8d588de3644dce925acbc|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/slack.hbs
|
||||
add|ember-template-lint|no-action|78|55|78|55|f790efaece9bf04396b25c2fedf4625a426f2467|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/slack.hbs
|
||||
add|ember-template-lint|no-action|80|58|80|58|2fc1814780dbf5fab6c8d588de3644dce925acbc|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/slack.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|52|48|52|48|138f75fcc7408e61c79185c37265529c752af8b3|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/slack.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|78|48|78|48|8fb95409c6333d15490f0777239f6e04641215e6|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/slack.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|26|20|26|20|a500be6114ce681e610b6fec7d2a6cea2f4555f7|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/slack.hbs
|
||||
add|ember-template-lint|simple-unless|61|44|61|44|02b279bc993a1ee6632c7e6edf341a2aabc519cb|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/slack.hbs
|
||||
add|ember-template-lint|no-action|17|21|17|21|df631bb317cb737790ce8fd9a9b107f5a196a10d|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/unsplash.hbs
|
||||
add|ember-template-lint|no-action|18|19|18|19|35178fb4b4e564116d2012fda7d3977a471fc8e7|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/unsplash.hbs
|
||||
add|ember-template-lint|no-action|53|56|53|56|2b83cff3852a6f3cb1f2d2613e1bb611e1af4996|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/unsplash.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|26|20|26|20|c8f3616a9f2eef8909029c4ffdedc5886064d6ca|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/unsplash.hbs
|
||||
add|ember-template-lint|no-action|33|66|33|66|2e441eb8956fdd6684b75ae1139f097237ebc1fa|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/zapier.hbs
|
||||
add|ember-template-lint|no-action|36|66|36|66|2d51bed033fe50baea3002bf5acc234ac3738b58|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/zapier.hbs
|
||||
add|ember-template-lint|no-action|58|66|58|66|bfcab210b651dbe74a1b9e5eb565756ab863f3bb|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/zapier.hbs
|
||||
add|ember-template-lint|no-action|253|17|253|17|1907c9b1ae81cfb450b7a2ae7e0f874c26595144|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/zapier.hbs
|
||||
add|ember-template-lint|no-action|254|15|254|15|20eb710122c95e74c9293fe14e11f105a86f172a|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/zapier.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|18|24|18|24|c1c041ff951875c8cf87ba24bd45d2a2a7597969|1658102400000|1668474000000|1673658000000|app/templates/settings/integrations/zapier.hbs
|
||||
add|ember-template-lint|no-action|30|67|30|67|84cad828f9f84f35c077dfea0661dac1ccbbcc79|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/index.hbs
|
||||
add|ember-template-lint|no-action|37|118|37|118|f00cc934ece7532a0c6ac89df3a05ff04b1f1501|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/index.hbs
|
||||
add|ember-template-lint|no-action|44|19|44|19|40bf79016dee9fe1dca46383af6c4c35a5441ea9|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/index.hbs
|
||||
add|ember-template-lint|no-action|50|19|50|19|de05e88a1b6b658430d78f36b93dfa372903c26d|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/index.hbs
|
||||
add|ember-template-lint|no-action|89|103|89|103|8a5526f86c3c3289a47e2e033e39292ceaa3999e|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/index.hbs
|
||||
add|ember-template-lint|no-action|92|105|92|105|8ef55f22a4332a43d47b2508c5903048f972cefa|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/index.hbs
|
||||
add|ember-template-lint|no-route-action|63|62|63|62|e9f8663f45f4f41bc590f8db8df3d5dabd9cc732|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/index.hbs
|
||||
add|ember-template-lint|no-action|22|25|22|25|df631bb317cb737790ce8fd9a9b107f5a196a10d|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|23|23|23|23|35178fb4b4e564116d2012fda7d3977a471fc8e7|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|39|54|39|54|5af61080eb82340db68e59c287a1e9273f805b1e|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|44|49|44|49|7f6812a5fb3d34d95b4e27ce4b9691b3065f7b8c|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|45|47|45|47|5af61080eb82340db68e59c287a1e9273f805b1e|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|52|93|52|93|a927cb3d62f088a149f64288c634921673563e7d|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|58|99|58|99|7628e64f5e59b4de2f529e029596f5570a4827ae|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|65|103|65|103|a98190ea4757d865b31ad00184c6cc12a89c1009|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|80|29|80|29|c81a8b61bc2a6a24c5103fd71843b71c96a7e413|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|81|27|81|27|a927cb3d62f088a149f64288c634921673563e7d|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|88|29|88|29|509c2ee1c3b1c57ee10b62649404561d5817de7f|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|89|27|89|27|7628e64f5e59b4de2f529e029596f5570a4827ae|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|99|31|99|31|a98190ea4757d865b31ad00184c6cc12a89c1009|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|104|33|104|33|34da22516e03547d6f322fe271a3251add8f3ec9|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|105|31|105|31|a98190ea4757d865b31ad00184c6cc12a89c1009|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|119|82|119|82|e2e24a0e1efe19ed982cc8140248570c03eee7b9|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|122|88|122|88|ea7c6e414cc202b6e4049eae4f60eb0829bd3ff0|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|126|35|126|35|ea7c6e414cc202b6e4049eae4f60eb0829bd3ff0|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|133|42|133|42|7a2a7a1b2159d811b310a9b89817108ddff9df88|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|137|35|137|35|7a2a7a1b2159d811b310a9b89817108ddff9df88|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|152|39|152|39|3da2270da5495c84c2b069564f2163b109d0e73e|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|153|43|153|43|4cd04fee6bbe85afe19501cd2f393c567e2f465e|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|172|39|172|39|66cebfc8448eced0bccf3faa57b4af0cd633e65e|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|173|43|173|43|3a3cdadac5b48ea65a6b10f0bb9319bed6d9ed79|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|193|43|193|43|eaee037b0dc6f3a8903f73c20098dfd77cba770c|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|194|47|194|47|b435e7cd7f48e3edb3214adc9c171181805500e3|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|207|75|207|75|e0b0429cdf24580adc31da56392cc9814a42b2bb|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|215|45|215|45|4a66eafd7ad8fea066c11d298665e40bb02e3fc1|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|216|43|216|43|923e950a7705bc29217afe2e0a81dbf6012777f1|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|227|39|227|39|5340ffcfe14bc0696c786cb698dc7436ff3e7b2a|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|228|43|228|43|cbc552ad67fd7382c02ac946a3de0fbe7dbb8509|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|243|39|243|39|6c405ec87c2cbc8470edc3bcba2ab93bf20e5c17|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|244|43|244|43|b26a1d1b4ba0bbe491099aeb78875160e2e417d6|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|259|39|259|39|1f32c13702a8b46410e9e10f9988fa2e0df41968|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|260|43|260|43|69b8d823159491abd67494eba728616ac0a928a5|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|276|39|276|39|226e659be4c2c10c0e5fbebb40f712f3016c9b90|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|277|43|277|43|d746c5532ce38d23d8537195a3afa6ce7235f577|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|289|39|289|39|61d6401bd1e43f082ab6fa49c83002d2e3faa886|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|290|43|290|43|eb4de7d8a9578a4b960da7d9b7679f3b1d67f886|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|332|106|332|106|d2028f094665a3702bbea21159b1c12237c94bc9|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|343|47|343|47|4661ca0fecfa23048521845c78a5a7cfce28609f|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|345|50|345|50|6403bc3b28507827968bd1fcd428d36d63b53511|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|360|43|360|43|c262ddbe8aa83b429a32392155869d7a068a5c3e|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|362|46|362|46|6403bc3b28507827968bd1fcd428d36d63b53511|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|375|43|375|43|08c167af90156f5bce0a15fe0153ba0c222297b6|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|377|46|377|46|6403bc3b28507827968bd1fcd428d36d63b53511|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|407|62|407|62|4150a56e60c88b9d8203a48c28069896522205ed|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|410|62|410|62|38bfdf3e4ec8c79d195e80a48c2f343757845cab|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|427|49|427|49|60dfff263d99797e3bcdf455c9c6ccf8ae539f1e|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-action|428|47|428|47|a1a5fab148882be2de9da5aca1d7d7e39e65a326|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-duplicate-landmark-elements|332|16|332|16|f8a398a428b26623555526f6e625aeca79d8dc72|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-duplicate-landmark-elements|393|16|393|16|c7e339eb9f5d83115a7fda2636d1487ba11b133d|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|207|62|207|62|19a5403007099acc29f4f7bbbb8fd008405002eb|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|152|32|152|32|7f1d3292c273e623c95015be04a3fc5bac131480|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|172|32|172|32|fed655605208b290a18b9f7da51a6cf7b40c0e9a|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|193|36|193|36|881cf5dcdbe984cb7b4d5a2ddde4e111147b3817|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|227|32|227|32|23912c3130690f1fb910732f42bc87922e3bc317|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|243|32|243|32|50e244fdb1572202192f659fe4052f18bea92552|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|259|32|259|32|c14e20b2b090706f9af9fa416c4300c06e206894|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|276|32|276|32|e286f50c92a17a31581207e4c65054f146cbd533|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|289|32|289|32|b6eefc591c761dcae13b0de50e074f5aac8f1d80|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|343|40|343|40|f9ad8f1af9275247b94cbab1de26e9f9d2218da5|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|360|36|360|36|2a5f6ae5ef152f0c0d953cfbaf810fdf0d08e996|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|375|36|375|36|c9d092bb6707073e3f11bdc393d1c1361e5f87c4|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-potential-path-strings|404|53|404|53|5c3a010b4844cd530c312ef2aa32de64b8a43db1|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-valueless-arguments|402|44|402|44|5d042f1f94738e82cc1b0c5c008cece383beaa30|1658102400000|1668474000000|1673658000000|app/templates/settings/staff/user.hbs
|
||||
add|ember-template-lint|no-down-event-binding|8|9|8|9|e82f6aa36fd44bb3dccff09770613eee19380f9b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-alt-input.hbs
|
||||
add|ember-template-lint|require-input-label|1|0|1|0|77a374cbfa52104125065d3ea3bb8ddee2e10a5e|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-alt-input.hbs
|
||||
add|ember-template-lint|no-action|15|18|15|18|8e38f1d73c212f5fe08d155a9ba83f7dc30e38a8|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-basic-html-input.hbs
|
||||
add|ember-template-lint|no-action|16|14|16|14|98c33bc8a7a032179cbbc279c04e635a4ac4bce3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-basic-html-input.hbs
|
||||
add|ember-template-lint|no-action|25|14|25|14|98c33bc8a7a032179cbbc279c04e635a4ac4bce3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-basic-html-input.hbs
|
||||
add|ember-template-lint|no-action|34|16|34|16|5b7ad6ecbae8b6379d42a3c7f343485cf7cecded|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-basic-html-input.hbs
|
||||
add|ember-template-lint|no-action|15|18|15|18|8e38f1d73c212f5fe08d155a9ba83f7dc30e38a8|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-basic-html-textarea.hbs
|
||||
add|ember-template-lint|no-action|16|14|16|14|98c33bc8a7a032179cbbc279c04e635a4ac4bce3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-basic-html-textarea.hbs
|
||||
add|ember-template-lint|no-action|25|14|25|14|98c33bc8a7a032179cbbc279c04e635a4ac4bce3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-basic-html-textarea.hbs
|
||||
add|ember-template-lint|no-action|34|16|34|16|5b7ad6ecbae8b6379d42a3c7f343485cf7cecded|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-basic-html-textarea.hbs
|
||||
add|ember-template-lint|no-action|6|14|6|14|870ce151c8f27f58bc1f8abc4be7707d1790515e|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-caption-input.hbs
|
||||
add|ember-template-lint|no-action|7|13|7|13|31c56395c52506a01b65825ef8bdb3b71a95c36b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-caption-input.hbs
|
||||
add|ember-template-lint|no-action|8|12|8|12|0dc0b984b33c3c1842d08f078270473b16371137|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-caption-input.hbs
|
||||
add|ember-template-lint|no-action|9|15|9|15|3b95b78c2246816876653a854e700c00873186b1|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-caption-input.hbs
|
||||
add|ember-template-lint|no-action|10|21|10|21|97ce84ec3c3e0aec9ad3d95414ffa3aa235d5ef3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-caption-input.hbs
|
||||
add|ember-template-lint|no-action|106|27|106|27|69d342e90e8b17545813ae908214a09cd8adbb4a|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-audio.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|160|29|160|29|97f6cf5d19825a64a958f2f80ed007ca2f6e0def|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-audio.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|106|20|106|20|f6b81930532691e5917b7a365ea0286aca41c410|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-audio.hbs
|
||||
add|ember-template-lint|require-input-label|115|20|115|20|6667d8ebc469e0d2ed3471a4b172410ce94e2895|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-audio.hbs
|
||||
add|ember-template-lint|require-input-label|118|20|118|20|f3ae4f72a8db3c91f535113a7471dba2b0ba74c9|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-audio.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|59|32|59|32|67bd02b65f7f92dc7f0a23f1d0821d694c12822a|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-audio.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|71|32|71|32|f7cffe175337549e4def0097056f57a9f4590c33|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-audio.hbs
|
||||
add|ember-template-lint|no-down-event-binding|62|25|62|25|a158be60fde14211f6abfbac329c94fa5a3b4a46|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-before-after.hbs
|
||||
add|ember-template-lint|require-input-label|54|16|54|16|98e790dc74c88df1b5eb2b212149a2fb0f9cf83c|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-before-after.hbs
|
||||
add|ember-template-lint|require-input-label|113|16|113|16|c610307bb78fba0a94b9f3f12f507cc37142f7b8|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-before-after.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|40|20|40|20|2376a21f7f520ed08dff9d123e45c14329d60537|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-before-after.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|48|20|48|20|ed3cb6ea286ecb0dcbf85a01fd434a8db45ba22d|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-before-after.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|69|24|69|24|fa6bca7cf3804e926c62628e15909bc57e409f83|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-before-after.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|93|24|93|24|3ac85fbe4dc6c41ea8f0ccbc5c52d31412fb6c7f|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-before-after.hbs
|
||||
add|ember-template-lint|style-concatenation|34|64|34|64|51cf172bdfd49494cf9561032700824034048fa1|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-before-after.hbs
|
||||
add|ember-template-lint|style-concatenation|38|20|38|20|e814b9aab6e7bf5d99c98e738bf7aa29b72d3bc2|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-before-after.hbs
|
||||
add|ember-template-lint|style-concatenation|65|65|65|65|beec7c7fb1c31e93a99d5d353d4b1175f5238749|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-before-after.hbs
|
||||
add|ember-template-lint|style-concatenation|121|61|121|61|beec7c7fb1c31e93a99d5d353d4b1175f5238749|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-before-after.hbs
|
||||
add|ember-template-lint|no-action|5|16|5|16|822c6a91d06436c87443aba1c5316874b4de7e93|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|no-action|6|18|6|18|40a9eaaf8ee3a30448bd8f5c51e4f57ad6f637cb|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|no-action|7|16|7|16|b029d6288b77b183c28684a537f2054da87763b7|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|no-action|8|14|8|14|45b936fe4ae520e86dc577dced7e4de626004fba|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|no-action|53|28|53|28|ee695277e2f7dac9fbe5570ebfbe9c738280a976|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|no-action|65|71|65|71|76726a13a086d82dab219df12e86db1773a9de32|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|no-action|66|85|66|85|bb78ad59bc384ea0de5e9459da9d85f1735ce141|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|no-action|67|38|67|38|3ad187464ff78253a0ea4dd17dcfcf0423f66864|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|no-action|78|24|78|24|3c723798059c71aedfa95cd70507bc0308728544|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|no-action|79|26|79|26|0dd30c3b295b8550ca0591cdf60cdc82dad4f868|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|no-down-event-binding|79|16|79|16|911ca011b4e567dddb2df874008ecfacae248d2d|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|require-input-label|72|12|72|12|f91038c1c6744efe51fa8a38debc4c012f4362b4|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|30|36|30|36|c072441cc8793a1dca82328ed637368158b1aa7c|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|42|32|42|32|08652917915a00a391c0881f39dca81d4f62a077|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-bookmark.hbs
|
||||
add|ember-template-lint|no-unused-block-params|1|0|1|0|fa339a96dc0cc3f7b984951073db51aef92c09b3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-button.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|64|160|64|160|645413ce9dfeb328b8259ae2209ac631aa30dc9d|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-callout.hbs
|
||||
add|ember-template-lint|no-triple-curlies|81|20|81|20|5fc0b56a3c059cf57b60432ba413d490074e3315|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-callout.hbs
|
||||
add|ember-template-lint|no-unused-block-params|1|0|1|0|8481ae69591188cc636eaae534ae8d328d95ef04|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-callout.hbs
|
||||
add|ember-template-lint|no-triple-curlies|167|23|167|23|9109f8baf6d3a4cce8e0ceb13f5bca99bdbe4004|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-email-cta.hbs
|
||||
add|ember-template-lint|no-unused-block-params|1|0|1|0|08ed50a248b697129b713403bcaf9549b027bd11|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-email-cta.hbs
|
||||
add|ember-template-lint|no-action|9|16|9|16|822c6a91d06436c87443aba1c5316874b4de7e93|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-email.hbs
|
||||
add|ember-template-lint|no-action|10|18|10|18|40a9eaaf8ee3a30448bd8f5c51e4f57ad6f637cb|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-email.hbs
|
||||
add|ember-template-lint|no-action|11|14|11|14|45b936fe4ae520e86dc577dced7e4de626004fba|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-email.hbs
|
||||
add|ember-template-lint|no-action|12|14|12|14|c27072dba50f6e36c62d708160adb2e96b8244e3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-email.hbs
|
||||
add|ember-template-lint|no-action|14|17|14|17|b562d5c8ffc834037fff995329aa4cd49218776b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-email.hbs
|
||||
add|ember-template-lint|no-action|26|22|26|22|5fef4f84e130c50100035200a6996e594f8c4a9a|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-email.hbs
|
||||
add|ember-template-lint|no-action|27|21|27|21|31c56395c52506a01b65825ef8bdb3b71a95c36b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-email.hbs
|
||||
add|ember-template-lint|no-action|28|20|28|20|0dc0b984b33c3c1842d08f078270473b16371137|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-email.hbs
|
||||
add|ember-template-lint|no-action|29|29|29|29|97ce84ec3c3e0aec9ad3d95414ffa3aa235d5ef3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-email.hbs
|
||||
add|ember-template-lint|no-triple-curlies|38|11|38|11|9109f8baf6d3a4cce8e0ceb13f5bca99bdbe4004|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-email.hbs
|
||||
add|ember-template-lint|no-action|5|16|5|16|822c6a91d06436c87443aba1c5316874b4de7e93|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed.hbs
|
||||
add|ember-template-lint|no-action|6|18|6|18|40a9eaaf8ee3a30448bd8f5c51e4f57ad6f637cb|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed.hbs
|
||||
add|ember-template-lint|no-action|7|16|7|16|b029d6288b77b183c28684a537f2054da87763b7|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed.hbs
|
||||
add|ember-template-lint|no-action|8|14|8|14|45b936fe4ae520e86dc577dced7e4de626004fba|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed.hbs
|
||||
add|ember-template-lint|no-action|33|28|33|28|ee695277e2f7dac9fbe5570ebfbe9c738280a976|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed.hbs
|
||||
add|ember-template-lint|no-action|46|71|46|71|76726a13a086d82dab219df12e86db1773a9de32|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed.hbs
|
||||
add|ember-template-lint|no-action|47|85|47|85|bb78ad59bc384ea0de5e9459da9d85f1735ce141|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed.hbs
|
||||
add|ember-template-lint|no-action|48|38|48|38|3ad187464ff78253a0ea4dd17dcfcf0423f66864|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed.hbs
|
||||
add|ember-template-lint|no-action|59|24|59|24|3c723798059c71aedfa95cd70507bc0308728544|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed.hbs
|
||||
add|ember-template-lint|no-action|60|26|60|26|0dd30c3b295b8550ca0591cdf60cdc82dad4f868|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed.hbs
|
||||
add|ember-template-lint|no-down-event-binding|60|16|60|16|911ca011b4e567dddb2df874008ecfacae248d2d|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed.hbs
|
||||
add|ember-template-lint|require-iframe-title|25|20|25|20|f8c77eef3da5ea547d640216997d9ac18f0c71d9|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed.hbs
|
||||
add|ember-template-lint|require-input-label|53|12|53|12|f5eb68e50039dcad089ce49b17e85a32d7ebe2cf|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed.hbs
|
||||
add|ember-template-lint|no-action|38|31|38|31|47a65e4376498373f3974d66dc59bb74d0528bb0|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-file.hbs
|
||||
add|ember-template-lint|no-action|44|31|44|31|06b0c88729efcb3c00abca78472bc02f561ecccd|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-file.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|92|29|92|29|ae3be0caab4a081a92fdd880dc74321cd6d780e0|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-file.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|38|24|38|24|046ed81ef8af3995371ed6258c24c8df23a9d064|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-file.hbs
|
||||
add|ember-template-lint|no-passed-in-event-handlers|44|24|44|24|675ef008ec0d64643d6a6b1bc6df36bc23709102|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-file.hbs
|
||||
add|ember-template-lint|no-unused-block-params|1|0|1|0|156821bab3e9e1e1fe662b997173fa21ba6ec8ff|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-file.hbs
|
||||
add|ember-template-lint|no-action|6|16|6|16|822c6a91d06436c87443aba1c5316874b4de7e93|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|7|18|7|18|40a9eaaf8ee3a30448bd8f5c51e4f57ad6f637cb|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|8|14|8|14|45b936fe4ae520e86dc577dced7e4de626004fba|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|16|14|16|14|d7c2ceb3a1005e005da2fed8a565d2ce929af5a5|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|17|16|17|16|df8466847c2a74b5331ee6768c4737c571712edd|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|24|23|24|23|79ce5098a6bd76f352070aea4492093460743b0b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|25|25|25|25|6a4072f62408f1964b1977ce4462b1d61537b548|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|26|25|26|25|b74e8f2d4504aa944639bd7a413292fae5cb209a|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|27|18|27|18|bd362a31233c52421c9db737d3d10b42ddefd6bd|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|50|110|50|110|ede1c2002a22780dcb2e86e6b07196b87f509a40|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|72|143|72|143|69e5f19c951bf70e9e7eaa72ea2b784ab97c3dff|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|90|40|90|40|8b347765bd019e65941a73b8132dbd3cdcf3fad5|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|99|52|99|52|76e4cca6ff2f817714404678f70aca9e64f58f7f|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|106|20|106|20|ee695277e2f7dac9fbe5570ebfbe9c738280a976|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|41|36|41|36|c32a307c8857ccf119696235e8fe2858687dbfa2|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-gallery.hbs
|
||||
add|ember-template-lint|no-action|31|26|31|26|520ad6b38441a28ec45d4bd8daaccf36ee50614c|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-header.hbs
|
||||
add|ember-template-lint|no-action|32|33|32|33|fc61d9c3c77f842c8389d4abb63e7a467a596c40|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-header.hbs
|
||||
add|ember-template-lint|no-action|40|26|40|26|eac3d5c693a72de84fab7b4dc2b3956588248d1e|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-header.hbs
|
||||
add|ember-template-lint|no-action|41|33|41|33|7352d51cbb767ffbdc8fd829b0013a0ca2d0c809|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-header.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|91|97|91|97|38e55d655123bc526549b4920480a65835f9890b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-header.hbs
|
||||
add|ember-template-lint|no-triple-curlies|172|46|172|46|f85a33dff9dc1a580ba55ca8e6f73bf1cd671f69|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-header.hbs
|
||||
add|ember-template-lint|no-triple-curlies|174|53|174|53|8aa6418d946a4a6de4a9f7e963860014b0dd68ee|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-header.hbs
|
||||
add|ember-template-lint|no-unused-block-params|1|0|1|0|e25f7866ab4ee682b08edf3b29a1351e4079538e|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-header.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|91|32|91|32|38e55d655123bc526549b4920480a65835f9890b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-header.hbs
|
||||
add|ember-template-lint|style-concatenation|24|90|24|90|17cca11a71b4921b82aa55b4d5772e6801e7d001|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-header.hbs
|
||||
add|ember-template-lint|style-concatenation|171|90|171|90|17cca11a71b4921b82aa55b4d5772e6801e7d001|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-header.hbs
|
||||
add|ember-template-lint|no-action|9|16|9|16|822c6a91d06436c87443aba1c5316874b4de7e93|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-html.hbs
|
||||
add|ember-template-lint|no-action|10|18|10|18|40a9eaaf8ee3a30448bd8f5c51e4f57ad6f637cb|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-html.hbs
|
||||
add|ember-template-lint|no-action|11|14|11|14|45b936fe4ae520e86dc577dced7e4de626004fba|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-html.hbs
|
||||
add|ember-template-lint|no-action|12|14|12|14|c27072dba50f6e36c62d708160adb2e96b8244e3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-html.hbs
|
||||
add|ember-template-lint|no-action|14|17|14|17|b562d5c8ffc834037fff995329aa4cd49218776b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-html.hbs
|
||||
add|ember-template-lint|no-action|22|20|22|20|5fef4f84e130c50100035200a6996e594f8c4a9a|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-html.hbs
|
||||
add|ember-template-lint|no-quoteless-attributes|20|23|20|23|20c117eaeeb511ade7f0ac922eb5dd4fa8e226df|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-html.hbs
|
||||
add|ember-template-lint|no-quoteless-attributes|21|26|21|26|20c117eaeeb511ade7f0ac922eb5dd4fa8e226df|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-html.hbs
|
||||
add|ember-template-lint|no-triple-curlies|25|47|25|47|8336bb2ae349079452ea98bf142c3e7dd649c549|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-html.hbs
|
||||
add|ember-template-lint|no-action|7|16|7|16|822c6a91d06436c87443aba1c5316874b4de7e93|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|8|18|8|18|40a9eaaf8ee3a30448bd8f5c51e4f57ad6f637cb|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|9|14|9|14|45b936fe4ae520e86dc577dced7e4de626004fba|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|13|16|13|16|b029d6288b77b183c28684a537f2054da87763b7|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|24|19|24|19|79b97ed19b0c3220eeee237e671cc380b8b10d6f|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|25|18|25|18|267fef7268487857463625638157e369c3ad9344|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|32|21|32|21|51bd4f460e5c86c46ccecbcb52638a43f8ca9b07|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|33|24|33|24|6c35771c823fe159562109db4dbe1dec3cfe82f0|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|34|22|34|22|0791eeb5a6fa727f27b18b9cbba690b3e7633908|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|64|150|64|150|69e5f19c951bf70e9e7eaa72ea2b784ab97c3dff|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|105|19|105|19|79b97ed19b0c3220eeee237e671cc380b8b10d6f|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|106|18|106|18|267fef7268487857463625638157e369c3ad9344|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|115|16|115|16|7ce20fdf2216b0a53e1954f99a03972382f14635|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|116|16|116|16|5b7ad6ecbae8b6379d42a3c7f343485cf7cecded|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|simple-unless|37|41|37|41|ca177565b6e1c5a6175982047708732f5dde7e59|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image.hbs
|
||||
add|ember-template-lint|no-action|9|17|9|17|2ecb8ec2db47e693d292cac348977b4c83e5b0a4|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|10|17|10|17|b562d5c8ffc834037fff995329aa4cd49218776b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|11|16|11|16|822c6a91d06436c87443aba1c5316874b4de7e93|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|12|18|12|18|40a9eaaf8ee3a30448bd8f5c51e4f57ad6f637cb|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|13|14|13|14|45b936fe4ae520e86dc577dced7e4de626004fba|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|14|14|14|14|c27072dba50f6e36c62d708160adb2e96b8244e3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|22|23|22|23|9ab4bf8e456620319065ed4f13de2d91f7acfb2c|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|23|22|23|22|d421a73a0ba7c7c037ba76cf8d94e18ccc428ad3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|24|33|24|33|a7539f813f4a3b95a771a7a8f372d651c5352ba2|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|29|26|29|26|98bcc5096c8ee474d35bace3f1987716a5cb0901|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|36|38|36|38|79b18b69be48cb15fe020b57db44cb55f0d54cc3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|44|23|44|23|3a6fe226433a067ecc30866b08c5e55e6bb93059|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|45|22|45|22|3f794b4f3e5a21abbe19fbf15aba586928af4f54|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|46|33|46|33|a2d6115b0418698f5ccb097023a3039b9c0e8dfa|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|64|32|64|32|fcd38782431c3a033d606fe06875297ad0fbf5a8|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|65|30|65|30|d819379a4dcb9a1425d0998fa83c0b738b257832|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|77|93|77|93|e64e7ee5049e28719f11468dfc3d25a51cbd751c|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-markdown.hbs
|
||||
add|ember-template-lint|no-action|42|193|42|193|0791eeb5a6fa727f27b18b9cbba690b3e7633908|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-product.hbs
|
||||
add|ember-template-lint|no-action|71|114|71|114|69e5f19c951bf70e9e7eaa72ea2b784ab97c3dff|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-product.hbs
|
||||
add|ember-template-lint|no-action|35|30|35|30|ea98008468a2546cc4db5918175d339c105557e7|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-toggle.hbs
|
||||
add|ember-template-lint|no-action|36|29|36|29|31c56395c52506a01b65825ef8bdb3b71a95c36b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-toggle.hbs
|
||||
add|ember-template-lint|no-action|37|28|37|28|0dc0b984b33c3c1842d08f078270473b16371137|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-toggle.hbs
|
||||
add|ember-template-lint|no-action|50|30|50|30|2568a6bbfc0f9b98fc443036900050aecbe849a1|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-toggle.hbs
|
||||
add|ember-template-lint|no-action|51|29|51|29|31c56395c52506a01b65825ef8bdb3b71a95c36b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-toggle.hbs
|
||||
add|ember-template-lint|no-action|52|28|52|28|0dc0b984b33c3c1842d08f078270473b16371137|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-toggle.hbs
|
||||
add|ember-template-lint|no-action|53|37|53|37|89578bd62979ce1da14325c918197f524cd73d6b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-toggle.hbs
|
||||
add|ember-template-lint|no-triple-curlies|63|24|63|24|b4fdf1937520db3f14f6f93ec84f03b7653414c0|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-toggle.hbs
|
||||
add|ember-template-lint|no-triple-curlies|72|16|72|16|9197e9050977ef47965e1b6e8d99bee542a7c559|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-toggle.hbs
|
||||
add|ember-template-lint|no-unused-block-params|1|0|1|0|c81c511a566ffd14b5570b2a72d89edb5ec390a0|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-toggle.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|153|105|153|105|3a10a7d94665ddc129c8ad4d64d50a4d9b5c9e34|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-video.hbs
|
||||
add|ember-template-lint|require-input-label|84|24|84|24|6667d8ebc469e0d2ed3471a4b172410ce94e2895|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-video.hbs
|
||||
add|ember-template-lint|require-input-label|87|24|87|24|f3ae4f72a8db3c91f535113a7471dba2b0ba74c9|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-video.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|39|20|39|20|26bfc8a4d99f4dd6f6340dc759411bc91c36ef9f|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-video.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|153|40|153|40|3a10a7d94665ddc129c8ad4d64d50a4d9b5c9e34|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-video.hbs
|
||||
add|ember-template-lint|simple-unless|36|37|36|37|ca177565b6e1c5a6175982047708732f5dde7e59|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-video.hbs
|
||||
add|ember-template-lint|simple-unless|41|26|41|26|ca177565b6e1c5a6175982047708732f5dde7e59|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-video.hbs
|
||||
add|ember-template-lint|no-action|36|45|36|45|44bab019144785f1c79a5928943790ae6c687106|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card.hbs
|
||||
add|ember-template-lint|no-down-event-binding|36|33|36|33|a158be60fde14211f6abfbac329c94fa5a3b4a46|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card.hbs
|
||||
add|ember-template-lint|style-concatenation|2|24|2|24|2d0ef0aab274628106cd50bf3f1f51a54a0de24b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card.hbs
|
||||
add|ember-template-lint|style-concatenation|5|9|5|9|3237d3db72009ef7baa5ebfa8d525fa5fd81262c|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card.hbs
|
||||
add|ember-template-lint|no-action|11|18|11|18|8e38f1d73c212f5fe08d155a9ba83f7dc30e38a8|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|12|19|12|19|7ca45569cf222ba15db12d11063b92224038d2e3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|13|25|13|25|8b1ee860eec1b61ced23c00e743046d9cad53a33|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|14|14|14|14|98c33bc8a7a032179cbbc279c04e635a4ac4bce3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|24|14|24|14|98c33bc8a7a032179cbbc279c04e635a4ac4bce3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|34|16|34|16|5b7ad6ecbae8b6379d42a3c7f343485cf7cecded|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|56|32|56|32|6ef6aab7e798fbacc42ce92a4f56b747f8037908|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|58|15|58|15|76f3c0a0e763a852707dbd87fcc01fba0bea5e4f|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|71|28|71|28|6ef6aab7e798fbacc42ce92a4f56b747f8037908|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|72|21|72|21|dc1cf61f19b8aed1edcea87cfc8b968c229f9c93|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|73|27|73|27|9195baebc5cc53f4be05b0aa955ab8e22a3dd099|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|83|28|83|28|6ef6aab7e798fbacc42ce92a4f56b747f8037908|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|84|21|84|21|dc1cf61f19b8aed1edcea87cfc8b968c229f9c93|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|85|27|85|27|9195baebc5cc53f4be05b0aa955ab8e22a3dd099|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|100|21|100|21|8cda060f630c81587747ae63bca22ba0479a417d|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|101|23|101|23|bbab5cd293cf71a2af917d6f84d566c42ff313b1|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|102|23|102|23|071fd2886b454eafc96ec0cc9a7ef01503a2688d|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|103|25|103|25|7db56e6734f46e63e91445e399e1a3a602c31636|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|106|23|106|23|0401c18c6ce3fe2477e645d5923710feb6491de3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|107|25|107|25|74128407cfa61ff0257028d0266c841fdebcdcb1|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|108|21|108|21|24b49ddef48d36b91530840b7831b973e37465b8|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|109|23|109|23|61683de5dc0f30ebbc34e67eba5fc99df939a892|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|111|36|111|36|bdce7d080c258a9867f07eb0e47da8248ae2f1ef|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|112|36|112|36|faef61191a8b49a36a9c0951e74d2a041f9e2912|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|113|34|113|34|4438907afa3f75452364ec151c6f926829541538|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|114|30|114|30|5e9930951aa23775756479da419025121c272239|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|126|30|126|30|5cd4dd9d1fe33c1ecd959992079dda2516b1f4f5|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-editor.hbs
|
||||
add|ember-template-lint|no-action|5|17|5|17|4db7a2d3c266d607f2440d7dc4d64625c94f7298|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-link-input.hbs
|
||||
add|ember-template-lint|no-action|6|19|6|19|18a04ff082ca003ee43fe89db6e45bbef426200d|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-link-input.hbs
|
||||
add|ember-template-lint|no-action|10|85|10|85|fdbf1af52da481e21da206e8ea385c625555f753|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-link-input.hbs
|
||||
add|ember-template-lint|no-down-event-binding|6|9|6|9|e82f6aa36fd44bb3dccff09770613eee19380f9b|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-link-input.hbs
|
||||
add|ember-template-lint|require-input-label|1|0|1|0|8e9e65247c8b4d7f421d2280af07a9ff39a8a501|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-link-input.hbs
|
||||
add|ember-template-lint|no-action|11|16|11|16|581ab008eb88e1bc0a0900599fc2b22da7bd0501|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-link-toolbar.hbs
|
||||
add|ember-template-lint|no-action|22|16|22|16|44aef00d7b12f6b9128d3e6f8debe3d2d7b3194d|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-link-toolbar.hbs
|
||||
add|ember-template-lint|no-args-paths|4|14|4|14|9ba62e613990ee354745eb5b05acb24e54e55302|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-media-selector.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|23|66|23|66|12fb0d3e1b679f781b065226f36d8afdcd5cedf0|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-menu-content.hbs
|
||||
add|ember-template-lint|simple-unless|4|79|4|79|5d9203e10703908836b537481cf012f167ded239|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-menu-content.hbs
|
||||
add|ember-template-lint|no-action|2|174|2|174|c69c5c97cc0db96f1040900aa3885fa3da5be7c2|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-plus-menu.hbs
|
||||
add|ember-template-lint|no-action|14|25|14|25|dcfe39b0f6e42f3e4cb5138223798067d7a0a2b7|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-plus-menu.hbs
|
||||
add|ember-template-lint|no-action|15|18|15|18|8e38f1d73c212f5fe08d155a9ba83f7dc30e38a8|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-text-replacement-html-input.hbs
|
||||
add|ember-template-lint|no-action|16|14|16|14|98c33bc8a7a032179cbbc279c04e635a4ac4bce3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-text-replacement-html-input.hbs
|
||||
add|ember-template-lint|no-action|25|14|25|14|98c33bc8a7a032179cbbc279c04e635a4ac4bce3|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-text-replacement-html-input.hbs
|
||||
add|ember-template-lint|no-action|34|16|34|16|5b7ad6ecbae8b6379d42a3c7f343485cf7cecded|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-text-replacement-html-input.hbs
|
||||
add|ember-template-lint|no-action|3|13|3|13|288404c640071968cb22e09f4b5f2e815e0f03ea|1658102400000|1668474000000|1673658000000|app/templates/settings/integration/webhooks/edit.hbs
|
||||
add|ember-template-lint|no-action|4|11|4|11|831dd12209c22868d0614c65417281fda7991f09|1658102400000|1668474000000|1673658000000|app/templates/settings/integration/webhooks/edit.hbs
|
||||
add|ember-template-lint|no-action|3|13|3|13|288404c640071968cb22e09f4b5f2e815e0f03ea|1658102400000|1668474000000|1673658000000|app/templates/settings/integration/webhooks/new.hbs
|
||||
add|ember-template-lint|no-action|4|11|4|11|831dd12209c22868d0614c65417281fda7991f09|1658102400000|1668474000000|1673658000000|app/templates/settings/integration/webhooks/new.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|3|44|3|44|a7f0566c430150bae4153e0dfb489a218bdeb8a4|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed/nft.hbs
|
||||
add|ember-template-lint|require-valid-alt-text|8|20|8|20|9d0c591086dc9139ff38a7b385c3367a83438786|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-embed/nft.hbs
|
||||
add|ember-template-lint|require-input-label|10|12|10|12|8c3c0ea315ff4da828363989a45fa11256a78796|1658102400000|1668474000000|1673658000000|lib/koenig-editor/addon/components/koenig-card-image/selector-tenor.hbs
|
||||
remove|ember-template-lint|no-down-event-binding|5|13|5|13|a158be60fde14211f6abfbac329c94fa5a3b4a46|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs
|
||||
remove|ember-template-lint|no-invalid-interactive|5|8|5|8|94046126dd697b080aa16222b00a3d5a545ee001|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs
|
||||
remove|ember-template-lint|no-invalid-interactive|6|8|6|8|94046126dd697b080aa16222b00a3d5a545ee001|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs
|
||||
remove|ember-template-lint|no-passed-in-event-handlers|26|12|26|12|4ad1176b9a4bb23d79114735b478240166113502|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs
|
||||
remove|ember-template-lint|no-passed-in-event-handlers|28|12|28|12|7ffe0a55c81efcfcd46880a930fa69bee73835b3|1658102400000|1668474000000|1673658000000|app/components/gh-koenig-editor.hbs
|
||||
remove|ember-template-lint|no-whitespace-within-word|80|20|80|20|5ec988f82d06bb76be61f23dbb9cb65f94326f34|1658102400000|1668474000000|1673658000000|app/components/modal-markdown-help.hbs
|
8
ghost/admin/.lint-todorc.js
Normal file
8
ghost/admin/.lint-todorc.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
'ember-template-lint': {
|
||||
daysToDecay: {
|
||||
warn: 120,
|
||||
error: 180,
|
||||
}
|
||||
}
|
||||
};
|
9
ghost/admin/.template-lintrc.js
Normal file
9
ghost/admin/.template-lintrc.js
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
extends: "recommended",
|
||||
|
||||
rules: {
|
||||
'no-forbidden-elements': ['meta', 'html', 'script'],
|
||||
'no-implicit-this': {allow: ['noop', 'now', 'site-icon-style', 'accent-color-background']},
|
||||
'no-inline-styles': false
|
||||
}
|
||||
};
|
3
ghost/admin/.watchmanconfig
Normal file
3
ghost/admin/.watchmanconfig
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"ignore_dirs": ["tmp", "dist"]
|
||||
}
|
70
ghost/admin/Gruntfile.js
Normal file
70
ghost/admin/Gruntfile.js
Normal file
@ -0,0 +1,70 @@
|
||||
/* eslint-env node */
|
||||
/* eslint-disable object-shorthand */
|
||||
'use strict';
|
||||
|
||||
module.exports = function (grunt) {
|
||||
// Find all of the task which start with `grunt-` and load them, rather than explicitly declaring them all
|
||||
require('matchdep').filterDev(['grunt-*', '!grunt-cli']).forEach(grunt.loadNpmTasks);
|
||||
|
||||
grunt.initConfig({
|
||||
clean: {
|
||||
built: {
|
||||
src: ['dist/**']
|
||||
},
|
||||
dependencies: {
|
||||
src: ['node_modules/**']
|
||||
},
|
||||
tmp: {
|
||||
src: ['tmp/**']
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
csscomb: {
|
||||
files: ['app/styles/**/*.css'],
|
||||
tasks: ['shell:csscombfix']
|
||||
}
|
||||
},
|
||||
|
||||
shell: {
|
||||
'npm-install': {
|
||||
command: 'yarn install'
|
||||
},
|
||||
|
||||
ember: {
|
||||
command: function (mode) {
|
||||
let liveReloadBaseUrl = grunt.option('live-reload-base-url') || '/ghost/';
|
||||
|
||||
switch (mode) {
|
||||
case 'prod':
|
||||
return 'npm run build -- --environment=production --silent';
|
||||
case 'dev':
|
||||
return 'npm run build';
|
||||
case 'watch':
|
||||
return `npm run start -- --live-reload-base-url=${liveReloadBaseUrl} --live-reload-port=4201`;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
csscombfix: {
|
||||
command: 'csscomb -c app/styles/csscomb.json -v app/styles'
|
||||
},
|
||||
|
||||
csscomblint: {
|
||||
command: 'csscomb -c app/styles/csscomb.json -lv app/styles'
|
||||
},
|
||||
|
||||
test: {
|
||||
command: 'npm test'
|
||||
},
|
||||
|
||||
options: {
|
||||
preferLocal: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.registerTask('init', 'Install the admin dependencies',
|
||||
['shell:npm-install']
|
||||
);
|
||||
};
|
22
ghost/admin/LICENSE
Normal file
22
ghost/admin/LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
Copyright (c) 2013-2022 Ghost Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
68
ghost/admin/README.md
Normal file
68
ghost/admin/README.md
Normal file
@ -0,0 +1,68 @@
|
||||
# Ghost-Admin
|
||||
|
||||
![](https://github.com/TryGhost/Admin/workflows/Test%20Suite/badge.svg?branch=main)
|
||||
|
||||
This is the home of Ember.js based admin app that ships with [Ghost](https://github.com/tryghost/ghost).
|
||||
|
||||
**Do you want to set up a Ghost blog?** Check the [getting started guide](https://ghost.org/docs/introduction/)
|
||||
|
||||
**Do you want to modify or contribute to Ghost-Admin?** Please read how to [install from source](https://ghost.org/docs/install/source/) and swing by our [forum](https://forum.ghost.org) if you need any help 😄
|
||||
|
||||
## Running tests
|
||||
|
||||
Build and run tests once:
|
||||
|
||||
```bash
|
||||
TZ=UTC yarn test
|
||||
```
|
||||
_Note the `TZ=UTC` environment variable which is currently required to get tests working if your system timezone doesn't match UTC._
|
||||
|
||||
If you are serving the admin app (e.g., when running `yarn serve`, or when running `yarn dev` in the main Ghost project), you can also run the tests in your browser by going to http://localhost:4200/tests.
|
||||
|
||||
This has the additional benefit that you can use `await this.pauseTest()` in your tests to temporarily pause tests (best to also add `this.timeout(0);` to avoid timeouts). This allows you to inspect the DOM in your browser to debug tests. You can resume tests by running `resumeTest()` in your browser console.
|
||||
|
||||
[More information](https://guides.emberjs.com/v3.28.0/testing/testing-application/#toc_debugging-your-tests)
|
||||
|
||||
|
||||
### Writing tests
|
||||
|
||||
When writing tests and not using the `http://localhost:4200/tests` browser tests, it can be easier to have a separate watching build that builds the project for the test environment (this drastically reduces the time you have to wait when running tests):
|
||||
|
||||
```bash
|
||||
yarn build --environment=test -w -o="dist-test"
|
||||
```
|
||||
|
||||
After that, you can easily run tests locally:
|
||||
|
||||
Run all tests:
|
||||
|
||||
```bash
|
||||
TZ=UTC yarn test 1 --path="dist-test"
|
||||
```
|
||||
|
||||
To have a cleaner output:
|
||||
|
||||
```bash
|
||||
TZ=UTC yarn test 1 --reporter dot --path="dist-test"
|
||||
```
|
||||
|
||||
This shows a dot (`.`) for every successful test, and `F` for every failed test. At the end, it will only show the output of the failed tests.
|
||||
|
||||
To run a specific test file:
|
||||
```bash
|
||||
TZ=UTC yarn test 1 --reporter dot --path="dist-test" -mp=tests/acceptance/settings/newsletters-test.js
|
||||
```
|
||||
_Hint: you can easily copy the path of a test in VSCode by right clicking on the test file and choosing `Copy Relative Path`._
|
||||
|
||||
To have a full list of the available options, run
|
||||
```bash
|
||||
ember exam --help
|
||||
```
|
||||
|
||||
## Have a bug or issue?
|
||||
|
||||
Bugs and issues (even if they only affect the admin app) should be opened on the core [Ghost](https://github.com/tryghost/ghost/issues) repository.
|
||||
|
||||
# Copyright & License
|
||||
|
||||
Copyright (c) 2013-2022 Ghost Foundation - Released under the [MIT license](LICENSE). Ghost and the Ghost Logo are trademarks of Ghost Foundation Ltd. Please see our [trademark policy](https://ghost.org/trademark/) for info on acceptable usage.
|
9
ghost/admin/SECURITY.md
Normal file
9
ghost/admin/SECURITY.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Reporting Security Vulnerabilities
|
||||
|
||||
Potential security vulnerabilities can be reported directly us at `security@ghost.org`. The Ghost Security Team communicates privately and works in a secured, isolated repository for tracking, testing, and resolving security-related issues.
|
||||
|
||||
The full, up-to-date details of our security policy and procedure can always be found in our documentation:
|
||||
|
||||
https://ghost.org/docs/security/
|
||||
|
||||
Please refer to this before emailing us. Thanks for helping make Ghost safe for everyone 🙏.
|
30
ghost/admin/app/README.md
Normal file
30
ghost/admin/app/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Ghost Admin App
|
||||
|
||||
Ember.js application used as a client-side admin for the [Ghost](http://ghost.org) blogging platform. This readme is a work in progress guide aimed at explaining the specific nuances of the Ghost Ember app to contributors whose main focus is on this side of things.
|
||||
|
||||
|
||||
## CSS
|
||||
|
||||
We use pure CSS, which is pre-processed for backwards compatibility by [Myth](http://myth.io). We do not follow any strict CSS framework, however our general style is pretty similar to BEM.
|
||||
|
||||
Styles are primarily broken up into 4 main categories:
|
||||
|
||||
* **Patterns** - are base level visual styles for HTML elements (eg. Buttons)
|
||||
* **Components** - are groups of patterns used to create a UI component (eg. Modals)
|
||||
* **Layouts** - are groups of components used to create application screens (eg. Settings)
|
||||
|
||||
All of these separate files are subsequently imported and compiled in `app.css`.
|
||||
|
||||
|
||||
## Front End Standards
|
||||
|
||||
* 4 spaces for HTML & CSS indentation. Never tabs.
|
||||
* Double quotes only, never single quotes.
|
||||
* Use tags and elements appropriate for an HTML5 doctype (including self-closing tags)
|
||||
* Adhere to the [Recess CSS](http://markdotto.com/2011/11/29/css-property-order/) property order.
|
||||
* Always a space after a property's colon (.e.g, display: block; and not display:block;).
|
||||
* End all lines with a semi-colon.
|
||||
* For multiple, comma-separated selectors, place each selector on its own line.
|
||||
* Use js- prefixed classes for JavaScript hooks into the DOM, and never use these in CSS as per [Slightly Obtrusive JavaSript](http://ozmm.org/posts/slightly_obtrusive_javascript.html)
|
||||
* Avoid over-nesting CSS. Never nest more than 3 levels deep.
|
||||
* Use comments to explain "why" not "what" (Good: This requires a z-index in order to appear above mobile navigation. Bad: This is a thing which is always on top!)
|
16
ghost/admin/app/adapters/api-key.js
Normal file
16
ghost/admin/app/adapters/api-key.js
Normal file
@ -0,0 +1,16 @@
|
||||
import ApplicationAdapter from './application';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class ApiKey extends ApplicationAdapter {
|
||||
queryRecord(store, type, query) {
|
||||
if (!query || query.id !== 'me') {
|
||||
return super.queryRecord(...arguments);
|
||||
}
|
||||
|
||||
let url = `${this.buildURL('users', 'me')}token/`;
|
||||
return this.ajax(url, 'GET', {data: {}}).then((data) => {
|
||||
return data;
|
||||
});
|
||||
}
|
||||
}
|
9
ghost/admin/app/adapters/application.js
Normal file
9
ghost/admin/app/adapters/application.js
Normal file
@ -0,0 +1,9 @@
|
||||
import EmbeddedRelationAdapter from 'ghost-admin/adapters/embedded-relation-adapter';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class Application extends EmbeddedRelationAdapter {
|
||||
shouldBackgroundReloadRecord() {
|
||||
return false;
|
||||
}
|
||||
}
|
44
ghost/admin/app/adapters/base.js
Normal file
44
ghost/admin/app/adapters/base.js
Normal file
@ -0,0 +1,44 @@
|
||||
import AjaxServiceSupport from 'ember-ajax/mixins/ajax-support';
|
||||
import RESTAdapter from '@ember-data/adapter/rest';
|
||||
import ghostPaths from 'ghost-admin/utils/ghost-paths';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {underscore} from '@ember/string';
|
||||
|
||||
export default RESTAdapter.extend(AjaxServiceSupport, {
|
||||
host: window.location.origin,
|
||||
namespace: ghostPaths().apiRoot.slice(1),
|
||||
|
||||
session: service(),
|
||||
|
||||
shouldBackgroundReloadRecord() {
|
||||
return false;
|
||||
},
|
||||
|
||||
query(store, type, query) {
|
||||
let id;
|
||||
|
||||
if (query.id) {
|
||||
id = query.id;
|
||||
delete query.id;
|
||||
}
|
||||
|
||||
return this.ajax(this.buildURL(type.modelName, id), 'GET', {data: query});
|
||||
},
|
||||
|
||||
pathForType() {
|
||||
const type = this._super(...arguments);
|
||||
return underscore(type);
|
||||
},
|
||||
|
||||
buildURL() {
|
||||
// Ensure trailing slashes
|
||||
let url = this._super(...arguments);
|
||||
let parsedUrl = new URL(url);
|
||||
|
||||
if (!parsedUrl.pathname.endsWith('/')) {
|
||||
parsedUrl.pathname += '/';
|
||||
}
|
||||
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
});
|
15
ghost/admin/app/adapters/custom-theme-setting-list.js
Normal file
15
ghost/admin/app/adapters/custom-theme-setting-list.js
Normal file
@ -0,0 +1,15 @@
|
||||
import ApplicationAdapter from 'ghost-admin/adapters/application';
|
||||
|
||||
export default class CustomThemeSettingListAdapter extends ApplicationAdapter {
|
||||
// we use `custom-theme-setting-list` model as a workaround for saving all
|
||||
// custom theme setting records in one request so it uses the base model url
|
||||
pathForType() {
|
||||
return 'custom_theme_settings';
|
||||
}
|
||||
|
||||
// there's no custom theme setting creation
|
||||
// newListModel.save() acts as an overall update request so force a PUT
|
||||
createRecord(store, type, snapshot) {
|
||||
return this.saveRecord(store, type, snapshot, {method: 'PUT'}, 'createRecord');
|
||||
}
|
||||
}
|
14
ghost/admin/app/adapters/email.js
Normal file
14
ghost/admin/app/adapters/email.js
Normal file
@ -0,0 +1,14 @@
|
||||
import ApplicationAdapter from './application';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class Email extends ApplicationAdapter {
|
||||
retry(model) {
|
||||
let url = `${this.buildURL('email', model.get('id'))}retry/`;
|
||||
|
||||
return this.ajax(url, 'PUT', {data: {}}).then((data) => {
|
||||
this.store.pushPayload(data);
|
||||
return model;
|
||||
});
|
||||
}
|
||||
}
|
145
ghost/admin/app/adapters/embedded-relation-adapter.js
Normal file
145
ghost/admin/app/adapters/embedded-relation-adapter.js
Normal file
@ -0,0 +1,145 @@
|
||||
import BaseAdapter from 'ghost-admin/adapters/base';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import {get} from '@ember/object';
|
||||
import {isNone} from '@ember/utils';
|
||||
import {underscore} from '@ember/string';
|
||||
|
||||
// EmbeddedRelationAdapter will augment the query object in calls made to
|
||||
// DS.Store#findRecord, findAll, query, and queryRecord with the correct "includes"
|
||||
// (?include=relatedType) by introspecting on the provided subclass of the DS.Model.
|
||||
// In cases where there is no query object (DS.Model#save, or simple finds) the URL
|
||||
// that is built will be augmented with ?include=... where appropriate.
|
||||
//
|
||||
// Example:
|
||||
// If a model has an embedded hasMany relation, the related type will be included:
|
||||
// roles: DS.hasMany('role', { embedded: 'always' }) => ?include=roles
|
||||
|
||||
@classic
|
||||
export default class EmbeddedRelationAdapter extends BaseAdapter {
|
||||
find(store, type, id, snapshot) {
|
||||
return this.ajax(this.buildIncludeURL(store, type.modelName, id, snapshot, 'find'), 'GET');
|
||||
}
|
||||
|
||||
findRecord(store, type, id, snapshot) {
|
||||
return this.ajax(this.buildIncludeURL(store, type.modelName, id, snapshot, 'findRecord'), 'GET');
|
||||
}
|
||||
|
||||
findAll(store, type, sinceToken) {
|
||||
let query, url;
|
||||
|
||||
if (sinceToken) {
|
||||
query = {since: sinceToken};
|
||||
}
|
||||
|
||||
url = this.buildIncludeURL(store, type.modelName, null, null, 'findAll');
|
||||
|
||||
return this.ajax(url, 'GET', {data: query});
|
||||
}
|
||||
|
||||
query(store, type, query) {
|
||||
return super.query(store, type, this.buildQuery(store, type.modelName, query));
|
||||
}
|
||||
|
||||
queryRecord(store, type, query) {
|
||||
return super.queryRecord(store, type, this.buildQuery(store, type.modelName, query));
|
||||
}
|
||||
|
||||
createRecord(store, type, snapshot) {
|
||||
return this.saveRecord(store, type, snapshot, {method: 'POST'}, 'createRecord');
|
||||
}
|
||||
|
||||
updateRecord(store, type, snapshot) {
|
||||
let options = {
|
||||
method: 'PUT',
|
||||
id: get(snapshot, 'id')
|
||||
};
|
||||
|
||||
return this.saveRecord(store, type, snapshot, options, 'updateRecord');
|
||||
}
|
||||
|
||||
saveRecord(store, type, snapshot, options, requestType) {
|
||||
let _options = options || {};
|
||||
let url = this.buildIncludeURL(store, type.modelName, _options.id, snapshot, requestType);
|
||||
let payload = this.preparePayload(store, type, snapshot);
|
||||
|
||||
return this.ajax(url, _options.method, payload);
|
||||
}
|
||||
|
||||
preparePayload(store, type, snapshot) {
|
||||
let serializer = store.serializerFor(type.modelName);
|
||||
let payload = {};
|
||||
|
||||
serializer.serializeIntoHash(payload, type, snapshot);
|
||||
|
||||
return {data: payload};
|
||||
}
|
||||
|
||||
buildIncludeURL(store, modelName, id, snapshot, requestType, query) {
|
||||
let includes = this.getEmbeddedRelations(store, modelName);
|
||||
let url = this.buildURL(modelName, id, snapshot, requestType, query);
|
||||
let parsedUrl = new URL(url);
|
||||
|
||||
if (includes.length) {
|
||||
parsedUrl.searchParams.append('include', includes.map(underscore).join(','));
|
||||
}
|
||||
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
|
||||
buildQuery(store, modelName, options) {
|
||||
let deDupe = {};
|
||||
let toInclude = this.getEmbeddedRelations(store, modelName);
|
||||
let query = options || {};
|
||||
|
||||
if (toInclude.length) {
|
||||
// If this is a find by id, build a query object and attach the includes
|
||||
if (typeof options === 'string' || typeof options === 'number') {
|
||||
query = {};
|
||||
query.id = options;
|
||||
query.include = toInclude.map(underscore).join(',');
|
||||
} else if (typeof options === 'object' || isNone(options)) {
|
||||
// If this is a find all (no existing query object) build one and attach
|
||||
// the includes.
|
||||
// If this is a find with an existing query object then merge the includes
|
||||
// into the existing object. Existing properties and includes are preserved.
|
||||
query = query || {};
|
||||
toInclude = toInclude.concat(query.include ? query.include.split(',') : []);
|
||||
|
||||
toInclude.forEach((include) => {
|
||||
deDupe[include] = true;
|
||||
});
|
||||
|
||||
query.include = Object.keys(deDupe).map(underscore).join(',');
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
getEmbeddedRelations(store, modelName) {
|
||||
let model = store.modelFor(modelName);
|
||||
let ret = [];
|
||||
let embedded = [];
|
||||
|
||||
// Iterate through the model's relationships and build a list
|
||||
// of those that need to be pulled in via "include" from the API
|
||||
model.eachRelationship((name, meta) => {
|
||||
if (
|
||||
meta.kind === 'hasMany'
|
||||
&& Object.prototype.hasOwnProperty.call(meta.options, 'embedded')
|
||||
&& meta.options.embedded === 'always'
|
||||
) {
|
||||
ret.push(name);
|
||||
embedded.push([name, meta.type]);
|
||||
}
|
||||
});
|
||||
|
||||
embedded.forEach(([relName, embeddedModelName]) => {
|
||||
this.getEmbeddedRelations(store, embeddedModelName).forEach((name) => {
|
||||
ret.push(`${relName}.${name}`);
|
||||
});
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
12
ghost/admin/app/adapters/label.js
Normal file
12
ghost/admin/app/adapters/label.js
Normal file
@ -0,0 +1,12 @@
|
||||
import ApplicationAdapter from 'ghost-admin/adapters/application';
|
||||
import SlugUrl from 'ghost-admin/utils/slug-url';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class Label extends ApplicationAdapter {
|
||||
buildURL(_modelName, _id, _snapshot, _requestType, query) {
|
||||
let url = super.buildURL(...arguments);
|
||||
|
||||
return SlugUrl(url, query);
|
||||
}
|
||||
}
|
27
ghost/admin/app/adapters/member.js
Normal file
27
ghost/admin/app/adapters/member.js
Normal file
@ -0,0 +1,27 @@
|
||||
import ApplicationAdapter from 'ghost-admin/adapters/application';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class Member extends ApplicationAdapter {
|
||||
queryRecord(store, type, query) {
|
||||
if (query && query.id) {
|
||||
let {id} = query;
|
||||
delete query.id;
|
||||
let url = this.buildURL(type.modelName, id, query, 'findRecord');
|
||||
return this.ajax(url, 'GET', {data: query});
|
||||
}
|
||||
|
||||
return super.queryRecord(...arguments);
|
||||
}
|
||||
|
||||
urlForDeleteRecord(id, modelName, snapshot) {
|
||||
let url = super.urlForDeleteRecord(...arguments);
|
||||
let parsedUrl = new URL(url);
|
||||
|
||||
if (snapshot && snapshot.adapterOptions && snapshot.adapterOptions.cancel) {
|
||||
parsedUrl.searchParams.set('cancel', 'true');
|
||||
}
|
||||
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
}
|
18
ghost/admin/app/adapters/newsletter.js
Normal file
18
ghost/admin/app/adapters/newsletter.js
Normal file
@ -0,0 +1,18 @@
|
||||
import ApplicationAdapter from 'ghost-admin/adapters/application';
|
||||
|
||||
export default class Newsletter extends ApplicationAdapter {
|
||||
buildIncludeURL(store, modelName, id, snapshot, requestType, query) {
|
||||
const url = this.buildURL(modelName, id, snapshot, requestType, query);
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
if (snapshot?.adapterOptions?.optInExisting) {
|
||||
parsedUrl.searchParams.append('opt_in_existing', 'true');
|
||||
}
|
||||
|
||||
if (snapshot?.adapterOptions?.include) {
|
||||
parsedUrl.searchParams.append('include', snapshot?.adapterOptions?.include);
|
||||
}
|
||||
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
}
|
23
ghost/admin/app/adapters/offer.js
Normal file
23
ghost/admin/app/adapters/offer.js
Normal file
@ -0,0 +1,23 @@
|
||||
import ApplicationAdapter from 'ghost-admin/adapters/application';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class Offer extends ApplicationAdapter {
|
||||
queryRecord(store, type, query) {
|
||||
if (query && query.id) {
|
||||
let {id} = query;
|
||||
delete query.id;
|
||||
let url = this.buildURL(type.modelName, id, query, 'findRecord');
|
||||
return this.ajax(url, 'GET', {data: query});
|
||||
}
|
||||
|
||||
return super.queryRecord(...arguments);
|
||||
}
|
||||
|
||||
urlForDeleteRecord() {
|
||||
let url = super.urlForDeleteRecord(...arguments);
|
||||
let parsedUrl = new URL(url);
|
||||
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
}
|
14
ghost/admin/app/adapters/page.js
Normal file
14
ghost/admin/app/adapters/page.js
Normal file
@ -0,0 +1,14 @@
|
||||
import ApplicationAdapter from 'ghost-admin/adapters/application';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class Page extends ApplicationAdapter {
|
||||
// posts and pages now include everything by default
|
||||
buildIncludeURL(store, modelName, id, snapshot, requestType, query) {
|
||||
return this.buildURL(modelName, id, snapshot, requestType, query);
|
||||
}
|
||||
|
||||
buildQuery(store, modelName, options) {
|
||||
return options;
|
||||
}
|
||||
}
|
30
ghost/admin/app/adapters/post.js
Normal file
30
ghost/admin/app/adapters/post.js
Normal file
@ -0,0 +1,30 @@
|
||||
import ApplicationAdapter from 'ghost-admin/adapters/application';
|
||||
|
||||
export default class Post extends ApplicationAdapter {
|
||||
// posts and pages now include everything by default
|
||||
buildIncludeURL(store, modelName, id, snapshot, requestType, query) {
|
||||
const url = this.buildURL(modelName, id, snapshot, requestType, query);
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
if (snapshot?.adapterOptions?.newsletter) {
|
||||
const newsletter = snapshot.adapterOptions.newsletter;
|
||||
parsedUrl.searchParams.append('newsletter', newsletter);
|
||||
|
||||
let emailSegment = snapshot?.adapterOptions?.emailSegment;
|
||||
|
||||
if (emailSegment) {
|
||||
if (emailSegment === 'status:free,status:-free') {
|
||||
emailSegment = 'all';
|
||||
}
|
||||
|
||||
parsedUrl.searchParams.append('email_segment', emailSegment);
|
||||
}
|
||||
}
|
||||
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
|
||||
buildQuery(store, modelName, options) {
|
||||
return options;
|
||||
}
|
||||
}
|
21
ghost/admin/app/adapters/setting.js
Normal file
21
ghost/admin/app/adapters/setting.js
Normal file
@ -0,0 +1,21 @@
|
||||
import ApplicationAdapter from 'ghost-admin/adapters/application';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class Setting extends ApplicationAdapter {
|
||||
updateRecord(store, type, record) {
|
||||
let data = {};
|
||||
let serializer = store.serializerFor(type.modelName);
|
||||
|
||||
// remove the fake id that we added onto the model.
|
||||
delete record.id;
|
||||
|
||||
// use the SettingSerializer to transform the model back into
|
||||
// an array of settings objects like the API expects
|
||||
serializer.serializeIntoHash(data, type, record);
|
||||
|
||||
// use the ApplicationAdapter's buildURL method but do not
|
||||
// pass in an id.
|
||||
return this.ajax(this.buildURL(type.modelName), 'PUT', {data});
|
||||
}
|
||||
}
|
12
ghost/admin/app/adapters/tag.js
Normal file
12
ghost/admin/app/adapters/tag.js
Normal file
@ -0,0 +1,12 @@
|
||||
import ApplicationAdapter from 'ghost-admin/adapters/application';
|
||||
import SlugUrl from 'ghost-admin/utils/slug-url';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class Tag extends ApplicationAdapter {
|
||||
buildURL(_modelName, _id, _snapshot, _requestType, query) {
|
||||
let url = super.buildURL(...arguments);
|
||||
|
||||
return SlugUrl(url, query);
|
||||
}
|
||||
}
|
14
ghost/admin/app/adapters/theme.js
Normal file
14
ghost/admin/app/adapters/theme.js
Normal file
@ -0,0 +1,14 @@
|
||||
import ApplicationAdapter from './application';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class Theme extends ApplicationAdapter {
|
||||
activate(model) {
|
||||
let url = `${this.buildURL('theme', model.get('id'))}activate/`;
|
||||
|
||||
return this.ajax(url, 'PUT', {data: {}}).then((data) => {
|
||||
this.store.pushPayload(data);
|
||||
return model;
|
||||
});
|
||||
}
|
||||
}
|
23
ghost/admin/app/adapters/tier.js
Normal file
23
ghost/admin/app/adapters/tier.js
Normal file
@ -0,0 +1,23 @@
|
||||
import ApplicationAdapter from 'ghost-admin/adapters/application';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class Tier extends ApplicationAdapter {
|
||||
queryRecord(store, type, query) {
|
||||
if (query && query.id) {
|
||||
let {id} = query;
|
||||
delete query.id;
|
||||
let url = this.buildURL(type.modelName, id, query, 'findRecord');
|
||||
return this.ajax(url, 'GET', {data: query});
|
||||
}
|
||||
|
||||
return super.queryRecord(...arguments);
|
||||
}
|
||||
|
||||
urlForDeleteRecord() {
|
||||
let url = super.urlForDeleteRecord(...arguments);
|
||||
let parsedUrl = new URL(url);
|
||||
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
}
|
22
ghost/admin/app/adapters/user.js
Normal file
22
ghost/admin/app/adapters/user.js
Normal file
@ -0,0 +1,22 @@
|
||||
import ApplicationAdapter from 'ghost-admin/adapters/application';
|
||||
import SlugUrl from 'ghost-admin/utils/slug-url';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class User extends ApplicationAdapter {
|
||||
buildURL(_modelName, _id, _snapshot, _requestType, query) {
|
||||
let url = super.buildURL(...arguments);
|
||||
|
||||
return SlugUrl(url, query);
|
||||
}
|
||||
|
||||
queryRecord(store, type, query) {
|
||||
if (!query || query.id !== 'me') {
|
||||
return super.queryRecord(...arguments);
|
||||
}
|
||||
|
||||
let url = this.buildURL(type.modelName, 'me', null, 'findRecord');
|
||||
|
||||
return this.ajax(url, 'GET', {data: {include: 'roles'}});
|
||||
}
|
||||
}
|
48
ghost/admin/app/app.js
Executable file
48
ghost/admin/app/app.js
Executable file
@ -0,0 +1,48 @@
|
||||
import 'ghost-admin/utils/link-component';
|
||||
import 'ghost-admin/utils/route';
|
||||
import Application from '@ember/application';
|
||||
import Resolver from 'ember-resolver';
|
||||
import config from 'ghost-admin/config/environment';
|
||||
import loadInitializers from 'ember-load-initializers';
|
||||
import moment from 'moment';
|
||||
import {registerWarnHandler} from '@ember/debug';
|
||||
|
||||
moment.updateLocale('en', {
|
||||
relativeTime: {
|
||||
m: '1 minute'
|
||||
}
|
||||
});
|
||||
|
||||
const App = Application.extend({
|
||||
Resolver,
|
||||
modulePrefix: config.modulePrefix,
|
||||
podModulePrefix: config.podModulePrefix,
|
||||
|
||||
// eslint-disable-next-line
|
||||
customEvents: {
|
||||
touchstart: null,
|
||||
touchmove: null,
|
||||
touchend: null,
|
||||
touchcancel: null
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: remove once the validations refactor is complete
|
||||
// eslint-disable-next-line
|
||||
registerWarnHandler((message, options, next) => {
|
||||
let skip = [
|
||||
'ds.errors.add',
|
||||
'ds.errors.remove',
|
||||
'ds.errors.clear'
|
||||
];
|
||||
|
||||
if (skip.includes(options.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
next(message, options);
|
||||
});
|
||||
|
||||
loadInitializers(App, config.modulePrefix);
|
||||
|
||||
export default App;
|
41
ghost/admin/app/authenticators/cookie.js
Normal file
41
ghost/admin/app/authenticators/cookie.js
Normal file
@ -0,0 +1,41 @@
|
||||
import Authenticator from 'ember-simple-auth/authenticators/base';
|
||||
import RSVP from 'rsvp';
|
||||
import {computed} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default Authenticator.extend({
|
||||
ajax: service(),
|
||||
ghostPaths: service(),
|
||||
|
||||
sessionEndpoint: computed('ghostPaths.apiRoot', function () {
|
||||
return `${this.ghostPaths.apiRoot}/session`;
|
||||
}),
|
||||
|
||||
restore: function () {
|
||||
return RSVP.resolve();
|
||||
},
|
||||
|
||||
authenticate(identification, password) {
|
||||
const data = {username: identification, password};
|
||||
const options = {
|
||||
data,
|
||||
contentType: 'application/json;charset=utf-8',
|
||||
// ember-ajax will try and parse the response as JSON if not explicitly set
|
||||
dataType: 'text'
|
||||
};
|
||||
|
||||
return this.ajax.post(this.sessionEndpoint, options);
|
||||
},
|
||||
|
||||
invalidate() {
|
||||
// if we're invalidating because of a 401 we can end up in an infinite
|
||||
// loop if we then try to perform a DELETE /session/ request
|
||||
// TODO: find a more elegant way to handle this
|
||||
if (this.ajax.skipSessionDeletion) {
|
||||
this.ajax.skipSessionDeletion = false;
|
||||
return RSVP.resolve();
|
||||
}
|
||||
|
||||
return this.ajax.del(this.sessionEndpoint);
|
||||
}
|
||||
});
|
3
ghost/admin/app/components/aspect-ratio-box.hbs
Normal file
3
ghost/admin/app/components/aspect-ratio-box.hbs
Normal file
@ -0,0 +1,3 @@
|
||||
{{#unless this.isResizing}}
|
||||
{{yield}}
|
||||
{{/unless}}
|
53
ghost/admin/app/components/aspect-ratio-box.js
Normal file
53
ghost/admin/app/components/aspect-ratio-box.js
Normal file
@ -0,0 +1,53 @@
|
||||
import Component from '@ember/component';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import {assert} from '@ember/debug';
|
||||
import {debounce, run} from '@ember/runloop';
|
||||
|
||||
@classic
|
||||
export default class AspectRatioBox extends Component {
|
||||
ratio = '1/1';
|
||||
base = 'height';
|
||||
isResizing = true;
|
||||
_ratio = 1;
|
||||
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
this._onResizeHandler = () => {
|
||||
debounce(this, this._resize, 200);
|
||||
};
|
||||
}
|
||||
|
||||
didReceiveAttrs() {
|
||||
super.didReceiveAttrs(...arguments);
|
||||
assert(
|
||||
'{{aspect-ratio-box}} requires a `ratio` property in the format `"16/9"`',
|
||||
this.ratio.match(/\d+\/\d+/)
|
||||
);
|
||||
this._ratio = this.ratio.split('/').reduce((prev, curr) => prev / curr);
|
||||
}
|
||||
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
this._resize();
|
||||
window.addEventListener('resize', this._onResizeHandler);
|
||||
}
|
||||
|
||||
willDestroyElement() {
|
||||
super.willDestroyElement(...arguments);
|
||||
window.removeEventListener('resize', this._onResizeHandler);
|
||||
}
|
||||
|
||||
_resize() {
|
||||
this.set('isResizing', true);
|
||||
|
||||
run.schedule('afterRender', this, function () {
|
||||
if (this.base === 'height') {
|
||||
this.element.style.width = `${this.element.clientHeight * this._ratio}px`;
|
||||
} else {
|
||||
this.element.style.height = `${this.element.clientWidth * this._ratio}px`;
|
||||
}
|
||||
|
||||
this.set('isResizing', false);
|
||||
});
|
||||
}
|
||||
}
|
15
ghost/admin/app/components/custom-theme-settings/boolean.hbs
Normal file
15
ghost/admin/app/components/custom-theme-settings/boolean.hbs
Normal file
@ -0,0 +1,15 @@
|
||||
<div class="gh-stack-item {{if (eq @index 0) "gh-setting-first" "gh-setting"}}">
|
||||
<div class="flex-grow-1">
|
||||
<div class="flex justify-between items-center relative">
|
||||
<span class="gh-setting-title" for={{this.checkboxId}}>
|
||||
{{humanize-setting-key @setting.key}}
|
||||
</span>
|
||||
<div class="for-switch x-small">
|
||||
<label for={{this.checkboxId}} class="switch">
|
||||
<input type="checkbox" class="gh-input" id={{this.checkboxId}} checked={{@setting.value}} {{on "input" this.toggleValue}}>
|
||||
<span class="input-toggle-component mt1"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
16
ghost/admin/app/components/custom-theme-settings/boolean.js
Normal file
16
ghost/admin/app/components/custom-theme-settings/boolean.js
Normal file
@ -0,0 +1,16 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {camelize} from '@ember/string';
|
||||
import {guidFor} from '@ember/object/internals';
|
||||
|
||||
export default class CustomThemeSettingsBooleanComponent extends Component {
|
||||
checkboxId = `checkbox-${guidFor(this)}`;
|
||||
checkboxName = camelize(this.args.setting.key);
|
||||
|
||||
@action
|
||||
toggleValue(changeEvent) {
|
||||
const value = changeEvent.target.checked;
|
||||
this.args.setting.set('value', value);
|
||||
this.args.onChange?.();
|
||||
}
|
||||
}
|
43
ghost/admin/app/components/custom-theme-settings/color.hbs
Normal file
43
ghost/admin/app/components/custom-theme-settings/color.hbs
Normal file
@ -0,0 +1,43 @@
|
||||
<div class="gh-stack-item {{if (eq @index 0) "gh-setting-first" "gh-setting"}}">
|
||||
<div class="flex flex-grow-1 justify-between">
|
||||
<div class="flex justify-between items-center relative">
|
||||
<span class="gh-setting-title" for={{this.inputId}}>
|
||||
{{humanize-setting-key @setting.key}}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="input-color">
|
||||
<input
|
||||
type="text"
|
||||
id={{this.inputId}}
|
||||
name={{this.inputName}}
|
||||
autocorrect="off"
|
||||
maxlength="6"
|
||||
value={{this.colorWithoutHash}}
|
||||
class="gh-input"
|
||||
{{on "input" (perform this.debounceValueUpdate)}}
|
||||
{{on "blur" this.updateValue}}
|
||||
{{on-key "Enter" this.blurElement}}
|
||||
data-test-input="accentColor"
|
||||
/>
|
||||
<div class="color-picker-horizontal-divider"></div>
|
||||
<div
|
||||
class="color-box-container"
|
||||
style={{this.this.colorPickerBgStyle}}
|
||||
>
|
||||
<input
|
||||
type="color"
|
||||
name={{this.inputName}}
|
||||
class="color-picker"
|
||||
value={{@setting.value}}
|
||||
{{on "input" (perform this.debounceValueUpdate)}}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.isInvalid}}
|
||||
<div class="w-100 red">Please enter a color in hex format</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
65
ghost/admin/app/components/custom-theme-settings/color.js
Normal file
65
ghost/admin/app/components/custom-theme-settings/color.js
Normal file
@ -0,0 +1,65 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {camelize} from '@ember/string';
|
||||
import {guidFor} from '@ember/object/internals';
|
||||
import {htmlSafe} from '@ember/template';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class CustomThemeSettingsColorComponent extends Component {
|
||||
inputId = `input-${guidFor(this)}`;
|
||||
inputName = camelize(this.args.setting.key);
|
||||
|
||||
@tracked isInvalid = false;
|
||||
|
||||
get colorWithoutHash() {
|
||||
const color = this.args.setting.value;
|
||||
if (color && color[0] === '#') {
|
||||
return color.slice(1);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
get colorPickerBgStyle() {
|
||||
return htmlSafe(`background-color: ${this.args.setting.value || '#ffffff'}`);
|
||||
}
|
||||
|
||||
@action
|
||||
updateValue(event) {
|
||||
const oldColor = this.args.setting.value;
|
||||
let newColor = event.target.value;
|
||||
|
||||
if (!newColor) {
|
||||
this.isInvalid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newColor[0] !== '#') {
|
||||
newColor = `#${newColor}`;
|
||||
}
|
||||
|
||||
if (newColor.match(/#[0-9A-Fa-f]{6}$/)) {
|
||||
this.isInvalid = false;
|
||||
|
||||
if (newColor === oldColor) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.args.setting.set('value', newColor);
|
||||
this.args.onChange?.();
|
||||
} else {
|
||||
this.isInvalid = true;
|
||||
}
|
||||
}
|
||||
|
||||
@task({restartable: true})
|
||||
*debounceValueUpdate(event) {
|
||||
yield timeout(500);
|
||||
this.updateValue(event);
|
||||
}
|
||||
|
||||
@action
|
||||
blurElement(event) {
|
||||
event.target.blur();
|
||||
}
|
||||
}
|
35
ghost/admin/app/components/custom-theme-settings/image.hbs
Normal file
35
ghost/admin/app/components/custom-theme-settings/image.hbs
Normal file
@ -0,0 +1,35 @@
|
||||
<div class="gh-stack-item {{if (eq @index 0) "gh-setting-first" "gh-setting"}}">
|
||||
<GhUploader
|
||||
@extensions={{this.imageExtensions}}
|
||||
@onComplete={{this.imageUploaded}}
|
||||
as |uploader|
|
||||
>
|
||||
<div class="{{if @setting.value "" "flex flex-grow-1 items-center justify-between"}}">
|
||||
<div class="gh-setting-content">
|
||||
<div class="gh-setting-title {{if @setting.value "gh-theme-setting-title"}}">{{humanize-setting-key @setting.key}}</div>
|
||||
{{#each uploader.errors as |error|}}
|
||||
<div class="gh-setting-error" data-test-error="icon">{{or error.context error.message}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="gh-setting-action gh-uploadbutton-container flex flex-column items-stretch">
|
||||
{{#if uploader.isUploading}}
|
||||
{{uploader.progressBar}}
|
||||
{{else if @setting.value}}
|
||||
<div class="gh-branding-image-container largeimg justify-start">
|
||||
<img class="blog-cover" src={{@setting.value}} {{on "click" uploader.triggerFileDialog}}>
|
||||
<button type="button" class="gh-setting-action-largeimg-delete" {{on "click" (fn this.updateValue null)}} data-test-delete-image="icon">
|
||||
{{svg-jar "trash" class="w4 h4 fill-white"}}
|
||||
</button>
|
||||
</div>
|
||||
{{else}}
|
||||
<button type="button" class="gh-btn gh-btn-white self-start" {{on "click" uploader.triggerFileDialog}} data-test-image-upload-btn="icon">
|
||||
<span>Upload</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
<div style="display:none">
|
||||
<GhFileInput @multiple={{false}} @action={{uploader.setFiles}} @accept={{this.imageMimeTypes}} @onInsert={{uploader.registerFileInput}} data-test-file-input="icon" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GhUploader>
|
||||
</div>
|
29
ghost/admin/app/components/custom-theme-settings/image.js
Normal file
29
ghost/admin/app/components/custom-theme-settings/image.js
Normal file
@ -0,0 +1,29 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {
|
||||
IMAGE_EXTENSIONS,
|
||||
IMAGE_MIME_TYPES
|
||||
} from 'ghost-admin/components/gh-image-uploader';
|
||||
import {action} from '@ember/object';
|
||||
import {camelize} from '@ember/string';
|
||||
import {guidFor} from '@ember/object/internals';
|
||||
|
||||
export default class CustomThemeSettingsImageComponent extends Component {
|
||||
inputId = `input-${guidFor(this)}`;
|
||||
inputName = camelize(this.args.setting.key);
|
||||
|
||||
imageExtensions = IMAGE_EXTENSIONS;
|
||||
imageMimeTypes = IMAGE_MIME_TYPES;
|
||||
|
||||
@action
|
||||
imageUploaded(images) {
|
||||
if (images[0]) {
|
||||
this.updateValue(images[0].url);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
updateValue(value) {
|
||||
this.args.setting.set('value', value);
|
||||
this.args.onChange?.();
|
||||
}
|
||||
}
|
19
ghost/admin/app/components/custom-theme-settings/select.hbs
Normal file
19
ghost/admin/app/components/custom-theme-settings/select.hbs
Normal file
@ -0,0 +1,19 @@
|
||||
<div class="gh-stack-item {{if (eq @index 0) "gh-setting-first" "gh-setting"}}">
|
||||
<div class="flex-grow-1">
|
||||
<label class="gh-setting-title gh-theme-setting-title" for={{this.selectId}}>
|
||||
{{humanize-setting-key @setting.key}}
|
||||
</label>
|
||||
|
||||
<span class="gh-select">
|
||||
<select class="ember-select" name={{this.selectName}} id={{this.selectId}} {{on "change" this.setSelection}}>
|
||||
{{#each @setting.options as |settingOption|}}
|
||||
<option value={{settingOption}} selected={{eq settingOption @setting.value}}>
|
||||
{{settingOption}}
|
||||
{{#if (eq settingOption @setting.default)}}(default){{/if}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
{{svg-jar "arrow-down-small"}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
16
ghost/admin/app/components/custom-theme-settings/select.js
Normal file
16
ghost/admin/app/components/custom-theme-settings/select.js
Normal file
@ -0,0 +1,16 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {camelize} from '@ember/string';
|
||||
import {guidFor} from '@ember/object/internals';
|
||||
|
||||
export default class CustomThemeSettingsSelectComponent extends Component {
|
||||
selectId = `select-${guidFor(this)}`;
|
||||
selectName = camelize(this.args.setting.key);
|
||||
|
||||
@action
|
||||
setSelection(changeEvent) {
|
||||
const value = changeEvent.target.value;
|
||||
this.args.setting.set('value', value);
|
||||
this.args.onChange?.();
|
||||
}
|
||||
}
|
17
ghost/admin/app/components/custom-theme-settings/text.hbs
Normal file
17
ghost/admin/app/components/custom-theme-settings/text.hbs
Normal file
@ -0,0 +1,17 @@
|
||||
<div class="gh-stack-item {{if (eq @index 0) "gh-setting-first" "gh-setting"}}">
|
||||
<div class="flex-grow-1">
|
||||
<label class="gh-setting-title gh-theme-setting-title" for={{this.inputId}}>
|
||||
{{humanize-setting-key @setting.key}}
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
class="gh-input"
|
||||
value={{@setting.value}}
|
||||
id={{this.inputId}}
|
||||
name={{this.inputName}}
|
||||
{{on "input" this.updateValue}}
|
||||
{{on "blur" this.triggerOnChange}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
19
ghost/admin/app/components/custom-theme-settings/text.js
Normal file
19
ghost/admin/app/components/custom-theme-settings/text.js
Normal file
@ -0,0 +1,19 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {camelize} from '@ember/string';
|
||||
import {guidFor} from '@ember/object/internals';
|
||||
|
||||
export default class CustomThemeSettingsTextComponent extends Component {
|
||||
inputId = `input-${guidFor(this)}`;
|
||||
inputName = camelize(this.args.setting.key);
|
||||
|
||||
@action
|
||||
updateValue(event) {
|
||||
this.args.setting.set('value', event.target.value);
|
||||
}
|
||||
|
||||
@action
|
||||
triggerOnChange() {
|
||||
this.args.onChange?.();
|
||||
}
|
||||
}
|
74
ghost/admin/app/components/dashboard/charts/anchor.hbs
Normal file
74
ghost/admin/app/components/dashboard/charts/anchor.hbs
Normal file
@ -0,0 +1,74 @@
|
||||
<section class="gh-dashboard-section gh-dashboard-anchor" {{did-insert this.loadCharts}}>
|
||||
<article class="gh-dashboard-box">
|
||||
{{#if this.hasPaidTiers}}
|
||||
<div class="gh-dashboard-select-title">
|
||||
<PowerSelect
|
||||
@selected={{this.selectedDisplayOption}}
|
||||
@options={{this.displayOptions}}
|
||||
@searchEnabled={{false}}
|
||||
@onChange={{this.onDisplayChange}}
|
||||
@triggerComponent="gh-power-select/trigger"
|
||||
@triggerClass="gh-contentfilter-menu-trigger"
|
||||
@dropdownClass="gh-contentfilter-menu-dropdown is-narrow"
|
||||
@matchTriggerWidth={{false}}
|
||||
@horizontalPosition="left"
|
||||
as |option|
|
||||
>
|
||||
{{#if option.name}}{{option.name}}{{else}}<span class="red">Unknown option</span>{{/if}}
|
||||
</PowerSelect>
|
||||
</div>
|
||||
{{else}}
|
||||
<Dashboard::Parts::Metric
|
||||
@label="Total members"
|
||||
@value={{format-number this.totalMembers}}
|
||||
@trends={{this.hasTrends}}
|
||||
@percentage={{this.totalMembersTrend}}
|
||||
@large={{true}} />
|
||||
{{/if}}
|
||||
|
||||
<div class="gh-dashboard-hero {{unless this.hasPaidTiers 'is-solo'}}">
|
||||
<div class="gh-dashboard-chart gh-dashboard-totals">
|
||||
<div class="gh-dashboard-chart-container">
|
||||
<div class="gh-dashboard-chart-box">
|
||||
{{#if this.loading}}
|
||||
<div class="gh-dashboard-chart-loading">
|
||||
<div class="gh-loading-spinner"></div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-fader">
|
||||
<EmberChart
|
||||
@type={{this.chartType}}
|
||||
@data={{this.chartData}}
|
||||
@options={{this.chartOptions}}
|
||||
@height={{200}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div id="gh-dashboard-anchor-tooltip" class="gh-dashboard-tooltip">
|
||||
<div class="gh-dashboard-tooltip-label">
|
||||
-
|
||||
</div>
|
||||
<div class="gh-dashboard-tooltip-value">
|
||||
<span class="indicator line"></span>
|
||||
<span class="value">–</span>
|
||||
<span class="metric">{{this.selectedDisplayOption.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-chart-ticks">
|
||||
<span id="gh-dashboard-anchor-date-start">-</span>
|
||||
<span id="gh-dashboard-anchor-date-end">-</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.hasPaidTiers}}
|
||||
<article class="gh-dashboard-minicharts">
|
||||
<Dashboard::Charts::PaidMrr />
|
||||
<Dashboard::Charts::PaidBreakdown />
|
||||
<Dashboard::Charts::PaidMix />
|
||||
</article>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</section>
|
741
ghost/admin/app/components/dashboard/charts/anchor.js
Normal file
741
ghost/admin/app/components/dashboard/charts/anchor.js
Normal file
@ -0,0 +1,741 @@
|
||||
/* global Chart */
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import moment from 'moment';
|
||||
import {action} from '@ember/object';
|
||||
import {getSymbol} from 'ghost-admin/utils/currency';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const DATE_FORMAT = 'D MMM, YYYY';
|
||||
|
||||
const DISPLAY_OPTIONS = [{
|
||||
name: 'Total members',
|
||||
value: 'total'
|
||||
}, {
|
||||
name: 'Paid members',
|
||||
value: 'paid'
|
||||
}, {
|
||||
name: 'Free members',
|
||||
value: 'free'
|
||||
}];
|
||||
|
||||
// custom ChartJS draw function
|
||||
Chart.defaults.hoverLine = Chart.defaults.line;
|
||||
Chart.controllers.hoverLine = Chart.controllers.line.extend({
|
||||
draw: function (ease) {
|
||||
Chart.controllers.line.prototype.draw.call(this, ease);
|
||||
|
||||
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
|
||||
let activePoint = this.chart.tooltip._active[0],
|
||||
ctx = this.chart.ctx,
|
||||
x = activePoint.tooltipPosition().x,
|
||||
topY = this.chart.legend.bottom,
|
||||
bottomY = this.chart.chartArea.bottom;
|
||||
|
||||
// draw line
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, topY);
|
||||
ctx.lineTo(x, bottomY);
|
||||
ctx.setLineDash([3, 4]);
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = '#7C8B9A';
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default class Anchor extends Component {
|
||||
@service dashboardStats;
|
||||
@service feature;
|
||||
@tracked chartDisplay = 'total';
|
||||
@tracked resizing = false;
|
||||
@tracked resizeTimer = null;
|
||||
|
||||
displayOptions = DISPLAY_OPTIONS;
|
||||
|
||||
willDestroy(...args) {
|
||||
super.willDestroy(...args);
|
||||
window.removeEventListener('resize', this.resizer, false);
|
||||
}
|
||||
|
||||
// this helps with ChartJS resizing stretching bug when resizing
|
||||
resizer = () => {
|
||||
this.resizing = true;
|
||||
|
||||
clearTimeout(this.resizeTimer); // this uses a trick to trigger only when resize is done
|
||||
this.resizeTimer = setTimeout(() => {
|
||||
this.resizing = false;
|
||||
}, 500);
|
||||
};
|
||||
|
||||
@action
|
||||
loadCharts() {
|
||||
this.dashboardStats.loadMemberCountStats();
|
||||
|
||||
window.addEventListener('resize', this.resizer, false);
|
||||
|
||||
if (this.hasPaidTiers) {
|
||||
this.dashboardStats.loadMrrStats();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
onDisplayChange(selected) {
|
||||
this.chartDisplay = selected.value;
|
||||
}
|
||||
|
||||
get selectedDisplayOption() {
|
||||
return this.displayOptions.find(d => d.value === this.chartDisplay) ?? this.displayOptions[0];
|
||||
}
|
||||
|
||||
get loading() {
|
||||
return this.dashboardStats.memberCountStats === null || this.resizing;
|
||||
}
|
||||
|
||||
get totalMembers() {
|
||||
return this.dashboardStats.memberCounts?.total ?? 0;
|
||||
}
|
||||
|
||||
get isTotalMembersZero() {
|
||||
return this.dashboardStats.memberCounts && this.totalMembers === 0;
|
||||
}
|
||||
|
||||
get paidMembers() {
|
||||
return this.dashboardStats.memberCounts?.paid ?? 0;
|
||||
}
|
||||
|
||||
get freeMembers() {
|
||||
return this.dashboardStats.memberCounts?.free ?? 0;
|
||||
}
|
||||
|
||||
get hasTrends() {
|
||||
return this.dashboardStats.memberCounts !== null
|
||||
&& this.dashboardStats.memberCountsTrend !== null;
|
||||
}
|
||||
|
||||
get totalMembersTrend() {
|
||||
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.total, this.dashboardStats.memberCounts.total);
|
||||
}
|
||||
|
||||
get paidMembersTrend() {
|
||||
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.paid, this.dashboardStats.memberCounts.paid);
|
||||
}
|
||||
|
||||
get freeMembersTrend() {
|
||||
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.free, this.dashboardStats.memberCounts.free);
|
||||
}
|
||||
|
||||
get hasPaidTiers() {
|
||||
return this.dashboardStats.siteStatus?.hasPaidTiers;
|
||||
}
|
||||
|
||||
get chartType() {
|
||||
return 'hoverLine'; // uses custom ChartJS draw function
|
||||
}
|
||||
|
||||
get chartTitle() {
|
||||
// paid
|
||||
if (this.chartDisplay === 'paid') {
|
||||
return 'Paid members';
|
||||
// free
|
||||
} else if (this.chartDisplay === 'free') {
|
||||
return 'Free members';
|
||||
}
|
||||
// total
|
||||
return 'Total members';
|
||||
}
|
||||
|
||||
get chartData() {
|
||||
let stats;
|
||||
let labels;
|
||||
let data;
|
||||
|
||||
if (this.chartDisplay === 'paid') {
|
||||
// paid
|
||||
stats = this.dashboardStats.filledMemberCountStats;
|
||||
labels = stats.map(stat => stat.date);
|
||||
data = stats.map(stat => stat.paid + stat.comped);
|
||||
} else if (this.chartDisplay === 'free') {
|
||||
// free
|
||||
stats = this.dashboardStats.filledMemberCountStats;
|
||||
labels = stats.map(stat => stat.date);
|
||||
data = stats.map(stat => stat.free);
|
||||
} else {
|
||||
// total
|
||||
stats = this.dashboardStats.filledMemberCountStats;
|
||||
labels = stats.map(stat => stat.date);
|
||||
data = stats.map(stat => stat.paid + stat.free + stat.comped);
|
||||
}
|
||||
|
||||
// with no members yet, let's show empty state with dummy data
|
||||
if (this.isTotalMembersZero) {
|
||||
stats = this.emptyData.stats;
|
||||
labels = this.emptyData.labels;
|
||||
data = this.emptyData.data;
|
||||
}
|
||||
|
||||
// gradient for line
|
||||
const canvasLine = document.createElement('canvas');
|
||||
const ctxLine = canvasLine.getContext('2d');
|
||||
const gradientLine = ctxLine.createLinearGradient(0, 0, 1000, 0);
|
||||
gradientLine.addColorStop(0, 'rgba(250, 45, 142, 1');
|
||||
gradientLine.addColorStop(1, 'rgba(143, 66, 255, 1');
|
||||
|
||||
// gradient for fill
|
||||
const canvasFill = document.createElement('canvas');
|
||||
const ctxFill = canvasFill.getContext('2d');
|
||||
const gradientFill = ctxFill.createLinearGradient(0, 0, 1000, 0);
|
||||
gradientFill.addColorStop(0, 'rgba(250, 45, 142, 0.2');
|
||||
gradientFill.addColorStop(1, 'rgba(143, 66, 255, 0.1');
|
||||
|
||||
return {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
data: data,
|
||||
tension: 1,
|
||||
cubicInterpolationMode: 'monotone',
|
||||
fill: true,
|
||||
fillColor: gradientFill,
|
||||
backgroundColor: gradientFill,
|
||||
pointRadius: 0,
|
||||
pointHitRadius: 10,
|
||||
pointBorderColor: '#8E42FF',
|
||||
pointBackgroundColor: '#8E42FF',
|
||||
pointHoverBackgroundColor: '#8E42FF',
|
||||
pointHoverBorderColor: '#8E42FF',
|
||||
pointHoverRadius: 0,
|
||||
borderColor: gradientLine,
|
||||
borderJoinStyle: 'miter'
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
get mrrCurrencySymbol() {
|
||||
if (this.dashboardStats.mrrStats === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const firstCurrency = this.dashboardStats.mrrStats[0] ? this.dashboardStats.mrrStats[0].currency : 'usd';
|
||||
return getSymbol(firstCurrency);
|
||||
}
|
||||
|
||||
get chartOptions() {
|
||||
let activeDays = this.dashboardStats.chartDays;
|
||||
let barColor = this.feature.nightShift ? 'rgba(200, 204, 217, 0.25)' : 'rgba(200, 204, 217, 0.65)';
|
||||
|
||||
return {
|
||||
maintainAspectRatio: false,
|
||||
responsiveAnimationDuration: 1,
|
||||
animation: false,
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
top: 2,
|
||||
bottom: 2,
|
||||
left: 1,
|
||||
right: 1
|
||||
}
|
||||
},
|
||||
hover: {
|
||||
onHover: function (e) {
|
||||
e.target.style.cursor = 'pointer';
|
||||
}
|
||||
},
|
||||
tooltips: {
|
||||
enabled: false,
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
custom: function (tooltip) {
|
||||
// get tooltip element
|
||||
const tooltipEl = document.getElementById('gh-dashboard-anchor-tooltip');
|
||||
const chartContainerEl = tooltipEl.parentElement;
|
||||
const chartWidth = chartContainerEl.offsetWidth;
|
||||
const tooltipWidth = tooltipEl.offsetWidth;
|
||||
|
||||
// only show tooltip when active
|
||||
if (tooltip.opacity === 0) {
|
||||
tooltipEl.style.opacity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// update tooltip styles
|
||||
tooltipEl.style.opacity = 1;
|
||||
tooltipEl.style.position = 'absolute';
|
||||
|
||||
let offsetX = 0;
|
||||
if (tooltip.x > chartWidth - tooltipWidth) {
|
||||
offsetX = tooltipWidth - 10;
|
||||
}
|
||||
|
||||
tooltipEl.style.left = tooltip.x - offsetX + 'px';
|
||||
tooltipEl.style.top = tooltip.y + 'px';
|
||||
},
|
||||
callbacks: {
|
||||
label: (tooltipItems, data) => {
|
||||
const value = data.datasets[tooltipItems.datasetIndex].data[tooltipItems.index].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
document.querySelector('#gh-dashboard-anchor-tooltip .gh-dashboard-tooltip-value .value').innerHTML = value;
|
||||
},
|
||||
title: (tooltipItems) => {
|
||||
const value = moment(tooltipItems[0].xLabel).format(DATE_FORMAT);
|
||||
document.querySelector('#gh-dashboard-anchor-tooltip .gh-dashboard-tooltip-label').innerHTML = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
display: true,
|
||||
gridLines: {
|
||||
drawTicks: false,
|
||||
display: true,
|
||||
drawBorder: false,
|
||||
color: 'transparent',
|
||||
zeroLineColor: barColor,
|
||||
zeroLineWidth: 1
|
||||
},
|
||||
ticks: {
|
||||
display: false
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
display: true,
|
||||
scaleLabel: {
|
||||
align: 'start'
|
||||
},
|
||||
gridLines: {
|
||||
color: barColor,
|
||||
borderDash: [3,4],
|
||||
display: true,
|
||||
drawBorder: true,
|
||||
drawTicks: false,
|
||||
zeroLineWidth: 1,
|
||||
zeroLineColor: barColor,
|
||||
zeroLineBorderDash: [3,4]
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
autoSkip: false,
|
||||
callback: function (value, index, values) {
|
||||
if (index === 0) {
|
||||
document.getElementById('gh-dashboard-anchor-date-start').innerHTML = moment(value).format(DATE_FORMAT);
|
||||
}
|
||||
if (index === (values.length - 1)) {
|
||||
document.getElementById('gh-dashboard-anchor-date-end').innerHTML = moment(value).format(DATE_FORMAT);
|
||||
}
|
||||
|
||||
if (activeDays === (30 + 1)) {
|
||||
if (!(index % 2)) {
|
||||
return value;
|
||||
}
|
||||
} else if (activeDays === (90 + 1)) {
|
||||
if (!(index % 3)) {
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get chartHeight() {
|
||||
return 200;
|
||||
}
|
||||
|
||||
get chartHeightSmall() {
|
||||
return 180;
|
||||
}
|
||||
|
||||
// used for empty state
|
||||
get emptyData() {
|
||||
return {
|
||||
stats: [
|
||||
{
|
||||
date: '2022-04-07',
|
||||
free: 2610,
|
||||
tier1: 295,
|
||||
tier2: 20,
|
||||
paid: 315,
|
||||
comped: 0,
|
||||
paidSubscribed: 2,
|
||||
paidCanceled: 1
|
||||
},
|
||||
{
|
||||
date: '2022-04-08',
|
||||
free: 2765,
|
||||
tier1: 298,
|
||||
tier2: 24,
|
||||
paid: 322,
|
||||
comped: 0,
|
||||
paidSubscribed: 7,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-04-09',
|
||||
free: 3160,
|
||||
tier1: 299,
|
||||
tier2: 28,
|
||||
paid: 327,
|
||||
comped: 0,
|
||||
paidSubscribed: 5,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-04-10',
|
||||
free: 3580,
|
||||
tier1: 300,
|
||||
tier2: 30,
|
||||
paid: 330,
|
||||
comped: 0,
|
||||
paidSubscribed: 4,
|
||||
paidCanceled: 1
|
||||
},
|
||||
{
|
||||
date: '2022-04-11',
|
||||
free: 3583,
|
||||
tier1: 301,
|
||||
tier2: 31,
|
||||
paid: 332,
|
||||
comped: 0,
|
||||
paidSubscribed: 2,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-04-12',
|
||||
free: 3857,
|
||||
tier1: 303,
|
||||
tier2: 36,
|
||||
paid: 339,
|
||||
comped: 0,
|
||||
paidSubscribed: 8,
|
||||
paidCanceled: 1
|
||||
},
|
||||
{
|
||||
date: '2022-04-13',
|
||||
free: 4223,
|
||||
tier1: 304,
|
||||
tier2: 39,
|
||||
paid: 343,
|
||||
comped: 0,
|
||||
paidSubscribed: 4,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-04-14',
|
||||
free: 4289,
|
||||
tier1: 306,
|
||||
tier2: 42,
|
||||
paid: 348,
|
||||
comped: 0,
|
||||
paidSubscribed: 6,
|
||||
paidCanceled: 1
|
||||
},
|
||||
{
|
||||
date: '2022-04-15',
|
||||
free: 4458,
|
||||
tier1: 307,
|
||||
tier2: 49,
|
||||
paid: 356,
|
||||
comped: 0,
|
||||
paidSubscribed: 8,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-04-16',
|
||||
free: 4752,
|
||||
tier1: 307,
|
||||
tier2: 49,
|
||||
paid: 356,
|
||||
comped: 0,
|
||||
paidSubscribed: 1,
|
||||
paidCanceled: 1
|
||||
},
|
||||
{
|
||||
date: '2022-04-17',
|
||||
free: 4947,
|
||||
tier1: 310,
|
||||
tier2: 50,
|
||||
paid: 360,
|
||||
comped: 0,
|
||||
paidSubscribed: 5,
|
||||
paidCanceled: 1
|
||||
},
|
||||
{
|
||||
date: '2022-04-18',
|
||||
free: 5047,
|
||||
tier1: 312,
|
||||
tier2: 49,
|
||||
paid: 361,
|
||||
comped: 0,
|
||||
paidSubscribed: 2,
|
||||
paidCanceled: 1
|
||||
},
|
||||
{
|
||||
date: '2022-04-19',
|
||||
free: 5430,
|
||||
tier1: 314,
|
||||
tier2: 55,
|
||||
paid: 369,
|
||||
comped: 0,
|
||||
paidSubscribed: 8,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-04-20',
|
||||
free: 5760,
|
||||
tier1: 316,
|
||||
tier2: 57,
|
||||
paid: 373,
|
||||
comped: 0,
|
||||
paidSubscribed: 4,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-04-21',
|
||||
free: 6022,
|
||||
tier1: 318,
|
||||
tier2: 63,
|
||||
paid: 381,
|
||||
comped: 0,
|
||||
paidSubscribed: 9,
|
||||
paidCanceled: 1
|
||||
},
|
||||
{
|
||||
date: '2022-04-22',
|
||||
free: 6294,
|
||||
tier1: 319,
|
||||
tier2: 64,
|
||||
paid: 383,
|
||||
comped: 0,
|
||||
paidSubscribed: 2,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-04-23',
|
||||
free: 6664,
|
||||
tier1: 320,
|
||||
tier2: 69,
|
||||
paid: 389,
|
||||
comped: 0,
|
||||
paidSubscribed: 6,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-04-24',
|
||||
free: 6721,
|
||||
tier1: 320,
|
||||
tier2: 70,
|
||||
paid: 390,
|
||||
comped: 0,
|
||||
paidSubscribed: 1,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-04-25',
|
||||
free: 6841,
|
||||
tier1: 321,
|
||||
tier2: 80,
|
||||
paid: 401,
|
||||
comped: 0,
|
||||
paidSubscribed: 11,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-04-26',
|
||||
free: 6880,
|
||||
tier1: 323,
|
||||
tier2: 89,
|
||||
paid: 412,
|
||||
comped: 0,
|
||||
paidSubscribed: 11,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-04-27',
|
||||
free: 7179,
|
||||
tier1: 325,
|
||||
tier2: 92,
|
||||
paid: 417,
|
||||
comped: 0,
|
||||
paidSubscribed: 5,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-04-28',
|
||||
free: 7288,
|
||||
tier1: 325,
|
||||
tier2: 100,
|
||||
paid: 425,
|
||||
comped: 0,
|
||||
paidSubscribed: 9,
|
||||
paidCanceled: 1
|
||||
},
|
||||
{
|
||||
date: '2022-04-29',
|
||||
free: 7430,
|
||||
tier1: 325,
|
||||
tier2: 101,
|
||||
paid: 426,
|
||||
comped: 0,
|
||||
paidSubscribed: 2,
|
||||
paidCanceled: 1
|
||||
},
|
||||
{
|
||||
date: '2022-04-30',
|
||||
free: 7458,
|
||||
tier1: 326,
|
||||
tier2: 102,
|
||||
paid: 428,
|
||||
comped: 0,
|
||||
paidSubscribed: 2,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-05-01',
|
||||
free: 7621,
|
||||
tier1: 327,
|
||||
tier2: 117,
|
||||
paid: 444,
|
||||
comped: 0,
|
||||
paidSubscribed: 17,
|
||||
paidCanceled: 1
|
||||
},
|
||||
{
|
||||
date: '2022-05-02',
|
||||
free: 7721,
|
||||
tier1: 328,
|
||||
tier2: 123,
|
||||
paid: 451,
|
||||
comped: 0,
|
||||
paidSubscribed: 8,
|
||||
paidCanceled: 1
|
||||
},
|
||||
{
|
||||
date: '2022-05-03',
|
||||
free: 7897,
|
||||
tier1: 327,
|
||||
tier2: 137,
|
||||
paid: 464,
|
||||
comped: 0,
|
||||
paidSubscribed: 14,
|
||||
paidCanceled: 1
|
||||
},
|
||||
{
|
||||
date: '2022-05-04',
|
||||
free: 7937,
|
||||
tier1: 327,
|
||||
tier2: 143,
|
||||
paid: 470,
|
||||
comped: 0,
|
||||
paidSubscribed: 6,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-05-05',
|
||||
free: 7961,
|
||||
tier1: 328,
|
||||
tier2: 158,
|
||||
paid: 486,
|
||||
comped: 0,
|
||||
paidSubscribed: 16,
|
||||
paidCanceled: 0
|
||||
},
|
||||
{
|
||||
date: '2022-05-06',
|
||||
free: 8006,
|
||||
tier1: 328,
|
||||
tier2: 162,
|
||||
paid: 490,
|
||||
comped: 0,
|
||||
paidSubscribed: 5,
|
||||
paidCanceled: 1
|
||||
}
|
||||
],
|
||||
labels: [
|
||||
'2022-04-07',
|
||||
'2022-04-08',
|
||||
'2022-04-09',
|
||||
'2022-04-10',
|
||||
'2022-04-11',
|
||||
'2022-04-12',
|
||||
'2022-04-13',
|
||||
'2022-04-14',
|
||||
'2022-04-15',
|
||||
'2022-04-16',
|
||||
'2022-04-17',
|
||||
'2022-04-18',
|
||||
'2022-04-19',
|
||||
'2022-04-20',
|
||||
'2022-04-21',
|
||||
'2022-04-22',
|
||||
'2022-04-23',
|
||||
'2022-04-24',
|
||||
'2022-04-25',
|
||||
'2022-04-26',
|
||||
'2022-04-27',
|
||||
'2022-04-28',
|
||||
'2022-04-29',
|
||||
'2022-04-30',
|
||||
'2022-05-01',
|
||||
'2022-05-02',
|
||||
'2022-05-03',
|
||||
'2022-05-04',
|
||||
'2022-05-05',
|
||||
'2022-05-06'
|
||||
],
|
||||
data: [
|
||||
2925,
|
||||
3087,
|
||||
3487,
|
||||
3910,
|
||||
3915,
|
||||
4196,
|
||||
4566,
|
||||
4637,
|
||||
4814,
|
||||
5108,
|
||||
5307,
|
||||
5408,
|
||||
5799,
|
||||
6133,
|
||||
6403,
|
||||
6677,
|
||||
7053,
|
||||
7111,
|
||||
7242,
|
||||
7292,
|
||||
7596,
|
||||
7713,
|
||||
7856,
|
||||
7886,
|
||||
8065,
|
||||
8172,
|
||||
8361,
|
||||
8407,
|
||||
8447,
|
||||
8496
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
calculatePercentage(from, to) {
|
||||
if (from === 0) {
|
||||
if (to > 0) {
|
||||
return 100;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.round((to - from) / from * 100);
|
||||
}
|
||||
}
|
49
ghost/admin/app/components/dashboard/charts/engagement.hbs
Normal file
49
ghost/admin/app/components/dashboard/charts/engagement.hbs
Normal file
@ -0,0 +1,49 @@
|
||||
<section class="gh-dashboard-section gh-dashboard-engagement">
|
||||
<article {{did-insert this.loadCharts}} class="gh-dashboard-box">
|
||||
<Dashboard::Parts::Metric
|
||||
@label="Engagement" />
|
||||
|
||||
<div class="gh-dashboard-columns">
|
||||
<div class="gh-dashboard-column gh-dashboard-engagement-30days">
|
||||
<Dashboard::Parts::Metric
|
||||
@label="Engaged in the last 30 days"
|
||||
@value={{this.data30Days}}
|
||||
@secondary={{true}}
|
||||
/>
|
||||
</div>
|
||||
<div class="gh-dashboard-column gh-dashboard-engagement-7days">
|
||||
<Dashboard::Parts::Metric
|
||||
@label="Engaged in the last 7 days"
|
||||
@value={{this.data7Days}}
|
||||
@secondary={{true}}
|
||||
/>
|
||||
</div>
|
||||
<div class="gh-dashboard-column gh-dashboard-engagement-subscribers">
|
||||
<Dashboard::Parts::Metric
|
||||
@label="Newsletter subscribers"
|
||||
@value={{this.dataSubscribers}}
|
||||
@secondary={{true}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
{{#if this.hasPaidTiers}}
|
||||
<div class="gh-dashboard-select">
|
||||
<PowerSelect
|
||||
@selected={{this.selectedStatusOption}}
|
||||
@options={{this.statusOptions}}
|
||||
@searchEnabled={{false}}
|
||||
@onChange={{this.onSwitchStatus}}
|
||||
@triggerComponent="gh-power-select/trigger"
|
||||
@triggerClass="gh-contentfilter-menu-trigger"
|
||||
@dropdownClass="gh-contentfilter-menu-dropdown is-narrow"
|
||||
@matchTriggerWidth={{false}}
|
||||
@horizontalPosition="right"
|
||||
as |option|
|
||||
>
|
||||
{{#if option.name}}{{option.name}}{{else}}<span class="red">Unknown option</span>{{/if}}
|
||||
</PowerSelect>
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
118
ghost/admin/app/components/dashboard/charts/engagement.js
Normal file
118
ghost/admin/app/components/dashboard/charts/engagement.js
Normal file
@ -0,0 +1,118 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {formatNumber} from '../../../helpers/format-number';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const STATUS_OPTIONS = [{
|
||||
name: 'All members',
|
||||
value: 'total'
|
||||
}, {
|
||||
name: 'Paid members',
|
||||
value: 'paid'
|
||||
}, {
|
||||
name: 'Free members',
|
||||
value: 'free'
|
||||
}];
|
||||
|
||||
export default class Engagement extends Component {
|
||||
@service dashboardStats;
|
||||
|
||||
@action
|
||||
loadCharts() {
|
||||
this.dashboardStats.lastSeenFilterStatus = this.status;
|
||||
this.dashboardStats.loadLastSeen();
|
||||
this.dashboardStats.loadMemberCountStats();
|
||||
this.dashboardStats.loadNewsletterSubscribers();
|
||||
}
|
||||
|
||||
@tracked status = 'total';
|
||||
statusOptions = STATUS_OPTIONS;
|
||||
|
||||
get selectedStatusOption() {
|
||||
return this.statusOptions.find(option => option.value === this.status);
|
||||
}
|
||||
|
||||
@action
|
||||
onSwitchStatus(selected) {
|
||||
this.status = selected.value;
|
||||
this.dashboardStats.lastSeenFilterStatus = this.status;
|
||||
this.dashboardStats.loadLastSeen();
|
||||
}
|
||||
|
||||
get loading() {
|
||||
return this.dashboardStats.memberCounts === null
|
||||
|| !this.dashboardStats.memberCounts[this.status]
|
||||
|| this.dashboardStats.membersLastSeen30d === null
|
||||
|| this.dashboardStats.membersLastSeen7d === null;
|
||||
}
|
||||
|
||||
get data30Days() {
|
||||
// fake empty data
|
||||
if (this.isTotalMembersZero) {
|
||||
return '30%';
|
||||
}
|
||||
|
||||
if (this.loading) {
|
||||
return '- %';
|
||||
}
|
||||
const total = this.dashboardStats.memberCounts[this.status];
|
||||
const part = this.dashboardStats.membersLastSeen30d;
|
||||
|
||||
if (total <= 0) {
|
||||
return '- %';
|
||||
}
|
||||
|
||||
const percentage = Math.round(part / total * 100);
|
||||
return `${percentage}%`;
|
||||
}
|
||||
|
||||
get data7Days() {
|
||||
// fake empty data
|
||||
if (this.isTotalMembersZero) {
|
||||
return '60%';
|
||||
}
|
||||
|
||||
if (this.loading) {
|
||||
return '- %';
|
||||
}
|
||||
const total = this.dashboardStats.memberCounts[this.status];
|
||||
const part = this.dashboardStats.membersLastSeen7d;
|
||||
|
||||
if (total <= 0) {
|
||||
return '- %';
|
||||
}
|
||||
|
||||
const percentage = Math.round(part / total * 100);
|
||||
return `${percentage}%`;
|
||||
}
|
||||
|
||||
get dataSubscribers() {
|
||||
// fake empty data
|
||||
if (this.isTotalMembersZero) {
|
||||
return '123';
|
||||
}
|
||||
|
||||
if (!this.dashboardStats.newsletterSubscribers) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return formatNumber(this.dashboardStats.newsletterSubscribers[this.status]);
|
||||
}
|
||||
|
||||
get dataEmailsSent() {
|
||||
return this.dashboardStats.emailsSent30d ?? 0;
|
||||
}
|
||||
|
||||
get hasPaidTiers() {
|
||||
return this.dashboardStats.siteStatus?.hasPaidTiers;
|
||||
}
|
||||
|
||||
get totalMembers() {
|
||||
return this.dashboardStats.memberCounts?.total ?? 0;
|
||||
}
|
||||
|
||||
get isTotalMembersZero() {
|
||||
return this.dashboardStats.memberCounts && this.totalMembers === 0;
|
||||
}
|
||||
}
|
30
ghost/admin/app/components/dashboard/charts/overview.hbs
Normal file
30
ghost/admin/app/components/dashboard/charts/overview.hbs
Normal file
@ -0,0 +1,30 @@
|
||||
<section class="gh-dashboard-section gh-dashboard-overview {{unless this.isTotalMembersMoreThanZero 'is-hidden'}}">
|
||||
<article {{did-insert this.loadCharts}} class="gh-dashboard-box is-secondary">
|
||||
<div class="gh-dashboard-columns">
|
||||
<div class="gh-dashboard-column">
|
||||
<Dashboard::Parts::Metric
|
||||
@label={{gh-pluralize this.totalMembers "Total member" without-count=true}}
|
||||
@value={{this.totalMembersFormatted}}
|
||||
@trends={{this.hasTrends}}
|
||||
@percentage={{this.totalMembersTrend}}
|
||||
@large={{true}} />
|
||||
</div>
|
||||
<div class="gh-dashboard-column">
|
||||
<Dashboard::Parts::Metric
|
||||
@label={{gh-pluralize this.paidMembers "Paid member" without-count=true}}
|
||||
@value={{this.paidMembersFormatted}}
|
||||
@trends={{this.hasTrends}}
|
||||
@percentage={{this.paidMembersTrend}}
|
||||
@large={{true}} />
|
||||
</div>
|
||||
<div class="gh-dashboard-column">
|
||||
<Dashboard::Parts::Metric
|
||||
@label={{gh-pluralize this.freeMembers "Free member" without-count=true}}
|
||||
@value={{this.freeMembersFormatted}}
|
||||
@trends={{this.hasTrends}}
|
||||
@percentage={{this.freeMembersTrend}}
|
||||
@large={{true}} />
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
88
ghost/admin/app/components/dashboard/charts/overview.js
Normal file
88
ghost/admin/app/components/dashboard/charts/overview.js
Normal file
@ -0,0 +1,88 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {formatNumber} from '../../../helpers/format-number';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default class Overview extends Component {
|
||||
@service dashboardStats;
|
||||
|
||||
@action
|
||||
loadCharts() {
|
||||
this.dashboardStats.loadMemberCountStats();
|
||||
}
|
||||
|
||||
get loading() {
|
||||
return this.dashboardStats.memberCountStats === null;
|
||||
}
|
||||
|
||||
get totalMembers() {
|
||||
return this.dashboardStats.memberCounts?.total ?? 0;
|
||||
}
|
||||
|
||||
get isTotalMembersMoreThanZero() {
|
||||
return this.dashboardStats.memberCounts && this.totalMembers > 0;
|
||||
}
|
||||
|
||||
get paidMembers() {
|
||||
return this.dashboardStats.memberCounts?.paid ?? 0;
|
||||
}
|
||||
|
||||
get freeMembers() {
|
||||
return this.dashboardStats.memberCounts?.free ?? 0;
|
||||
}
|
||||
|
||||
get totalMembersFormatted() {
|
||||
if (this.dashboardStats.memberCounts === null) {
|
||||
return '-';
|
||||
}
|
||||
return formatNumber(this.totalMembers);
|
||||
}
|
||||
|
||||
get paidMembersFormatted() {
|
||||
if (this.dashboardStats.memberCounts === null) {
|
||||
return '-';
|
||||
}
|
||||
return formatNumber(this.paidMembers);
|
||||
}
|
||||
|
||||
get freeMembersFormatted() {
|
||||
if (this.dashboardStats.memberCounts === null) {
|
||||
return '-';
|
||||
}
|
||||
return formatNumber(this.freeMembers);
|
||||
}
|
||||
|
||||
get hasTrends() {
|
||||
return this.dashboardStats.memberCounts !== null
|
||||
&& this.dashboardStats.memberCountsTrend !== null
|
||||
&& this.dashboardStats.currentMRR !== null
|
||||
&& this.dashboardStats.currentMRRTrend !== null;
|
||||
}
|
||||
|
||||
get totalMembersTrend() {
|
||||
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.total, this.dashboardStats.memberCounts.total);
|
||||
}
|
||||
|
||||
get paidMembersTrend() {
|
||||
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.paid, this.dashboardStats.memberCounts.paid);
|
||||
}
|
||||
|
||||
get freeMembersTrend() {
|
||||
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.free, this.dashboardStats.memberCounts.free);
|
||||
}
|
||||
|
||||
get hasPaidTiers() {
|
||||
return this.dashboardStats.siteStatus?.hasPaidTiers;
|
||||
}
|
||||
|
||||
calculatePercentage(from, to) {
|
||||
if (from === 0) {
|
||||
if (to > 0) {
|
||||
return 100;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.round((to - from) / from * 100);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<div class="gh-dashboard-minichart gh-dashboard-breakdown">
|
||||
<div class="gh-dashboard-content">
|
||||
<div class="gh-dashboard-data">
|
||||
<Dashboard::Parts::Metric
|
||||
@label={{this.chartTitle}} />
|
||||
|
||||
<div class="gh-dashboard-legend">
|
||||
<div class="gh-dashboard-legend-item">New</div>
|
||||
<div class="gh-dashboard-legend-item">Canceled</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-chart" {{did-insert this.loadCharts}}>
|
||||
{{#if this.loading}}
|
||||
<div class="gh-dashboard-chart-loading">
|
||||
<div class="gh-loading-spinner"></div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-chart-container">
|
||||
<div class="gh-dashboard-chart-box">
|
||||
<EmberChart
|
||||
@type={{this.chartType}}
|
||||
@data={{this.chartData}}
|
||||
@options={{this.chartOptions}}
|
||||
@height={{110}} />
|
||||
</div>
|
||||
|
||||
<div id="gh-dashboard-breakdown-tooltip" class="gh-dashboard-tooltip">
|
||||
<div class="gh-dashboard-tooltip-label">
|
||||
-
|
||||
</div>
|
||||
<div class="gh-dashboard-tooltip-value">
|
||||
<div class="gh-dashboard-tooltip-value-1"><span class="indicator solid"></span><span class="value"></span></div>
|
||||
<div class="gh-dashboard-tooltip-value-1"><span class="metric">New</span></div>
|
||||
<div class="gh-dashboard-tooltip-value-2"><span class="indicator solid"></span><span class="value"></span></div>
|
||||
<div class="gh-dashboard-tooltip-value-2"><span class="metric">Canceled</span></div>
|
||||
<div class="gh-dashboard-tooltip-value-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
344
ghost/admin/app/components/dashboard/charts/paid-breakdown.js
Normal file
344
ghost/admin/app/components/dashboard/charts/paid-breakdown.js
Normal file
@ -0,0 +1,344 @@
|
||||
/* globals Chart */
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import moment from 'moment';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
const DATE_FORMAT = 'D MMM, YYYY';
|
||||
|
||||
// Custom ChartJS rounded rectangle
|
||||
Chart.elements.RoundedRectangle = Chart.elements.Rectangle.extend({
|
||||
draw: function () {
|
||||
var ctx = this._chart.ctx;
|
||||
var vm = this._view;
|
||||
var left, right, top, bottom, borderSkipped, radius;
|
||||
|
||||
// If radius is less than 0 or is large enough to cause drawing errors a max
|
||||
// radius is imposed. If cornerRadius is not defined set it to 0.
|
||||
var cornerRadius = this._chart.config.options.cornerRadius;
|
||||
var fullCornerRadius = this._chart.config.options.fullCornerRadius;
|
||||
var stackedRounded = this._chart.config.options.stackedRounded;
|
||||
|
||||
if (cornerRadius < 0) {
|
||||
cornerRadius = 0;
|
||||
}
|
||||
if (typeof cornerRadius === 'undefined') {
|
||||
cornerRadius = 0;
|
||||
}
|
||||
if (typeof fullCornerRadius === 'undefined') {
|
||||
fullCornerRadius = true;
|
||||
}
|
||||
if (typeof stackedRounded === 'undefined') {
|
||||
stackedRounded = false;
|
||||
}
|
||||
|
||||
left = vm.x - vm.width / 2;
|
||||
right = vm.x + vm.width / 2;
|
||||
top = vm.y;
|
||||
bottom = vm.base;
|
||||
borderSkipped = vm.borderSkipped || 'bottom';
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = vm.backgroundColor;
|
||||
ctx.strokeStyle = vm.borderColor;
|
||||
|
||||
// Corner points, from bottom-left to bottom-right clockwise
|
||||
// | 1 2 |
|
||||
// | 0 3 |
|
||||
var corners = [
|
||||
[left, bottom],
|
||||
[left, top],
|
||||
[right, top],
|
||||
[right, bottom]
|
||||
];
|
||||
|
||||
// Find first (starting) corner with fallback to 'bottom'
|
||||
var borders = ['bottom', 'left', 'top', 'right'];
|
||||
var startCorner = borders.indexOf(borderSkipped, 0);
|
||||
if (startCorner === -1) {
|
||||
startCorner = 0;
|
||||
}
|
||||
|
||||
function cornerAt(index) {
|
||||
return corners[(startCorner + index) % 4];
|
||||
}
|
||||
|
||||
// Draw rectangle from 'startCorner'
|
||||
var corner = cornerAt(0);
|
||||
ctx.moveTo(corner[0], corner[1]);
|
||||
|
||||
var nextCornerId, width, height, x, y;
|
||||
for (var i = 1; i < 4; i++) {
|
||||
corner = cornerAt(i);
|
||||
nextCornerId = i + 1;
|
||||
if (nextCornerId === 4) {
|
||||
nextCornerId = 0;
|
||||
}
|
||||
|
||||
width = corners[2][0] - corners[1][0];
|
||||
height = corners[0][1] - corners[1][1];
|
||||
x = corners[1][0];
|
||||
y = corners[1][1];
|
||||
|
||||
radius = cornerRadius;
|
||||
// Fix radius being too large
|
||||
if (radius > Math.abs(height) / 2) {
|
||||
radius = Math.floor(Math.abs(height) / 2);
|
||||
}
|
||||
if (radius > Math.abs(width) / 2) {
|
||||
radius = Math.floor(Math.abs(width) / 2);
|
||||
}
|
||||
|
||||
var xTL, xTR, yTL, yTR, xBL, xBR, yBL, yBR;
|
||||
if (height < 0) {
|
||||
// Negative values in a standard bar chart
|
||||
xTL = x;
|
||||
xTR = x + width;
|
||||
yTL = y + height;
|
||||
yTR = y + height;
|
||||
|
||||
xBL = x;
|
||||
xBR = x + width;
|
||||
yBL = y;
|
||||
yBR = y;
|
||||
|
||||
// Draw
|
||||
ctx.moveTo(xBL + radius, yBL);
|
||||
ctx.lineTo(xBR - radius, yBR);
|
||||
|
||||
// bottom right
|
||||
ctx.quadraticCurveTo(xBR, yBR, xBR, yBR - radius);
|
||||
ctx.lineTo(xTR, yTR + radius);
|
||||
|
||||
// top right
|
||||
ctx.lineTo(xTR, yTR, xTR - radius, yTR);
|
||||
ctx.lineTo(xTL + radius, yTL);
|
||||
|
||||
// top left
|
||||
ctx.lineTo(xTL, yTL, xTL, yTL + radius);
|
||||
ctx.lineTo(xBL, yBL - radius);
|
||||
|
||||
// bottom left
|
||||
ctx.quadraticCurveTo(xBL, yBL, xBL + radius, yBL);
|
||||
} else {
|
||||
// Positive values in a standard bar chart
|
||||
ctx.moveTo(x + radius, y);
|
||||
ctx.lineTo(x + width - radius, y);
|
||||
|
||||
// top right
|
||||
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
||||
ctx.lineTo(x + width, y + height - radius);
|
||||
|
||||
// bottom right
|
||||
ctx.lineTo(x + width, y + height, x + width - radius, y + height);
|
||||
|
||||
ctx.lineTo(x + radius, y + height);
|
||||
ctx.lineTo(x, y + height, x, y + height - radius);
|
||||
ctx.lineTo(x, y + radius);
|
||||
|
||||
// top left
|
||||
ctx.quadraticCurveTo(x, y, x + radius, y);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.fill();
|
||||
}
|
||||
});
|
||||
|
||||
Chart.defaults.hoverBar = Chart.defaults.bar;
|
||||
Chart.controllers.hoverBar = Chart.controllers.bar.extend({
|
||||
draw: function (ease) {
|
||||
Chart.controllers.bar.prototype.draw.call(this, ease);
|
||||
|
||||
var ctx = this.chart.ctx;
|
||||
|
||||
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
|
||||
let activePoint = this.chart.tooltip._active[0],
|
||||
x = activePoint.tooltipPosition().x,
|
||||
topY = this.chart.legend.bottom,
|
||||
bottomY = this.chart.chartArea.bottom;
|
||||
|
||||
// draw line
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, topY);
|
||||
ctx.lineTo(x, bottomY);
|
||||
ctx.setLineDash([3, 4]);
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = '#7C8B9A';
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
},
|
||||
dataElementType: Chart.elements.RoundedRectangle
|
||||
});
|
||||
|
||||
export default class PaidBreakdown extends Component {
|
||||
@service dashboardStats;
|
||||
@service feature;
|
||||
|
||||
@action
|
||||
loadCharts() {
|
||||
this.dashboardStats.loadSubscriptionCountStats();
|
||||
}
|
||||
|
||||
get loading() {
|
||||
return this.dashboardStats.subscriptionCountStats === null;
|
||||
}
|
||||
|
||||
get chartTitle() {
|
||||
return 'Paid subscribers';
|
||||
}
|
||||
|
||||
get chartType() {
|
||||
return 'hoverBar';
|
||||
}
|
||||
|
||||
get chartData() {
|
||||
const stats = this.dashboardStats.filledSubscriptionCountStats;
|
||||
const labels = stats.map(stat => stat.date);
|
||||
const newData = stats.map(stat => stat.signups);
|
||||
const canceledData = stats.map(stat => -stat.cancellations);
|
||||
let barThickness = 5;
|
||||
|
||||
if (newData.length >= 30 + 1 && newData.length < 90) {
|
||||
barThickness = 3.5;
|
||||
} else if (newData.length >= 90) {
|
||||
barThickness = 1.5;
|
||||
}
|
||||
|
||||
return {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
data: newData,
|
||||
backgroundColor: '#8E42FF',
|
||||
cubicInterpolationMode: 'monotone',
|
||||
barThickness: barThickness,
|
||||
minBarLength: 3
|
||||
}, {
|
||||
data: canceledData,
|
||||
backgroundColor: '#FB76B4',
|
||||
cubicInterpolationMode: 'monotone',
|
||||
barThickness: barThickness,
|
||||
minBarLength: 3
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
get chartOptions() {
|
||||
const barColor = this.feature.nightShift ? 'rgba(200, 204, 217, 0.25)' : 'rgba(200, 204, 217, 0.65)';
|
||||
|
||||
return {
|
||||
responsive: true,
|
||||
cornerRadius: 50,
|
||||
fullCornerRadius: false,
|
||||
maintainAspectRatio: false,
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
top: 4,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0
|
||||
}
|
||||
},
|
||||
hover: {
|
||||
onHover: function (e) {
|
||||
e.target.style.cursor = 'pointer';
|
||||
}
|
||||
},
|
||||
animation: false,
|
||||
responsiveAnimationDuration: 1,
|
||||
tooltips: {
|
||||
enabled: false,
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
custom: function (tooltip) {
|
||||
// get tooltip element
|
||||
const tooltipEl = document.getElementById('gh-dashboard-breakdown-tooltip');
|
||||
const chartContainerEl = tooltipEl.parentElement;
|
||||
const chartWidth = chartContainerEl.offsetWidth;
|
||||
const tooltipWidth = tooltipEl.offsetWidth;
|
||||
|
||||
// only show tooltip when active
|
||||
if (tooltip.opacity === 0) {
|
||||
tooltipEl.style.display = 'none';
|
||||
tooltipEl.style.opacity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
let offsetX = 0;
|
||||
if (tooltip.x > chartWidth - tooltipWidth) {
|
||||
offsetX = tooltipWidth - 10;
|
||||
}
|
||||
|
||||
// update tooltip styles
|
||||
tooltipEl.style.display = 'block';
|
||||
tooltipEl.style.opacity = 1;
|
||||
tooltipEl.style.position = 'absolute';
|
||||
tooltipEl.style.left = tooltip.x - offsetX + 'px';
|
||||
tooltipEl.style.top = '70px';
|
||||
},
|
||||
callbacks: {
|
||||
label: (tooltipItems, data) => {
|
||||
// new data
|
||||
let newValue = parseInt(data.datasets[0].data[tooltipItems.index].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','));
|
||||
document.querySelector('#gh-dashboard-breakdown-tooltip .gh-dashboard-tooltip-value-1 .value').innerHTML = `${newValue}`;
|
||||
|
||||
// canceld data
|
||||
let canceledValue = Math.abs(parseInt(data.datasets[1].data[tooltipItems.index].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')));
|
||||
document.querySelector('#gh-dashboard-breakdown-tooltip .gh-dashboard-tooltip-value-2 .value').innerHTML = `${canceledValue}`;
|
||||
},
|
||||
title: (tooltipItems) => {
|
||||
const value = moment(tooltipItems[0].xLabel).format(DATE_FORMAT);
|
||||
document.querySelector('#gh-dashboard-breakdown-tooltip .gh-dashboard-tooltip-label').innerHTML = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
offset: false,
|
||||
gridLines: {
|
||||
drawTicks: false,
|
||||
display: true,
|
||||
drawBorder: false,
|
||||
color: 'rgba(255, 255, 255, 0.1)',
|
||||
lineWidth: 0,
|
||||
zeroLineColor: barColor,
|
||||
zeroLineWidth: 1
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
fontColor: '#7C8B9A',
|
||||
padding: 8,
|
||||
precision: 0
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
offset: true,
|
||||
stacked: true,
|
||||
gridLines: {
|
||||
color: barColor,
|
||||
borderDash: [4,4],
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
drawTicks: false,
|
||||
zeroLineWidth: 1,
|
||||
zeroLineColor: barColor,
|
||||
zeroLineBorderDash: [4,4]
|
||||
},
|
||||
ticks: {
|
||||
display: false
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
57
ghost/admin/app/components/dashboard/charts/paid-mix.hbs
Normal file
57
ghost/admin/app/components/dashboard/charts/paid-mix.hbs
Normal file
@ -0,0 +1,57 @@
|
||||
<div class="gh-dashboard-minichart gh-dashboard-mix {{if this.isChartCadence 'is-cadence'}}">
|
||||
<div class="gh-dashboard-content">
|
||||
<div class="gh-dashboard-data">
|
||||
<Dashboard::Parts::Metric
|
||||
@label="Paid mix" />
|
||||
|
||||
{{#if this.isChartCadence}}
|
||||
<div class="gh-dashboard-legend">
|
||||
<div class="gh-dashboard-legend-item">Monthly</div>
|
||||
<div class="gh-dashboard-legend-item">Annual</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="gh-dashboard-chart" {{did-insert this.loadCharts}}>
|
||||
{{#if this.loading}}
|
||||
<div class="gh-dashboard-chart-loading">
|
||||
<div class="gh-loading-spinner"></div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-chart-container">
|
||||
<div class="gh-dashboard-chart-box">
|
||||
<EmberChart
|
||||
@type={{this.chartType}}
|
||||
@data={{this.chartData}}
|
||||
@options={{this.chartOptions}}
|
||||
@height={{110}} />
|
||||
</div>
|
||||
|
||||
<div id="gh-dashboard-mix-tooltip" class="gh-dashboard-tooltip">
|
||||
<div class="gh-dashboard-tooltip-value">
|
||||
-
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.hasMultipleTiers }}
|
||||
<div class="gh-dashboard-select">
|
||||
<PowerSelect
|
||||
@selected={{this.selectedModeOption}}
|
||||
@options={{this.modeOptions}}
|
||||
@searchEnabled={{false}}
|
||||
@onChange={{this.onSwitchMode}}
|
||||
@triggerComponent="gh-power-select/trigger"
|
||||
@triggerClass="gh-contentfilter-menu-trigger"
|
||||
@dropdownClass="gh-contentfilter-menu-dropdown is-narrow"
|
||||
@matchTriggerWidth={{false}}
|
||||
@horizontalPosition="right"
|
||||
as |option|
|
||||
>
|
||||
{{#if option.name}}{{option.name}}{{else}}<span class="red">Unknown option</span>{{/if}}
|
||||
</PowerSelect>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
524
ghost/admin/app/components/dashboard/charts/paid-mix.js
Normal file
524
ghost/admin/app/components/dashboard/charts/paid-mix.js
Normal file
@ -0,0 +1,524 @@
|
||||
/* globals Chart */
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const MODE_OPTIONS = [{
|
||||
name: 'Cadence',
|
||||
value: 'cadence'
|
||||
}, {
|
||||
name: 'Tiers',
|
||||
value: 'tiers'
|
||||
}];
|
||||
|
||||
// Custom ChartJS rounded rectangle
|
||||
Chart.elements.Rectangle.prototype.draw = function () {
|
||||
var ctx = this._chart.ctx;
|
||||
var vm = this._view;
|
||||
var left, right, top, bottom, borderSkipped, radius;
|
||||
|
||||
// If radius is less than 0 or is large enough to cause drawing errors a max
|
||||
// radius is imposed. If cornerRadius is not defined set it to 0.
|
||||
var cornerRadius = this._chart.config.options.cornerRadius;
|
||||
var fullCornerRadius = this._chart.config.options.fullCornerRadius;
|
||||
var stackedRounded = this._chart.config.options.stackedRounded;
|
||||
var typeOfChart = this._chart.config.type;
|
||||
|
||||
if (cornerRadius < 0) {
|
||||
cornerRadius = 0;
|
||||
}
|
||||
if (typeof cornerRadius === 'undefined') {
|
||||
cornerRadius = 0;
|
||||
}
|
||||
if (typeof fullCornerRadius === 'undefined') {
|
||||
fullCornerRadius = true;
|
||||
}
|
||||
if (typeof stackedRounded === 'undefined') {
|
||||
stackedRounded = false;
|
||||
}
|
||||
|
||||
if (!vm.horizontal) {
|
||||
// bar
|
||||
left = vm.x - vm.width / 2;
|
||||
right = vm.x + vm.width / 2;
|
||||
top = vm.y;
|
||||
bottom = vm.base;
|
||||
borderSkipped = vm.borderSkipped || 'bottom';
|
||||
} else {
|
||||
// horizontal bar
|
||||
left = vm.base;
|
||||
right = vm.x;
|
||||
top = vm.y - vm.height / 2;
|
||||
bottom = vm.y + vm.height / 2;
|
||||
borderSkipped = vm.borderSkipped || 'left';
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = vm.backgroundColor;
|
||||
ctx.strokeStyle = vm.borderColor;
|
||||
|
||||
// Corner points, from bottom-left to bottom-right clockwise
|
||||
// | 1 2 |
|
||||
// | 0 3 |
|
||||
var corners = [
|
||||
[left, bottom],
|
||||
[left, top],
|
||||
[right, top],
|
||||
[right, bottom]
|
||||
];
|
||||
|
||||
// Find first (starting) corner with fallback to 'bottom'
|
||||
var borders = ['bottom', 'left', 'top', 'right'];
|
||||
var startCorner = borders.indexOf(borderSkipped, 0);
|
||||
if (startCorner === -1) {
|
||||
startCorner = 0;
|
||||
}
|
||||
|
||||
function cornerAt(index) {
|
||||
return corners[(startCorner + index) % 4];
|
||||
}
|
||||
|
||||
// Draw rectangle from 'startCorner'
|
||||
var corner = cornerAt(0);
|
||||
ctx.moveTo(corner[0], corner[1]);
|
||||
|
||||
var nextCornerId, width, height, x, y;
|
||||
for (var i = 1; i < 4; i++) {
|
||||
corner = cornerAt(i);
|
||||
nextCornerId = i + 1;
|
||||
if (nextCornerId === 4) {
|
||||
nextCornerId = 0;
|
||||
}
|
||||
|
||||
width = corners[2][0] - corners[1][0];
|
||||
height = corners[0][1] - corners[1][1];
|
||||
x = corners[1][0];
|
||||
y = corners[1][1];
|
||||
|
||||
radius = cornerRadius;
|
||||
// Fix radius being too large
|
||||
if (radius > Math.abs(height) / 2) {
|
||||
radius = Math.floor(Math.abs(height) / 2);
|
||||
}
|
||||
if (radius > Math.abs(width) / 2) {
|
||||
radius = Math.floor(Math.abs(width) / 2);
|
||||
}
|
||||
|
||||
var xTL, xTR, yTL, yTR, xBL, xBR, yBL, yBR;
|
||||
if (width < 0) {
|
||||
// Negative values in a horizontal bar chart
|
||||
xTL = x + width;
|
||||
xTR = x;
|
||||
yTL = y;
|
||||
yTR = y;
|
||||
|
||||
xBL = x + width;
|
||||
xBR = x;
|
||||
yBL = y + height;
|
||||
yBR = y + height;
|
||||
|
||||
// Draw
|
||||
ctx.moveTo(xBL + radius, yBL);
|
||||
ctx.lineTo(xBR - radius, yBR);
|
||||
|
||||
// Bottom right corner
|
||||
fullCornerRadius ? ctx.quadraticCurveTo(xBR, yBR, xBR, yBR - radius) : ctx.lineTo(xBR, yBR, xBR, yBR - radius);
|
||||
ctx.lineTo(xTR, yTR + radius);
|
||||
|
||||
// top right Corner
|
||||
fullCornerRadius ? ctx.quadraticCurveTo(xTR, yTR, xTR - radius, yTR) : ctx.lineTo(xTR, yTR, xTR - radius, yTR);
|
||||
ctx.lineTo(xTL + radius, yTL);
|
||||
|
||||
// top left corner
|
||||
ctx.quadraticCurveTo(xTL, yTL, xTL, yTL + radius);
|
||||
ctx.lineTo(xBL, yBL - radius);
|
||||
|
||||
// bttom left corner
|
||||
ctx.quadraticCurveTo(xBL, yBL, xBL + radius, yBL);
|
||||
} else {
|
||||
var lastVisible = 0;
|
||||
for (var findLast = 0, findLastTo = this._chart.data.datasets.length; findLast < findLastTo; findLast++) {
|
||||
if (!this._chart.getDatasetMeta(findLast).hidden) {
|
||||
lastVisible = findLast;
|
||||
}
|
||||
}
|
||||
var rounded = this._datasetIndex === lastVisible;
|
||||
|
||||
if (rounded) {
|
||||
//Positive Value
|
||||
ctx.moveTo(x + radius, y);
|
||||
ctx.lineTo(x + width - radius, y);
|
||||
|
||||
// top right
|
||||
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
||||
ctx.lineTo(x + width, y + height - radius);
|
||||
|
||||
// bottom right
|
||||
if (fullCornerRadius || typeOfChart === 'horizontalBar') {
|
||||
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
||||
} else {
|
||||
ctx.lineTo(x + width, y + height, x + width - radius, y + height);
|
||||
}
|
||||
|
||||
ctx.lineTo(x + radius, y + height);
|
||||
|
||||
// bottom left
|
||||
if (fullCornerRadius) {
|
||||
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
||||
} else {
|
||||
ctx.lineTo(x, y + height, x, y + height - radius);
|
||||
}
|
||||
|
||||
ctx.lineTo(x, y + radius);
|
||||
|
||||
// top left
|
||||
if (fullCornerRadius || typeOfChart === 'bar') {
|
||||
ctx.quadraticCurveTo(x, y, x + radius, y);
|
||||
} else {
|
||||
ctx.lineTo(x, y, x + radius, y);
|
||||
}
|
||||
} else {
|
||||
ctx.moveTo(x, y);
|
||||
ctx.lineTo(x + width, y);
|
||||
ctx.lineTo(x + width, y + height);
|
||||
ctx.lineTo(x, y + height);
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
export default class PaidMix extends Component {
|
||||
@service dashboardStats;
|
||||
|
||||
/**
|
||||
* Call this method when you need to fetch new data from the server.
|
||||
*/
|
||||
@action
|
||||
loadCharts() {
|
||||
this.dashboardStats.loadMemberCountStats();
|
||||
// The dashboard stats service will take care or reusing and limiting API-requests between charts
|
||||
if (this.mode === 'cadence') {
|
||||
this.dashboardStats.loadPaidMembersByCadence();
|
||||
} else {
|
||||
this.dashboardStats.loadPaidMembersByTier();
|
||||
}
|
||||
}
|
||||
|
||||
@tracked mode = 'cadence';
|
||||
modeOptions = MODE_OPTIONS;
|
||||
|
||||
get selectedModeOption() {
|
||||
return this.modeOptions.find(option => option.value === this.mode);
|
||||
}
|
||||
|
||||
get hasMultipleTiers() {
|
||||
return this.dashboardStats.siteStatus?.hasMultipleTiers;
|
||||
}
|
||||
|
||||
get totalMembers() {
|
||||
return this.dashboardStats.memberCounts?.total ?? 0;
|
||||
}
|
||||
|
||||
get isTotalMembersZero() {
|
||||
return this.dashboardStats.memberCounts?.total === 0;
|
||||
}
|
||||
|
||||
@action
|
||||
onSwitchMode(selected) {
|
||||
this.mode = selected.value;
|
||||
|
||||
if (this.loading) {
|
||||
// We don't have the data yet for the newly selected mode
|
||||
this.loadCharts();
|
||||
}
|
||||
}
|
||||
|
||||
get loading() {
|
||||
if (this.mode === 'cadence') {
|
||||
return this.dashboardStats.paidMembersByCadence === null;
|
||||
}
|
||||
return this.dashboardStats.paidMembersByTier === null;
|
||||
}
|
||||
|
||||
get chartType() {
|
||||
return 'horizontalBar';
|
||||
}
|
||||
|
||||
get areTiersAllZero() {
|
||||
if (this.dashboardStats.paidMembersByTier === null || this.dashboardStats.paidMembersByTier.length === 0) {
|
||||
return true;
|
||||
}
|
||||
const data = this.dashboardStats.paidMembersByTier.map(stat => stat.members);
|
||||
let areAllTiersZero = true;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i] > 0) {
|
||||
areAllTiersZero = false;
|
||||
}
|
||||
}
|
||||
return areAllTiersZero;
|
||||
}
|
||||
|
||||
get chartData() {
|
||||
const totalCadence = this.dashboardStats.paidMembersByCadence.month + this.dashboardStats.paidMembersByCadence.year;
|
||||
const monthlyPercentage = Math.round(this.dashboardStats.paidMembersByCadence.month / totalCadence * 100);
|
||||
const annualPercentage = Math.round(this.dashboardStats.paidMembersByCadence.year / totalCadence * 100);
|
||||
const barThickness = 5;
|
||||
|
||||
if (this.mode === 'cadence') {
|
||||
// there has to be negative values to make rounded corners work
|
||||
// empty chart render when there is no data
|
||||
if (totalCadence === 0 && !this.isTotalMembersZero) {
|
||||
return {
|
||||
labels: ['Cadence'],
|
||||
datasets: [{
|
||||
label: 'Monthly',
|
||||
data: [-50],
|
||||
backgroundColor: '#F3F6F8',
|
||||
barThickness
|
||||
},{
|
||||
label: 'Annual',
|
||||
data: [50],
|
||||
backgroundColor: '#EBEEF0',
|
||||
barThickness
|
||||
}]
|
||||
};
|
||||
|
||||
// fake colorful data for underneath empty state
|
||||
} else if (this.isTotalMembersZero) {
|
||||
return {
|
||||
labels: ['Cadence'],
|
||||
datasets: [{
|
||||
label: 'Monthly',
|
||||
data: [-40],
|
||||
backgroundColor: '#8E42FF',
|
||||
barThickness
|
||||
},{
|
||||
label: 'Annual',
|
||||
data: [60],
|
||||
backgroundColor: '#FB76B4',
|
||||
barThickness
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
labels: ['Cadence'],
|
||||
datasets: [{
|
||||
label: 'Monthly',
|
||||
data: [-monthlyPercentage],
|
||||
backgroundColor: '#8E42FF',
|
||||
barThickness
|
||||
}, {
|
||||
label: 'Annual',
|
||||
data: [annualPercentage],
|
||||
backgroundColor: '#FB76B4',
|
||||
barThickness
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// if it's for tiers...
|
||||
const labels = this.dashboardStats.paidMembersByTier.map(stat => stat.tier.name);
|
||||
const data = this.dashboardStats.paidMembersByTier.map(stat => stat.members);
|
||||
const colors = ['#853EED', '#CA3FED', '#E993CC', '#DB7777', '#EE9696', '#FEC7C0', '#853EED', '#CA3FED', '#E993CC', '#DB7777', '#EE9696', '#FEC7C0'];
|
||||
const zeroColors = ['#E6E9EB', '#EEF1F2', '#F6F8FA', '#EEF1F2', '#E6E9EB', '#EEF1F2', '#F6F8FA', '#EEF1F2', '#E6E9EB', '#EEF1F2', '#F6F8FA', '#EEF1F2'];
|
||||
let datasets = [];
|
||||
let totalTiersAmount;
|
||||
|
||||
// tiers all have 0 data
|
||||
if (this.areTiersAllZero) {
|
||||
totalTiersAmount = 100;
|
||||
let equalPercentageData = Math.round(100 / data.length);
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
data[i] = equalPercentageData;
|
||||
}
|
||||
// tiers have good data
|
||||
} else {
|
||||
totalTiersAmount = 0;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
totalTiersAmount += data[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (!this.areTiersAllZero && data[i] === 0) {
|
||||
// If not all tiers are zero, hide tiers with 0 members, because
|
||||
// they are not visible in the graph and can break rounded corners
|
||||
continue;
|
||||
}
|
||||
|
||||
let tierPercentage = Math.round(data[i] / totalTiersAmount * 100);
|
||||
|
||||
// The first value has to be negative to make rounded corners work
|
||||
if (i === 0) {
|
||||
tierPercentage = -tierPercentage;
|
||||
}
|
||||
datasets.push({
|
||||
data: [tierPercentage],
|
||||
label: labels[i],
|
||||
backgroundColor: this.areTiersAllZero ? zeroColors[i] : colors[i],
|
||||
barThickness
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
labels: ['Tiers'],
|
||||
datasets
|
||||
};
|
||||
}
|
||||
|
||||
get chartOptions() {
|
||||
let that = this;
|
||||
let ticksY = {display: false};
|
||||
let totalCadence = this.dashboardStats.paidMembersByCadence.month + this.dashboardStats.paidMembersByCadence.year;
|
||||
let minTickValue = -(Math.round(this.dashboardStats.paidMembersByCadence.month / totalCadence * 100));
|
||||
let maxTickValue = Math.round(this.dashboardStats.paidMembersByCadence.year / totalCadence * 100);
|
||||
|
||||
// this is for cadence...
|
||||
if (this.mode === 'cadence') {
|
||||
// for when it's empty
|
||||
if (totalCadence === 0) {
|
||||
minTickValue = -50;
|
||||
maxTickValue = 50;
|
||||
}
|
||||
|
||||
ticksY = {
|
||||
display: false,
|
||||
min: minTickValue,
|
||||
max: maxTickValue
|
||||
};
|
||||
|
||||
// this is for tiers...
|
||||
} else {
|
||||
if (!this.areTiersAllZero) {
|
||||
let data = this.dashboardStats.paidMembersByTier.map(stat => stat.members);
|
||||
let totalTiersAmount = 0;
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
totalTiersAmount += data[i];
|
||||
}
|
||||
|
||||
let negativeTierPercentage = Math.round(data[0] / totalTiersAmount * 100);
|
||||
|
||||
ticksY = {
|
||||
display: false,
|
||||
min: -negativeTierPercentage,
|
||||
max: 100 - negativeTierPercentage // take the negative away from 100 to create a full width bar
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
cornerRadius: 50,
|
||||
fullCornerRadius: false,
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
top: 72,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 4
|
||||
}
|
||||
},
|
||||
animation: false,
|
||||
responsiveAnimationDuration: 1,
|
||||
hover: {
|
||||
onHover: function (e) {
|
||||
e.target.style.cursor = 'pointer';
|
||||
}
|
||||
},
|
||||
tooltips: {
|
||||
enabled: false,
|
||||
intersect: false,
|
||||
mode: 'single',
|
||||
custom: function (tooltip) {
|
||||
// get tooltip element
|
||||
const tooltipEl = document.getElementById('gh-dashboard-mix-tooltip');
|
||||
const chartContainerEl = tooltipEl.parentElement;
|
||||
const chartWidth = chartContainerEl.offsetWidth;
|
||||
const tooltipWidth = tooltipEl.offsetWidth;
|
||||
|
||||
// only show tooltip when active
|
||||
if (tooltip.opacity === 0) {
|
||||
tooltipEl.style.opacity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
let offsetX = 0;
|
||||
|
||||
if (that.mode === 'cadence') {
|
||||
// these adjustments should match the special width and margin values in css
|
||||
if (tooltip.x > (chartWidth * 0.69) - tooltipWidth) {
|
||||
offsetX = tooltipWidth - 10;
|
||||
}
|
||||
offsetX -= (chartWidth * 0.30);
|
||||
} else {
|
||||
if (tooltip.x > chartWidth - tooltipWidth) {
|
||||
offsetX = tooltipWidth - 10;
|
||||
}
|
||||
}
|
||||
|
||||
// update tooltip styles
|
||||
tooltipEl.style.opacity = 1;
|
||||
tooltipEl.style.position = 'absolute';
|
||||
tooltipEl.style.left = tooltip.x - offsetX + 'px';
|
||||
tooltipEl.style.top = '30px';
|
||||
},
|
||||
callbacks: {
|
||||
label: (tooltipItems, data) => {
|
||||
const tooltipTextEl = document.querySelector('#gh-dashboard-mix-tooltip .gh-dashboard-tooltip-value');
|
||||
const label = data.datasets[tooltipItems.datasetIndex].label || '';
|
||||
var value = data.datasets[tooltipItems.datasetIndex].data[tooltipItems.index] || 0;
|
||||
if (value < 0) {
|
||||
value = -value;
|
||||
}
|
||||
if (that.isTotalMembersZero || totalCadence === 0) {
|
||||
value = 0;
|
||||
} else {
|
||||
value += '%';
|
||||
}
|
||||
tooltipTextEl.innerHTML = `<span class="indicator solid" style="background-color: ${data.datasets[tooltipItems.datasetIndex].backgroundColor}"></span><span class="value">${value}</span><span class="metric">${label}</span>`;
|
||||
},
|
||||
title: () => {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
stacked: true,
|
||||
gridLines: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
display: false
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
stacked: true,
|
||||
gridLines: {
|
||||
display: false
|
||||
},
|
||||
ticks: ticksY
|
||||
}]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get isChartCadence() {
|
||||
return (this.mode === 'cadence');
|
||||
}
|
||||
|
||||
get isChartTiers() {
|
||||
return (this.mode === 'tiers');
|
||||
}
|
||||
}
|
39
ghost/admin/app/components/dashboard/charts/paid-mrr.hbs
Normal file
39
ghost/admin/app/components/dashboard/charts/paid-mrr.hbs
Normal file
@ -0,0 +1,39 @@
|
||||
<div class="gh-dashboard-minichart gh-dashboard-mrr">
|
||||
<div class="gh-dashboard-content">
|
||||
<div class="gh-dashboard-data">
|
||||
<Dashboard::Parts::Metric
|
||||
@label={{this.chartTitle}}
|
||||
@value="{{this.currentMRRFormatted}}"
|
||||
@trends={{this.hasTrends}}
|
||||
@percentage={{this.mrrTrend}} />
|
||||
</div>
|
||||
<div class="gh-dashboard-chart">
|
||||
{{#if this.loading}}
|
||||
<div class="gh-dashboard-chart-loading">
|
||||
<div class="gh-loading-spinner"></div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-chart-container">
|
||||
<div class="gh-dashboard-chart-box">
|
||||
<EmberChart
|
||||
@type={{this.chartType}}
|
||||
@data={{this.chartData}}
|
||||
@options={{this.chartOptions}}
|
||||
@height={{110}} />
|
||||
</div>
|
||||
|
||||
<div id="gh-dashboard-mrr-tooltip" class="gh-dashboard-tooltip">
|
||||
<div class="gh-dashboard-tooltip-label">
|
||||
-
|
||||
</div>
|
||||
<div class="gh-dashboard-tooltip-value">
|
||||
<span class="indicator line"></span>
|
||||
<span class="value">-</span>
|
||||
<span class="metric">MRR</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
477
ghost/admin/app/components/dashboard/charts/paid-mrr.js
Normal file
477
ghost/admin/app/components/dashboard/charts/paid-mrr.js
Normal file
@ -0,0 +1,477 @@
|
||||
/* global Chart */
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import moment from 'moment';
|
||||
import {getSymbol} from 'ghost-admin/utils/currency';
|
||||
import {ghPriceAmount} from '../../../helpers/gh-price-amount';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
const DATE_FORMAT = 'D MMM, YYYY';
|
||||
|
||||
// custom ChartJS draw function
|
||||
Chart.defaults.hoverLine = Chart.defaults.line;
|
||||
Chart.controllers.hoverLine = Chart.controllers.line.extend({
|
||||
draw: function (ease) {
|
||||
Chart.controllers.line.prototype.draw.call(this, ease);
|
||||
|
||||
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
|
||||
let activePoint = this.chart.tooltip._active[0],
|
||||
ctx = this.chart.ctx,
|
||||
x = activePoint.tooltipPosition().x,
|
||||
topY = this.chart.legend.bottom,
|
||||
bottomY = this.chart.chartArea.bottom;
|
||||
|
||||
// draw line
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, topY);
|
||||
ctx.lineTo(x, bottomY);
|
||||
ctx.setLineDash([3, 4]);
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = '#7C8B9A';
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default class PaidMrr extends Component {
|
||||
@service dashboardStats;
|
||||
@service feature;
|
||||
|
||||
get loading() {
|
||||
return this.dashboardStats.mrrStats === null;
|
||||
}
|
||||
|
||||
get currentMRR() {
|
||||
return this.dashboardStats.currentMRR ?? 0;
|
||||
}
|
||||
|
||||
get mrrTrend() {
|
||||
return this.calculatePercentage(this.dashboardStats.currentMRRTrend, this.dashboardStats.currentMRR);
|
||||
}
|
||||
|
||||
get hasTrends() {
|
||||
return this.dashboardStats.currentMRR !== null
|
||||
&& this.dashboardStats.currentMRRTrend !== null;
|
||||
}
|
||||
|
||||
get chartTitle() {
|
||||
return 'MRR';
|
||||
}
|
||||
|
||||
get chartType() {
|
||||
return 'hoverLine'; // uses custom ChartJS draw function
|
||||
}
|
||||
|
||||
get totalMembers() {
|
||||
return this.dashboardStats.memberCounts?.total ?? 0;
|
||||
}
|
||||
|
||||
get isTotalMembersZero() {
|
||||
return this.dashboardStats.memberCounts && this.totalMembers === 0;
|
||||
}
|
||||
|
||||
get mrrCurrencySymbol() {
|
||||
if (this.dashboardStats.mrrStats === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const firstCurrency = this.dashboardStats.mrrStats[0] ? this.dashboardStats.mrrStats[0].currency : 'usd';
|
||||
return getSymbol(firstCurrency);
|
||||
}
|
||||
|
||||
get currentMRRFormatted() {
|
||||
// fake empty data
|
||||
if (this.isTotalMembersZero) {
|
||||
return '$123';
|
||||
}
|
||||
|
||||
if (this.dashboardStats.mrrStats === null) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const valueText = ghPriceAmount(this.currentMRR, {cents: false});
|
||||
return `${this.mrrCurrencySymbol}${valueText}`;
|
||||
}
|
||||
|
||||
get chartData() {
|
||||
let stats = this.dashboardStats.filledMrrStats;
|
||||
let labels = stats.map(stat => stat.date);
|
||||
let data = stats.map(stat => stat.mrr);
|
||||
|
||||
// with no members yet, let's show empty state with dummy data
|
||||
if (this.isTotalMembersZero) {
|
||||
stats = this.emptyData.stats;
|
||||
labels = this.emptyData.labels;
|
||||
data = this.emptyData.data;
|
||||
}
|
||||
|
||||
// gradient for fill
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, 120);
|
||||
gradient.addColorStop(0, 'rgba(143, 66, 255, 0.15');
|
||||
gradient.addColorStop(1, 'rgba(143, 66, 255, 0.0');
|
||||
|
||||
return {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
data: data,
|
||||
tension: 1,
|
||||
cubicInterpolationMode: 'monotone',
|
||||
fill: false,
|
||||
fillColor: gradient,
|
||||
backgroundColor: gradient,
|
||||
pointRadius: 0,
|
||||
pointHitRadius: 10,
|
||||
pointBorderColor: '#8E42FF',
|
||||
pointBackgroundColor: '#8E42FF',
|
||||
pointHoverBackgroundColor: '#8E42FF',
|
||||
pointHoverBorderColor: '#8E42FF',
|
||||
pointHoverRadius: 0,
|
||||
borderColor: '#8E42FF',
|
||||
borderJoinStyle: 'miter'
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
get chartOptions() {
|
||||
const that = this;
|
||||
const barColor = this.feature.nightShift ? 'rgba(200, 204, 217, 0.25)' : 'rgba(200, 204, 217, 0.65)';
|
||||
|
||||
return {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
top: 2,
|
||||
bottom: 2,
|
||||
left: 0,
|
||||
right: 0
|
||||
}
|
||||
},
|
||||
hover: {
|
||||
onHover: function (e) {
|
||||
e.target.style.cursor = 'pointer';
|
||||
}
|
||||
},
|
||||
animation: false,
|
||||
responsiveAnimationDuration: 1,
|
||||
tooltips: {
|
||||
enabled: false,
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
custom: function (tooltip) {
|
||||
// get tooltip element
|
||||
const tooltipEl = document.getElementById('gh-dashboard-mrr-tooltip');
|
||||
const chartContainerEl = tooltipEl.parentElement;
|
||||
const chartWidth = chartContainerEl.offsetWidth;
|
||||
const tooltipWidth = tooltipEl.offsetWidth;
|
||||
|
||||
// only show tooltip when active
|
||||
if (tooltip.opacity === 0) {
|
||||
tooltipEl.style.opacity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
let offsetX = 0;
|
||||
if (tooltip.x > chartWidth - tooltipWidth) {
|
||||
offsetX = tooltipWidth - 10;
|
||||
}
|
||||
|
||||
// update tooltip styles
|
||||
tooltipEl.style.opacity = 1;
|
||||
tooltipEl.style.position = 'absolute';
|
||||
tooltipEl.style.left = tooltip.x - offsetX + 'px';
|
||||
tooltipEl.style.top = tooltip.y + 'px';
|
||||
},
|
||||
callbacks: {
|
||||
label: (tooltipItems, data) => {
|
||||
const value = `${that.mrrCurrencySymbol}${ghPriceAmount(data.datasets[tooltipItems.datasetIndex].data[tooltipItems.index], {cents: false})}`;
|
||||
document.querySelector('#gh-dashboard-mrr-tooltip .gh-dashboard-tooltip-value .value').innerHTML = value;
|
||||
},
|
||||
title: (tooltipItems) => {
|
||||
const value = moment(tooltipItems[0].xLabel).format(DATE_FORMAT);
|
||||
document.querySelector('#gh-dashboard-mrr-tooltip .gh-dashboard-tooltip-label').innerHTML = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
display: true,
|
||||
gridLines: {
|
||||
drawTicks: false,
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
color: 'transparent',
|
||||
zeroLineColor: barColor,
|
||||
zeroLineWidth: 1
|
||||
},
|
||||
ticks: {
|
||||
display: false
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
display: true,
|
||||
scaleLabel: {
|
||||
align: 'start'
|
||||
},
|
||||
gridLines: {
|
||||
color: barColor,
|
||||
borderDash: [4,4],
|
||||
display: false,
|
||||
drawBorder: true,
|
||||
drawTicks: false,
|
||||
zeroLineWidth: 1,
|
||||
zeroLineColor: barColor,
|
||||
zeroLineBorderDash: [4,4]
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
beginAtZero: true
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// used for empty state
|
||||
get emptyData() {
|
||||
return {
|
||||
stats: [
|
||||
{
|
||||
date: '2022-04-07',
|
||||
mrr: 0,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-08',
|
||||
mrr: 0,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-09',
|
||||
mrr: 1500,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-10',
|
||||
mrr: 2000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-11',
|
||||
mrr: 4500,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-12',
|
||||
mrr: 7500,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-13',
|
||||
mrr: 11000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-14',
|
||||
mrr: 12500,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-15',
|
||||
mrr: 14500,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-16',
|
||||
mrr: 18000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-17',
|
||||
mrr: 21500,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-18',
|
||||
mrr: 25000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-19',
|
||||
mrr: 28000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-20',
|
||||
mrr: 30000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-21',
|
||||
mrr: 34000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-22',
|
||||
mrr: 35000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-23',
|
||||
mrr: 35500,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-24',
|
||||
mrr: 37000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-25',
|
||||
mrr: 38000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-26',
|
||||
mrr: 40500,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-27',
|
||||
mrr: 43500,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-28',
|
||||
mrr: 47000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-29',
|
||||
mrr: 48000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-04-30',
|
||||
mrr: 50500,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-05-01',
|
||||
mrr: 53500,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-05-02',
|
||||
mrr: 55000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-05-03',
|
||||
mrr: 56500,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-05-04',
|
||||
mrr: 57000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-05-05',
|
||||
mrr: 58000,
|
||||
currency: 'usd'
|
||||
},
|
||||
{
|
||||
date: '2022-05-06',
|
||||
mrr: 58500,
|
||||
currency: 'usd'
|
||||
}
|
||||
],
|
||||
labels: [
|
||||
'2022-04-07',
|
||||
'2022-04-08',
|
||||
'2022-04-09',
|
||||
'2022-04-10',
|
||||
'2022-04-11',
|
||||
'2022-04-12',
|
||||
'2022-04-13',
|
||||
'2022-04-14',
|
||||
'2022-04-15',
|
||||
'2022-04-16',
|
||||
'2022-04-17',
|
||||
'2022-04-18',
|
||||
'2022-04-19',
|
||||
'2022-04-20',
|
||||
'2022-04-21',
|
||||
'2022-04-22',
|
||||
'2022-04-23',
|
||||
'2022-04-24',
|
||||
'2022-04-25',
|
||||
'2022-04-26',
|
||||
'2022-04-27',
|
||||
'2022-04-28',
|
||||
'2022-04-29',
|
||||
'2022-04-30',
|
||||
'2022-05-01',
|
||||
'2022-05-02',
|
||||
'2022-05-03',
|
||||
'2022-05-04',
|
||||
'2022-05-05',
|
||||
'2022-05-06'
|
||||
],
|
||||
data: [
|
||||
0,
|
||||
1500,
|
||||
4000,
|
||||
5000,
|
||||
9000,
|
||||
11500,
|
||||
22500,
|
||||
26000,
|
||||
30000,
|
||||
30000,
|
||||
31000,
|
||||
33000,
|
||||
33500,
|
||||
35500,
|
||||
36500,
|
||||
36500,
|
||||
40000,
|
||||
40500,
|
||||
43500,
|
||||
47000,
|
||||
49000,
|
||||
49500,
|
||||
50000,
|
||||
50000,
|
||||
53000,
|
||||
56000,
|
||||
58000,
|
||||
61000,
|
||||
63500,
|
||||
63500
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
calculatePercentage(from, to) {
|
||||
if (from === 0) {
|
||||
if (to > 0) {
|
||||
return 100;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.round((to - from) / from * 100);
|
||||
}
|
||||
}
|
131
ghost/admin/app/components/dashboard/charts/recents.hbs
Normal file
131
ghost/admin/app/components/dashboard/charts/recents.hbs
Normal file
@ -0,0 +1,131 @@
|
||||
<section class="gh-dashboard-section gh-dashboard-recents" {{did-insert this.loadPosts}}>
|
||||
<article class="gh-dashboard-box">
|
||||
<div class="gh-dashboard-tabs">
|
||||
<button type="button" class="gh-dashboard-tab {{if this.postsTabSelected 'is-selected'}}" {{on "click" this.changeTabToPosts}}>
|
||||
<Dashboard::Parts::Metric
|
||||
@label="Recent posts" />
|
||||
</button>
|
||||
{{#if this.areMembersEnabled}}
|
||||
<button type="button" class="gh-dashboard-tab {{if this.activityTabSelected 'is-selected'}}" {{on "click" this.changeTabToActivity}}>
|
||||
<Dashboard::Parts::Metric
|
||||
@label="Member activity" />
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if this.postsTabSelected}}
|
||||
<div class="gh-dashboard-recents-posts gh-dashboard-list {{unless this.areNewslettersEnabled 'is-single'}}">
|
||||
<div class="gh-dashboard-list-header">
|
||||
<div class="gh-dashboard-list-title">Title</div>
|
||||
{{#if this.areNewslettersEnabled}}
|
||||
<div class="gh-dashboard-list-title">Sends</div>
|
||||
<div class="gh-dashboard-list-title">Open rate</div>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-list-title">Published</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="gh-dashboard-list-body">
|
||||
{{#each this.posts as |post|}}
|
||||
<LinkTo class="gh-dashboard-list-item permalink" @route="editor.edit" @models={{array post.displayName post.id}}>
|
||||
<div class="gh-dashboard-list-item-sub">
|
||||
<span class="gh-dashboard-list-text">{{post.title}}</span>
|
||||
</div>
|
||||
{{#if this.areNewslettersEnabled}}
|
||||
<div class="gh-dashboard-list-item-sub">
|
||||
<span class="gh-dashboard-metric-minivalue {{unless post.email "na"}}">
|
||||
{{#if post.email}}
|
||||
{{format-number post.email.emailCount}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="gh-dashboard-list-item-sub">
|
||||
<span class="gh-dashboard-rate-bar">
|
||||
{{#if post.email}}
|
||||
<span class="gh-dashboard-metric-minivalue">{{post.email.openRate}}%</span>
|
||||
<span class="gh-dashboard-rate-amount"><span style={{html-safe (concat "width: " post.email.openRate "%;")}}/></span>
|
||||
{{else}}
|
||||
<span class="gh-dashboard-metric-minivalue na">—</span>
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-list-item-sub">
|
||||
<span class="gh-dashboard-list-subtext">{{moment-format (moment-site-tz post.publishedAtUTC) "DD MMM YYYY HH:mm"}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-list-empty">
|
||||
<p>No published posts yet.</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="gh-dashboard-list-footer">
|
||||
<LinkTo @route="posts" @query={{reset-query-params "posts"}}>See all posts →</LinkTo>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-recents-activity gh-dashboard-list" data-test-dashboard-member-activity>
|
||||
<div class="gh-dashboard-list-header">
|
||||
<div class="gh-dashboard-list-title">Member</div>
|
||||
<div class="gh-dashboard-list-title">Event</div>
|
||||
<div class="gh-dashboard-list-title">Time</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-list-body">
|
||||
{{#let (members-event-fetcher filter=(members-event-filter excludeEmailEvents=true) pageSize=5) as |eventsFetcher|}}
|
||||
{{#if eventsFetcher.isError}}
|
||||
<div class="gh-dashboard-list-error">
|
||||
<p>There was an error loading events</p>
|
||||
{{#if eventsFetcher.errorMessage}}
|
||||
<code>{{eventsFetcher.errorMessage}}</code>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if eventsFetcher.isLoading}}
|
||||
<div class="gh-dashboard-list-loading">
|
||||
<div class="gh-loading-spinner"></div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{#if eventsFetcher.data}}
|
||||
{{#each eventsFetcher.data as |event|}}
|
||||
{{#let (parse-member-event event eventsFetcher.hasMultipleNewsletters) as |parsedEvent|}}
|
||||
<div class="gh-dashboard-list-item member-details">
|
||||
<div class="gh-dashboard-list-item-sub">
|
||||
<GhMemberAvatar @member={{parsedEvent.member}} @containerClass="w8 h8 mr3 flex-shrink-0" />
|
||||
<LinkTo class="gh-dashboard-list-text" @route="member" @model="{{parsedEvent.memberId}}" data-test-dashboard-member-activity-item>{{parsedEvent.subject}}</LinkTo>
|
||||
</div>
|
||||
<div class="gh-dashboard-list-item-sub">
|
||||
{{svg-jar parsedEvent.icon}}
|
||||
<span class="gh-dashboard-list-subtext">
|
||||
{{capitalize-first-letter parsedEvent.action}}
|
||||
{{#if parsedEvent.url}}
|
||||
"<a class="ghost-members-activity-object-link" href="{{parsedEvent.url}}" target="_blank" rel="noopener noreferrer">{{parsedEvent.object}}</a>"
|
||||
{{else}}
|
||||
{{parsedEvent.object}}
|
||||
{{/if}}
|
||||
{{parsedEvent.info}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="gh-dashboard-list-item-sub">
|
||||
<span class="gh-dashboard-list-subtext">{{moment-format parsedEvent.timestamp "DD MMM YYYY HH:mm"}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{/let}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<div class="gh-dashboard-list-empty" data-test-no-member-activities>
|
||||
<p>No activity yet.</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
</div>
|
||||
<div class="gh-dashboard-list-footer">
|
||||
<LinkTo @route="members-activity" @query={{reset-query-params "members-activity"}}>See all activity →</LinkTo>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</article>
|
||||
</section>
|
43
ghost/admin/app/components/dashboard/charts/recents.js
Normal file
43
ghost/admin/app/components/dashboard/charts/recents.js
Normal file
@ -0,0 +1,43 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class Recents extends Component {
|
||||
@service store;
|
||||
@service dashboardStats;
|
||||
|
||||
@tracked selected = 'posts';
|
||||
@tracked posts = [];
|
||||
|
||||
@action
|
||||
async loadPosts() {
|
||||
this.posts = await this.store.query('post', {limit: 5, filter: 'status:[published,sent]', order: 'published_at desc'});
|
||||
}
|
||||
|
||||
@action
|
||||
changeTabToPosts() {
|
||||
this.selected = 'posts';
|
||||
}
|
||||
|
||||
@action
|
||||
changeTabToActivity() {
|
||||
this.selected = 'activity';
|
||||
}
|
||||
|
||||
get postsTabSelected() {
|
||||
return (this.selected === 'posts');
|
||||
}
|
||||
|
||||
get activityTabSelected() {
|
||||
return (this.selected === 'activity');
|
||||
}
|
||||
|
||||
get areMembersEnabled() {
|
||||
return this.dashboardStats.siteStatus?.membersEnabled;
|
||||
}
|
||||
|
||||
get areNewslettersEnabled() {
|
||||
return this.dashboardStats.siteStatus?.newslettersEnabled;
|
||||
}
|
||||
}
|
48
ghost/admin/app/components/dashboard/parts/metric.hbs
Normal file
48
ghost/admin/app/components/dashboard/parts/metric.hbs
Normal file
@ -0,0 +1,48 @@
|
||||
<div class="gh-dashboard-metric {{if @center "is-center"}} {{if @reverse "is-reverse"}} {{if @large "is-large"}}">
|
||||
<div class="gh-dashboard-metric-data">
|
||||
{{#if @secondary}}
|
||||
{{#if @value}}
|
||||
<div class="gh-dashboard-metric-value {{if @secondary 'is-secondary'}}">
|
||||
<span class="value">{{@value}}</span>
|
||||
{{#if @trends}}
|
||||
<Dashboard::Parts::Percentage @percentage={{@percentage}}/>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<h5 class="gh-dashboard-metric-label {{if @secondary 'is-secondary'}}">
|
||||
{{@label}}
|
||||
</h5>
|
||||
{{else}}
|
||||
{{#if @reverse}}
|
||||
{{#if @value}}
|
||||
<div class="gh-dashboard-metric-value">
|
||||
<span class="value">{{@value}}</span>
|
||||
{{#if @trends}}
|
||||
<Dashboard::Parts::Percentage @percentage={{@percentage}}/>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<h5 class="gh-dashboard-metric-label">
|
||||
{{@label}}
|
||||
</h5>
|
||||
{{else}}
|
||||
<h5 class="gh-dashboard-metric-label">
|
||||
{{@label}}
|
||||
</h5>
|
||||
{{#if @value}}
|
||||
<div class="gh-dashboard-metric-value">
|
||||
<span class="value">{{@value}}</span>
|
||||
{{#if @trends}}
|
||||
<Dashboard::Parts::Percentage @percentage={{@percentage}}/>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if @extra}}
|
||||
<div class="gh-dashboard-metric-extra">
|
||||
{{@extra}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
@ -0,0 +1,7 @@
|
||||
{{#if (gt @percentage 0) }}
|
||||
<div class="gh-dashboard-percentage is-positive">+{{ @percentage }}%</div>
|
||||
{{else if (lt @percentage 0)}}
|
||||
<div class="gh-dashboard-percentage is-negative">{{ @percentage }}%</div>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-percentage">0%</div>
|
||||
{{/if}}
|
7
ghost/admin/app/components/dashboard/parts/zero.hbs
Normal file
7
ghost/admin/app/components/dashboard/parts/zero.hbs
Normal file
@ -0,0 +1,7 @@
|
||||
<div class="gh-dashboard-zero">
|
||||
<div class="gh-dashboard-zero-message">
|
||||
<h4>Welcome to your Dashboard</h4>
|
||||
<p>You'll find member analytics here once<br />someone signs up.</p>
|
||||
<p><LinkTo @route="members">Add or import members →</LinkTo></p>
|
||||
</div>
|
||||
</div>
|
120
ghost/admin/app/components/dashboard/prototype/control-panel.hbs
Normal file
120
ghost/admin/app/components/dashboard/prototype/control-panel.hbs
Normal file
@ -0,0 +1,120 @@
|
||||
<div class="gh-main-section prototype-control-panel" {{did-insert this.onInsert}}>
|
||||
<h4 class="gh-main-section-header small bn">Prototype control panel</h4>
|
||||
|
||||
<div class="gh-expandable">
|
||||
<div class="gh-expandable-block">
|
||||
<div class="gh-expandable-header">
|
||||
<div>
|
||||
<h4 class="gh-expandable-title">Enable fake data</h4>
|
||||
<p class="gh-expandable-description">
|
||||
Replace real data with mocked data to test the dashboard
|
||||
</p>
|
||||
</div>
|
||||
<div class="for-switch">
|
||||
<label class="switch">
|
||||
<Input @type="checkbox" @checked={{this.enabled}} />
|
||||
<span class="input-toggle-component"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.enabled}}
|
||||
<div class="gh-expandable-block">
|
||||
<div class="gh-expandable-header">
|
||||
<div>
|
||||
<h4 class="gh-expandable-title">Emulated state</h4>
|
||||
<p class="gh-expandable-description">
|
||||
Choose a state you want to emulate for this site
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<PowerSelect
|
||||
@selected={{this.selectedStateOption}}
|
||||
@options={{this.stateOptions}}
|
||||
@searchEnabled={{false}}
|
||||
@onChange={{this.onStateChange}}
|
||||
@triggerComponent="gh-power-select/trigger"
|
||||
@triggerClass="gh-contentfilter-menu-trigger"
|
||||
@dropdownClass="gh-contentfilter-menu-dropdown"
|
||||
@matchTriggerWidth={{false}}
|
||||
as |option|
|
||||
>
|
||||
{{#if option.name}}{{option.name}}{{else}}<span class="red">Unknown option</span>{{/if}}
|
||||
</PowerSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gh-expandable-block">
|
||||
<div class="gh-expandable-header">
|
||||
<div>
|
||||
<h4 class="gh-expandable-title">Newsletters enabled</h4>
|
||||
<p class="gh-expandable-description">
|
||||
All email related charts shown
|
||||
</p>
|
||||
</div>
|
||||
<div class="for-switch">
|
||||
<label class="switch">
|
||||
<Input @type="checkbox" @checked={{this.mockNewslettersEnabled}} />
|
||||
<span class="input-toggle-component"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gh-expandable-block">
|
||||
<div class="gh-expandable-header">
|
||||
<div>
|
||||
<h4 class="gh-expandable-title">Members enabled</h4>
|
||||
<p class="gh-expandable-description">
|
||||
Setting subscription access to other value than 'Nobody'
|
||||
</p>
|
||||
</div>
|
||||
<div class="for-switch">
|
||||
<label class="switch">
|
||||
<Input @type="checkbox" @checked={{this.mockMembersEnabled}} />
|
||||
<span class="input-toggle-component"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.mockMembersEnabled}}
|
||||
<div class="gh-expandable-block">
|
||||
<div class="gh-expandable-header">
|
||||
<div>
|
||||
<h4 class="gh-expandable-title">Has paid tiers and Stripe connected</h4>
|
||||
<p class="gh-expandable-description">
|
||||
Paid memberships
|
||||
</p>
|
||||
</div>
|
||||
<div class="for-switch">
|
||||
<label class="switch">
|
||||
<Input @type="checkbox" @checked={{this.mockPaidTiers}} />
|
||||
<span class="input-toggle-component"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gh-expandable-block">
|
||||
<div class="gh-expandable-header">
|
||||
<div>
|
||||
<h4 class="gh-expandable-title">Has multiple paid tiers</h4>
|
||||
<p class="gh-expandable-description">
|
||||
Adds the ability to show paid mix per tier
|
||||
</p>
|
||||
</div>
|
||||
<div class="for-switch">
|
||||
<label class="switch">
|
||||
<Input @type="checkbox" @checked={{this.mockMultipleTiers}} />
|
||||
<span class="input-toggle-component"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
170
ghost/admin/app/components/dashboard/prototype/control-panel.js
vendored
Normal file
170
ghost/admin/app/components/dashboard/prototype/control-panel.js
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const STATE_OPTIONS = [
|
||||
{
|
||||
name: 'No data',
|
||||
value: {
|
||||
days: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '7 days',
|
||||
value: {
|
||||
days: 7
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '30 days',
|
||||
value: {
|
||||
days: 30
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '90 days',
|
||||
value: {
|
||||
days: 90
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'One year',
|
||||
value: {
|
||||
days: 365
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Two years',
|
||||
value: {
|
||||
days: 730
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export default class ControlPanel extends Component {
|
||||
@service dashboardStats;
|
||||
@service dashboardMocks;
|
||||
|
||||
stateOptions = STATE_OPTIONS;
|
||||
|
||||
@tracked state = STATE_OPTIONS[1].value;
|
||||
|
||||
@action
|
||||
onInsert() {
|
||||
this.loadState();
|
||||
this.dashboardMocks.updateMockedData(this.state);
|
||||
// Don't reload all (because then we would load unused graphs too)
|
||||
|
||||
if (this.enabled) {
|
||||
this.updateState();
|
||||
}
|
||||
}
|
||||
|
||||
saveState() {
|
||||
try {
|
||||
localStorage.setItem('dashboard5-prototype-state', JSON.stringify(this.state));
|
||||
localStorage.setItem('dashboard5-prototype-status', JSON.stringify(this.dashboardMocks.siteStatus));
|
||||
localStorage.setItem('dashboard5-prototype-enabled', JSON.stringify(this.enabled));
|
||||
} catch (e) {
|
||||
// ignore localStorage not supported errors
|
||||
}
|
||||
}
|
||||
|
||||
loadState() {
|
||||
try {
|
||||
const savedState = localStorage.getItem('dashboard5-prototype-state');
|
||||
if (savedState) {
|
||||
const parsed = JSON.parse(savedState);
|
||||
if (parsed) {
|
||||
this.state = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
const savedStatus = localStorage.getItem('dashboard5-prototype-status');
|
||||
if (savedStatus) {
|
||||
const parsed = JSON.parse(savedStatus);
|
||||
if (parsed) {
|
||||
this.dashboardMocks.siteStatus = {...this.dashboardMocks.siteStatus, ...parsed};
|
||||
}
|
||||
}
|
||||
|
||||
const enabledStr = localStorage.getItem('dashboard5-prototype-enabled');
|
||||
if (enabledStr) {
|
||||
const parsed = JSON.parse(enabledStr);
|
||||
if (typeof parsed === 'boolean' && parsed !== this.dashboardMocks.enabled) {
|
||||
this.dashboardMocks.enabled = parsed;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore localStorage not supported errors
|
||||
}
|
||||
}
|
||||
|
||||
get selectedStateOption() {
|
||||
return this.stateOptions.find(option => option.value.days === this.state.days);
|
||||
}
|
||||
|
||||
@action
|
||||
onStateChange(option) {
|
||||
this.state = option.value;
|
||||
this.updateMockedData();
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
@action
|
||||
updateMockedData() {
|
||||
this.dashboardMocks.updateMockedData(this.state);
|
||||
}
|
||||
|
||||
updateState() {
|
||||
this.dashboardStats.reloadAll();
|
||||
this.saveState();
|
||||
}
|
||||
|
||||
// Convenience mappers
|
||||
get enabled() {
|
||||
return this.dashboardMocks.enabled;
|
||||
}
|
||||
|
||||
set enabled(val) {
|
||||
this.dashboardMocks.enabled = val;
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
get mockPaidTiers() {
|
||||
return this.dashboardMocks.siteStatus?.hasPaidTiers;
|
||||
}
|
||||
|
||||
set mockPaidTiers(val) {
|
||||
this.dashboardMocks.siteStatus.hasPaidTiers = val;
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
get mockMultipleTiers() {
|
||||
return this.dashboardMocks.siteStatus?.hasMultipleTiers;
|
||||
}
|
||||
|
||||
set mockMultipleTiers(val) {
|
||||
this.dashboardMocks.siteStatus.hasMultipleTiers = val;
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
get mockNewslettersEnabled() {
|
||||
return this.dashboardMocks.siteStatus?.newslettersEnabled;
|
||||
}
|
||||
|
||||
set mockNewslettersEnabled(val) {
|
||||
this.dashboardMocks.siteStatus.newslettersEnabled = val;
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
get mockMembersEnabled() {
|
||||
return this.dashboardMocks.siteStatus?.membersEnabled;
|
||||
}
|
||||
|
||||
set mockMembersEnabled(val) {
|
||||
this.dashboardMocks.siteStatus.membersEnabled = val;
|
||||
this.updateState();
|
||||
}
|
||||
}
|
17
ghost/admin/app/components/dashboard/resources/community.hbs
Normal file
17
ghost/admin/app/components/dashboard/resources/community.hbs
Normal file
@ -0,0 +1,17 @@
|
||||
<section class="gh-dashboard-resource gh-dashboard-community">
|
||||
<a href="https://ghost.org/resources/community/" target="_blank" rel="noopener noreferrer" class="gh-dashboard-resource-box">
|
||||
<div class="gh-dashboard-resource-title">
|
||||
<h4>Ghost Creator Community</h4>
|
||||
</div>
|
||||
<div class="gh-dashboard-resource-body">
|
||||
<div class="gh-dashboard-list">
|
||||
<div class="gh-dashboard-list-body">
|
||||
<p>Talk strategy.<br />Get advice.<br />Or just hang out.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-resource-footer">
|
||||
Share the journey →
|
||||
</div>
|
||||
</a>
|
||||
</section>
|
@ -0,0 +1,24 @@
|
||||
<section class="gh-dashboard-resource gh-dashboard-newsletter" {{did-insert this.load}}>
|
||||
<article class="gh-dashboard-resource-box">
|
||||
<div class="gh-dashboard-resource-title">
|
||||
<h4>Latest from the newsletter</h4>
|
||||
</div>
|
||||
<div class="gh-dashboard-resource-body">
|
||||
{{#if (not (or this.loading this.error))}}
|
||||
<div class="gh-dashboard-resource-bigarticle">
|
||||
{{#each this.newsletters as |entry|}}
|
||||
<a class="gh-dashboard-resource-smallarticle" href={{set-query-params entry.url utm_source='admin'}} target="_blank" rel="noopener noreferrer">
|
||||
<div class="gh-dashboard-resource-text">
|
||||
<h3>{{entry.title}}</h3>
|
||||
<p>{{entry.excerpt}}</p>
|
||||
</div>
|
||||
</a>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="gh-dashboard-resource-footer">
|
||||
<a href="https://ghost.org/resources/newsletter/" target="_blank" class="gh-dashboard-subscribe-button" rel="noopener noreferrer">Subscribe <span>to the newsletter </span>→</a>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
44
ghost/admin/app/components/dashboard/resources/newsletter.js
Normal file
44
ghost/admin/app/components/dashboard/resources/newsletter.js
Normal file
@ -0,0 +1,44 @@
|
||||
import Component from '@glimmer/component';
|
||||
import fetch from 'fetch';
|
||||
import {action} from '@ember/object';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const API_URL = 'https://resources.ghost.io/resources';
|
||||
const API_KEY = 'b30afc1721f5d8d021ec3450ef';
|
||||
const NEWSLETTER_COUNT = 1;
|
||||
|
||||
export default class Newsletter extends Component {
|
||||
@tracked loading = null;
|
||||
@tracked error = null;
|
||||
@tracked newsletters = null;
|
||||
|
||||
@action
|
||||
load() {
|
||||
this.loading = true;
|
||||
this.fetch.perform().then(() => {
|
||||
this.loading = false;
|
||||
}).catch((error) => {
|
||||
this.error = error;
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@task
|
||||
*fetch() {
|
||||
const order = encodeURIComponent('published_at DESC');
|
||||
const key = encodeURIComponent(API_KEY);
|
||||
const limit = encodeURIComponent(NEWSLETTER_COUNT);
|
||||
const filter = encodeURIComponent('tag:newsletter');
|
||||
let response = yield fetch(`${API_URL}/ghost/api/content/posts/?limit=${limit}&order=${order}&key=${key}&include=none&filter=${filter}`);
|
||||
if (!response.ok) {
|
||||
// eslint-disable-next-line
|
||||
console.error('Failed to fetch newsletters', {response});
|
||||
this.error = 'Failed to fetch';
|
||||
return;
|
||||
}
|
||||
|
||||
let result = yield response.json();
|
||||
this.newsletters = result.posts || [];
|
||||
}
|
||||
}
|
23
ghost/admin/app/components/dashboard/resources/resources.hbs
Normal file
23
ghost/admin/app/components/dashboard/resources/resources.hbs
Normal file
@ -0,0 +1,23 @@
|
||||
<section class="gh-dashboard-resource gh-dashboard-resources" {{did-insert this.load}}>
|
||||
<article class="gh-dashboard-resource-box">
|
||||
{{#if (not (or this.loading this.error))}}
|
||||
<a href="{{this.resource.url}}" target="_blank" class="gh-dashboard-resource-thumbnail" rel="noopener noreferrer" style={{html-safe (concat "background-image: url(" this.resource.feature_image ")")}} aria-label="Resource link"></a>
|
||||
<div class="gh-dashboard-resource-contents">
|
||||
<div class="gh-dashboard-resource-title">
|
||||
<h4>Resources</h4>
|
||||
</div>
|
||||
<div class="gh-dashboard-resource-body">
|
||||
<a href="{{this.resource.url}}" target="_blank" class="gh-dashboard-resource-bigarticle" rel="noopener noreferrer">
|
||||
<div class="gh-dashboard-resource-text">
|
||||
<h3>{{this.resource.title}}</h3>
|
||||
<p>{{this.resource.excerpt}}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="gh-dashboard-resource-footer">
|
||||
<a href="https://ghost.org/resources/" target="_blank" rel="noopener noreferrer">Learn more →</a>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</article>
|
||||
</section>
|
76
ghost/admin/app/components/dashboard/resources/resources.js
Normal file
76
ghost/admin/app/components/dashboard/resources/resources.js
Normal file
@ -0,0 +1,76 @@
|
||||
import Component from '@glimmer/component';
|
||||
import fetch from 'fetch';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const API_URL = 'https://resources.ghost.io/resources';
|
||||
const API_KEY = 'b30afc1721f5d8d021ec3450ef';
|
||||
const RESOURCE_COUNT = 1;
|
||||
|
||||
export default class Resources extends Component {
|
||||
@service dashboardStats;
|
||||
@tracked loading = null;
|
||||
@tracked error = null;
|
||||
@tracked resources = null;
|
||||
@tracked resource = null;
|
||||
|
||||
@action
|
||||
load() {
|
||||
this.loading = true;
|
||||
this.fetch.perform().then(() => {
|
||||
this.loading = false;
|
||||
}).catch((error) => {
|
||||
this.error = error;
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
get tag() {
|
||||
// Depending on the state of the site, we might want to show resources from different tags.
|
||||
if (this.areMembersEnabled && this.hasPaidTiers) {
|
||||
return 'business';
|
||||
}
|
||||
|
||||
if (this.areMembersEnabled) {
|
||||
return 'growth';
|
||||
}
|
||||
return 'building';
|
||||
}
|
||||
|
||||
@task
|
||||
*fetch() {
|
||||
const order = encodeURIComponent('published_at DESC');
|
||||
const key = encodeURIComponent(API_KEY);
|
||||
const limit = encodeURIComponent(RESOURCE_COUNT);
|
||||
const filter = encodeURIComponent('tag:' + this.tag);
|
||||
let response = yield fetch(`${API_URL}/ghost/api/content/posts/?limit=${limit}&order=${order}&key=${key}&include=none&filter=${filter}`);
|
||||
if (!response.ok) {
|
||||
// eslint-disable-next-line
|
||||
console.error('Failed to fetch resources', {response});
|
||||
this.error = 'Failed to fetch';
|
||||
return;
|
||||
}
|
||||
|
||||
let result = yield response.json();
|
||||
this.resources = result.posts || [];
|
||||
this.resource = this.resources[0]; // just get the first
|
||||
}
|
||||
|
||||
get hasPaidTiers() {
|
||||
return this.dashboardStats.siteStatus?.hasPaidTiers;
|
||||
}
|
||||
|
||||
get areNewslettersEnabled() {
|
||||
return this.dashboardStats.siteStatus?.newslettersEnabled;
|
||||
}
|
||||
|
||||
get areMembersEnabled() {
|
||||
return this.dashboardStats.siteStatus?.membersEnabled;
|
||||
}
|
||||
|
||||
get hasNothingEnabled() {
|
||||
return (!this.areMembersEnabled && !this.areNewslettersEnabled && !this.hasPaidTiers);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<section class="gh-dashboard-resource gh-dashboard-staff-picks" {{did-insert this.load}}>
|
||||
<article class="gh-dashboard-resource-box">
|
||||
<div class="gh-dashboard-resource-title is-large has-border">
|
||||
<h4>Staff picks</h4>
|
||||
<p>Hand picked stories from around the web, published with Ghost.</p>
|
||||
</div>
|
||||
<div class="gh-dashboard-resource-body">
|
||||
<div class="gh-dashboard-list">
|
||||
{{#if (not (or this.loading this.error))}}
|
||||
<div class="gh-dashboard-list-body">
|
||||
{{#each this.staffPicks as |entry|}}
|
||||
<div class="gh-dashboard-list-item">
|
||||
<a class="gh-dashboard-list-post permalink" href={{set-query-params entry.link utm_source='ghost'}} target="_blank" rel="noopener noreferrer">
|
||||
<span class="gh-dashboard-list-link">
|
||||
<span>{{entry.title}}</span>
|
||||
</span>
|
||||
<div class="gh-dashboard-resource-secondary">{{entry.creator}}</div>
|
||||
</a>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-list-empty">
|
||||
<p>No staff picks yet.</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-resource-footer">
|
||||
<a href="https://twitter.com/ghoststaffpicks" target="_blank" rel="noopener noreferrer">{{svg-jar "twitter-logo"}} <span>Follow on Twitter</span></a>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
@ -0,0 +1,57 @@
|
||||
import Component from '@glimmer/component';
|
||||
import fetch from 'fetch';
|
||||
import {action} from '@ember/object';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const RSS_FEED_URL = 'https://zapier.com/engine/rss/678920/ghoststaffpicks';
|
||||
const LIMIT = 3;
|
||||
|
||||
export default class StaffPicks extends Component {
|
||||
@tracked loading = null;
|
||||
@tracked error = null;
|
||||
@tracked staffPicks = null;
|
||||
|
||||
@action
|
||||
load() {
|
||||
this.loading = true;
|
||||
this.fetch.perform().then(() => {
|
||||
this.loading = false;
|
||||
}, (error) => {
|
||||
this.error = error;
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@task
|
||||
*fetch() {
|
||||
let response = yield fetch(RSS_FEED_URL);
|
||||
if (!response.ok) {
|
||||
// eslint-disable-next-line
|
||||
console.error('Failed to fetch staff picks', {response});
|
||||
this.error = 'Failed to fetch';
|
||||
return;
|
||||
}
|
||||
|
||||
const str = yield response.text();
|
||||
const document = new DOMParser().parseFromString(str, 'text/xml');
|
||||
|
||||
const items = document.querySelectorAll('channel > item');
|
||||
this.staffPicks = [];
|
||||
|
||||
for (let index = 0; index < items.length && index < LIMIT; index += 1) {
|
||||
const item = items[index];
|
||||
const title = item.getElementsByTagName('title')[0].textContent;
|
||||
const link = item.getElementsByTagName('link')[0].textContent;
|
||||
const creator = item.getElementsByTagName('dc:creator')[0].textContent;
|
||||
|
||||
const entry = {
|
||||
title,
|
||||
link,
|
||||
creator
|
||||
};
|
||||
|
||||
this.staffPicks.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
33
ghost/admin/app/components/dashboard/resources/whats-new.hbs
Normal file
33
ghost/admin/app/components/dashboard/resources/whats-new.hbs
Normal file
@ -0,0 +1,33 @@
|
||||
<section class="gh-dashboard-resource gh-dashboard-whats-new" {{did-insert this.load}}>
|
||||
<article class="gh-dashboard-resource-box">
|
||||
<div class="gh-dashboard-resource-title is-large has-border">
|
||||
<h4>What's new</h4>
|
||||
<p>All the latest improvements.</p>
|
||||
</div>
|
||||
<div class="gh-dashboard-resource-body">
|
||||
<div class="gh-dashboard-list {{if this.whatsNew.hasNew "has-new"}}">
|
||||
{{#if (not (or this.loading this.error))}}
|
||||
<div class="gh-dashboard-list-body">
|
||||
{{#each this.entries as |entry|}}
|
||||
<div class="gh-dashboard-list-item">
|
||||
<LinkTo class="gh-dashboard-list-post" @route="whatsnew" @query={{hash entry=entry.slug}}>
|
||||
<span class="gh-dashboard-list-link">
|
||||
<span>{{entry.title}}</span>
|
||||
</span>
|
||||
<div class="gh-dashboard-resource-secondary">{{moment-format entry.published_at "D MMM YYYY"}}</div>
|
||||
</LinkTo>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-list-empty">
|
||||
<p>No new features yet.</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-resource-footer">
|
||||
<LinkTo @route="whatsnew" @query={{hash entry=null}} class="green">See more features →</LinkTo>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
24
ghost/admin/app/components/dashboard/resources/whats-new.js
Normal file
24
ghost/admin/app/components/dashboard/resources/whats-new.js
Normal file
@ -0,0 +1,24 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class WhatsNew extends Component {
|
||||
@service whatsNew;
|
||||
|
||||
@tracked entries = null;
|
||||
@tracked loading = null;
|
||||
@tracked error = null;
|
||||
|
||||
@action
|
||||
load() {
|
||||
this.loading = true;
|
||||
this.whatsNew.fetchLatest.perform().then(() => {
|
||||
this.loading = false;
|
||||
this.entries = this.whatsNew.entries.slice(0, 3);
|
||||
}, (error) => {
|
||||
this.error = error;
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
84
ghost/admin/app/components/editor/modals/preview.hbs
Normal file
84
ghost/admin/app/components/editor/modals/preview.hbs
Normal file
@ -0,0 +1,84 @@
|
||||
<div class="flex flex-column h-100">
|
||||
<header class="modal-header gh-post-preview-header" data-test-modal="preview-email">
|
||||
<div class="left">
|
||||
<button class="gh-btn-editor gh-editor-back-button" title="Close" type="button" {{on "click" @close}}>
|
||||
<span>{{svg-jar "arrow-left"}} Editor</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="gh-post-preview-btn-group">
|
||||
<div class="gh-contentfilter gh-btn-group">
|
||||
<button type="button" class="gh-btn {{if (eq this.tab "browser") "gh-btn-group-selected"}} gh-post-preview-mode" {{on "click" (fn this.changeTab "browser")}}><span>{{svg-jar "desktop"}}</span></button>
|
||||
<button type="button" class="gh-btn {{if (eq this.tab "mobile") "gh-btn-group-selected"}} gh-post-preview-mode" {{on "click" (fn this.changeTab "mobile")}}><span>{{svg-jar "mobile-phone"}}</span></button>
|
||||
{{#if (and (not-eq this.settings.membersSignupAccess "none") (not-eq this.settings.editorDefaultEmailRecipients "disabled"))}}
|
||||
{{#if (and @data.publishOptions.post.isPost (not @data.publishOptions.user.isContributor))}}
|
||||
<button type="button" class="gh-btn {{if (eq this.tab "email") "gh-btn-group-selected"}} gh-post-preview-mode" {{on "click" (fn this.changeTab "email")}}><span>{{svg-jar "email-unread"}}</span></button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<button type="button" class="gh-btn {{if (eq this.tab "social") "gh-btn-group-selected"}} gh-post-preview-mode" {{on "click" (fn this.changeTab "social")}}><span>{{svg-jar "twitter"}}</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor gh-editor-preview-trigger active"
|
||||
{{on "click" @close}}
|
||||
>
|
||||
<span>Preview</span>
|
||||
</button>
|
||||
|
||||
{{#if @data.publishOptions.user.isContributor}}
|
||||
<GhTaskButton
|
||||
@buttonText="Save"
|
||||
@task={{@data.saveTask}}
|
||||
@runningText="Saving"
|
||||
@class="gh-btn gh-btn-icon gh-btn-editor gh-editor-save-trigger contributor-save-button"
|
||||
data-test-preview-contributor-save />
|
||||
{{else}}
|
||||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor darkgrey gh-publish-trigger"
|
||||
{{on "click" @data.togglePreviewPublish}}
|
||||
>
|
||||
<span>Publish</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
<div class="settings-menu-toggle-spacer"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{#if this.saveFirstTask.isRunning}}
|
||||
<GhLoadingSpinner />
|
||||
{{else}}
|
||||
{{#if (eq this.tab "browser")}}
|
||||
<Editor::Modals::Preview::Browser
|
||||
@post={{@data.publishOptions.post}}
|
||||
@skipAnimation={{this.skipAnimation}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if (and (eq this.tab "mobile"))}}
|
||||
<Editor::Modals::Preview::Mobile
|
||||
@post={{@data.publishOptions.post}}
|
||||
@skipAnimation={{this.skipAnimation}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#unless @data.publishOptions.user.isContributor}}
|
||||
{{#if (and (eq this.tab "email") @data.publishOptions.post.isPost)}}
|
||||
<Editor::Modals::Preview::Email
|
||||
@post={{@data.publishOptions.post}}
|
||||
@newsletter={{@data.publishOptions.newsletter}}
|
||||
@skipAnimation={{this.skipAnimation}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
{{#if (eq this.tab "social")}}
|
||||
<Editor::Modals::Preview::Social
|
||||
@post={{@data.publishOptions.post}}
|
||||
@skipAnimation={{this.skipAnimation}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
48
ghost/admin/app/components/editor/modals/preview.js
Normal file
48
ghost/admin/app/components/editor/modals/preview.js
Normal file
@ -0,0 +1,48 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class EditorPostPreviewModal extends Component {
|
||||
@service settings;
|
||||
@service session;
|
||||
|
||||
static modalOptions = {
|
||||
className: 'fullscreen-modal-total-overlay publish-modal',
|
||||
omitBackdrop: true,
|
||||
ignoreBackdropClick: true
|
||||
};
|
||||
|
||||
@tracked tab = this.args.data.currentTab || 'browser';
|
||||
@tracked isChangingTab = false;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.saveFirstTask.perform();
|
||||
}
|
||||
|
||||
get skipAnimation() {
|
||||
return this.args.data.skipAnimation || this.isChangingTab;
|
||||
}
|
||||
|
||||
@action
|
||||
changeTab(tab) {
|
||||
this.tab = tab;
|
||||
this.isChangingTab = true;
|
||||
this.args.data.changeTab?.(tab);
|
||||
}
|
||||
|
||||
@task
|
||||
*saveFirstTask() {
|
||||
const {saveTask, publishOptions, hasDirtyAttributes} = this.args.data;
|
||||
|
||||
if (saveTask.isRunning) {
|
||||
return yield saveTask.last;
|
||||
}
|
||||
|
||||
if (publishOptions.post.isDraft && hasDirtyAttributes) {
|
||||
yield saveTask.perform();
|
||||
}
|
||||
}
|
||||
}
|
30
ghost/admin/app/components/editor/modals/preview/browser.hbs
Normal file
30
ghost/admin/app/components/editor/modals/preview/browser.hbs
Normal file
@ -0,0 +1,30 @@
|
||||
<div class="gh-post-preview-container gh-post-preview-browser-container {{unless @skipAnimation "fade-in"}}">
|
||||
<div class="gh-browserpreview-browser">
|
||||
<div class="tabs">
|
||||
<ul><li></li><li></li><li></li></ul>
|
||||
<div>
|
||||
{{#if (or @icon this.settings.icon)}}
|
||||
<span class="favicon"><img src={{or @icon this.settings.icon}} alt="icon"></span>
|
||||
{{else}}
|
||||
<span class="favicon default">{{svg-jar "default-favicon"}}</span>
|
||||
{{/if}}
|
||||
<span class="db truncate w-90">{{@post.previewUrl}}</span>
|
||||
<button type="button" {{on "click" (perform this.copyPreviewUrl)}} class="gh-post-preview-url">
|
||||
<span class="green-d1">
|
||||
{{#if this.copyPreviewUrl.isRunning}}
|
||||
{{svg-jar "check-circle" class="check v-mid mr1 ml2"}} Copied
|
||||
{{else}}
|
||||
{{svg-jar "copy" class="w4 ml3 v-mid fill-darkgrey"}}
|
||||
{{/if}}
|
||||
</span>
|
||||
</button>
|
||||
<a href={{@post.previewUrl}} target="_blank" rel="noopener noreferrer" class="gh-publish-preview-newtab">
|
||||
{{svg-jar "external"}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-browserpreview-iframecontainer">
|
||||
<iframe class="gh-pe-iframe" src={{@post.previewUrl}} title="Desktop browser post preview"></iframe>
|
||||
</div>
|
||||
</div>
|
11
ghost/admin/app/components/editor/modals/preview/browser.js
Normal file
11
ghost/admin/app/components/editor/modals/preview/browser.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Component from '@glimmer/component';
|
||||
import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
|
||||
export default class ModalPostPreviewBrowserComponent extends Component {
|
||||
@task
|
||||
*copyPreviewUrl() {
|
||||
copyTextToClipboard(this.args.post.previewUrl);
|
||||
yield timeout(this.isTesting ? 50 : 3000);
|
||||
}
|
||||
}
|
70
ghost/admin/app/components/editor/modals/preview/email.hbs
Normal file
70
ghost/admin/app/components/editor/modals/preview/email.hbs
Normal file
@ -0,0 +1,70 @@
|
||||
<div class="gh-post-preview-container gh-post-preview-email-container {{unless @skipAnimation "fade-in"}}">
|
||||
<div class="gh-post-preview-email-mockup">
|
||||
<div class="gh-pe-emailclient-sender">
|
||||
<p>
|
||||
<span class="strong">{{or this.newsletter.senderName this.settings.title}}</span> <{{full-email-address (or this.newsletter.senderEmail "noreply")}}>
|
||||
</p>
|
||||
<p><span class="dark">To:</span> Jamie Larson <jamie@example.com></p>
|
||||
</div>
|
||||
<iframe class="gh-pe-iframe" {{did-insert this.renderEmailPreview}} title="Email preview" sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox"></iframe>
|
||||
</div>
|
||||
{{!-- <div class="flex">
|
||||
<div class="gh-btn-group mr3">
|
||||
<button type="button" class="gh-btn {{if (eq this.memberSegment "status:free") "gh-btn-group-selected"}}" {{on "click" (fn this.changeMemberSegment "status:free")}}><span>Free member</span></button>
|
||||
<button type="button" class="gh-btn {{if (eq this.memberSegment "status:-free") "gh-btn-group-selected"}}" {{on "click" (fn this.changeMemberSegment "status:-free")}}><span>Paid member</span></button>
|
||||
</div>
|
||||
|
||||
<div class="gh-post-preview-email-input {{if this.sendPreviewEmailError "error"}}">
|
||||
<Input
|
||||
@value={{this.previewEmailAddress}}
|
||||
class="gh-input gh-post-preview-email-input"
|
||||
placeholder="you@yoursite.com"
|
||||
aria-invalid={{if this.sendPreviewEmailError "true"}}
|
||||
aria-describedby={{if this.sendPreviewEmailError "sendError"}}
|
||||
{{on-key "Enter" (perform this.sendPreviewEmailTask)}}
|
||||
/>
|
||||
{{#if this.sendPreviewEmailError}}
|
||||
<div class="error fixed nowrap f8 lh-heading"><span class="response" id="sendError">{{this.sendPreviewEmailError}}</span></div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<GhTaskButton
|
||||
@task={{this.sendPreviewEmailTask}}
|
||||
@buttonText="Send test email"
|
||||
@successText="Sent"
|
||||
@runningText="Sending..."
|
||||
@class="gh-btn gh-btn-green gh-btn-icon gh-post-preview-email-trigger"
|
||||
/>
|
||||
</div> --}}
|
||||
</div>
|
||||
<div class="gh-post-preview-email-footer">
|
||||
<div class="gh-post-preview-email-test">
|
||||
<div class="gh-btn-group mr3">
|
||||
<button type="button" class="gh-btn {{if (eq this.memberSegment "status:free") "gh-btn-group-selected"}}" {{on "click" (fn this.changeMemberSegment "status:free")}}><span>Free member</span></button>
|
||||
<button type="button" class="gh-btn {{if (eq this.memberSegment "status:-free") "gh-btn-group-selected"}}" {{on "click" (fn this.changeMemberSegment "status:-free")}}><span>Paid member</span></button>
|
||||
</div>
|
||||
|
||||
<div class="gh-post-preview-email-input {{if this.sendPreviewEmailError "error"}}">
|
||||
<Input
|
||||
@value={{this.previewEmailAddress}}
|
||||
class="gh-input gh-post-preview-email-input"
|
||||
placeholder="you@yoursite.com"
|
||||
aria-label="Email address to receive preview"
|
||||
aria-invalid={{if this.sendPreviewEmailError "true"}}
|
||||
aria-describedby={{if this.sendPreviewEmailError "sendError"}}
|
||||
{{on-key "Enter" (perform this.sendPreviewEmailTask)}}
|
||||
/>
|
||||
{{#if this.sendPreviewEmailError}}
|
||||
<div class="error fixed nowrap f8 lh-heading"><span class="response" id="sendError">{{this.sendPreviewEmailError}}</span></div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<GhTaskButton
|
||||
@task={{this.sendPreviewEmailTask}}
|
||||
@buttonText="Send test email"
|
||||
@successText="Sent"
|
||||
@runningText="Sending..."
|
||||
@class="gh-btn gh-btn-icon gh-post-preview-email-trigger"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
154
ghost/admin/app/components/editor/modals/preview/email.js
Normal file
154
ghost/admin/app/components/editor/modals/preview/email.js
Normal file
@ -0,0 +1,154 @@
|
||||
import Component from '@glimmer/component';
|
||||
import validator from 'validator';
|
||||
import {action} from '@ember/object';
|
||||
import {htmlSafe} from '@ember/template';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const INJECTED_CSS = `
|
||||
html::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
background: transparent
|
||||
}
|
||||
html {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
`;
|
||||
|
||||
// TODO: remove duplication with <ModalPostEmailPreview>
|
||||
export default class ModalPostPreviewEmailComponent extends Component {
|
||||
@service ajax;
|
||||
@service config;
|
||||
@service feature;
|
||||
@service ghostPaths;
|
||||
@service session;
|
||||
@service settings;
|
||||
@service store;
|
||||
|
||||
@tracked html = '';
|
||||
@tracked subject = '';
|
||||
@tracked memberSegment = 'status:free';
|
||||
@tracked previewEmailAddress = this.session.user.email;
|
||||
@tracked sendPreviewEmailError = '';
|
||||
|
||||
get newsletter() {
|
||||
return this.args.post.newsletter || this.args.newsletter;
|
||||
}
|
||||
|
||||
get mailgunIsEnabled() {
|
||||
return this.config.get('mailgunIsConfigured') ||
|
||||
!!(this.settings.get('mailgunApiKey') && this.settings.get('mailgunDomain') && this.settings.get('mailgunBaseUrl'));
|
||||
}
|
||||
|
||||
@action
|
||||
async renderEmailPreview(iframe) {
|
||||
this._previewIframe = iframe;
|
||||
|
||||
await this._fetchEmailData();
|
||||
// avoid timing issues when _fetchEmailData didn't perform any async ops
|
||||
await timeout(100);
|
||||
|
||||
if (iframe) {
|
||||
iframe.contentWindow.document.open();
|
||||
iframe.contentWindow.document.write(this.html);
|
||||
iframe.contentWindow.document.close();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
changeMemberSegment(segment) {
|
||||
this.memberSegment = segment;
|
||||
|
||||
if (this._previewIframe) {
|
||||
this.renderEmailPreview(this._previewIframe);
|
||||
}
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*sendPreviewEmailTask() {
|
||||
try {
|
||||
const resourceId = this.args.post.id;
|
||||
const testEmail = this.previewEmailAddress.trim();
|
||||
|
||||
if (!validator.isEmail(testEmail)) {
|
||||
this.sendPreviewEmailError = 'Please enter a valid email';
|
||||
return false;
|
||||
}
|
||||
if (!this.mailgunIsEnabled) {
|
||||
this.sendPreviewEmailError = 'Please verify your email settings';
|
||||
return false;
|
||||
}
|
||||
this.sendPreviewEmailError = '';
|
||||
|
||||
const url = this.ghostPaths.url.api('/email_previews/posts', resourceId);
|
||||
const data = {emails: [testEmail], memberSegment: this.memberSegment};
|
||||
const options = {
|
||||
data,
|
||||
dataType: 'json'
|
||||
};
|
||||
|
||||
yield this.ajax.post(url, options);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error) {
|
||||
let message = 'Email could not be sent, verify mail settings';
|
||||
|
||||
// grab custom error message if present
|
||||
if (
|
||||
error.payload && error.payload.errors
|
||||
&& error.payload.errors[0] && error.payload.errors[0].message) {
|
||||
message = htmlSafe(error.payload.errors[0].message);
|
||||
}
|
||||
|
||||
this.sendPreviewEmailError = message;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _fetchEmailData() {
|
||||
let {html, subject, memberSegment} = this;
|
||||
let {post} = this.args;
|
||||
|
||||
if (html && subject && memberSegment === this._lastMemberSegment) {
|
||||
return {html, subject};
|
||||
}
|
||||
|
||||
this._lastMemberSegment = memberSegment;
|
||||
|
||||
// model is an email
|
||||
if (post.html && post.subject) {
|
||||
html = post.html;
|
||||
subject = post.subject;
|
||||
// model is a post with an existing email
|
||||
} else if (post.email) {
|
||||
html = post.email.html;
|
||||
subject = post.email.subject;
|
||||
// model is a post, fetch email preview
|
||||
} else {
|
||||
let url = new URL(this.ghostPaths.url.api('/email_previews/posts', post.id), window.location.href);
|
||||
url.searchParams.set('memberSegment', this.memberSegment);
|
||||
url.searchParams.set('newsletter', this.newsletter.slug);
|
||||
|
||||
let response = await this.ajax.request(url.href);
|
||||
let [emailPreview] = response.email_previews;
|
||||
html = emailPreview.html;
|
||||
subject = emailPreview.subject;
|
||||
}
|
||||
|
||||
// inject extra CSS into the html for disabling links and scrollbars etc
|
||||
let domParser = new DOMParser();
|
||||
let htmlDoc = domParser.parseFromString(html, 'text/html');
|
||||
let stylesheet = htmlDoc.querySelector('style');
|
||||
let originalCss = stylesheet.innerHTML;
|
||||
stylesheet.innerHTML = `${originalCss}\n\n${INJECTED_CSS}`;
|
||||
|
||||
const doctype = new XMLSerializer().serializeToString(htmlDoc.doctype);
|
||||
html = doctype + htmlDoc.documentElement.outerHTML;
|
||||
|
||||
this.html = html;
|
||||
this.subject = subject;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<div class="modal-body modal-preview-email-content gh-pe-mobile-container gh-post-preview-container h-auto overflow-auto {{unless @skipAnimation "fade-in"}}">
|
||||
<div class="gh-pe-mobile-bezel">
|
||||
<div class="gh-pe-mobile-screen">
|
||||
<iframe class="gh-post-preview-iframe" src={{@post.previewUrl}} title="Mobile browser post preview"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
11
ghost/admin/app/components/editor/modals/preview/mobile.js
Normal file
11
ghost/admin/app/components/editor/modals/preview/mobile.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Component from '@glimmer/component';
|
||||
import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
|
||||
export default class ModalPostPreviewBrowserComponent extends Component {
|
||||
@task
|
||||
*copyPreviewUrl() {
|
||||
copyTextToClipboard(this.args.post.previewUrl);
|
||||
yield timeout(this.isTesting ? 50 : 3000);
|
||||
}
|
||||
}
|
263
ghost/admin/app/components/editor/modals/preview/social.hbs
Normal file
263
ghost/admin/app/components/editor/modals/preview/social.hbs
Normal file
@ -0,0 +1,263 @@
|
||||
<div class="gh-post-preview-container gh-post-preview-social-container {{unless @skipAnimation "fade-in"}}">
|
||||
<p class="mb8 fw5">This is how your content will look when shared, you can click on any elements you’d like to edit.</p>
|
||||
<div class="flex flex-column">
|
||||
<div class="flex gh-social-container-responsive">
|
||||
<div class="gh-social-og-container">
|
||||
<div class="flex ma3 mb2">
|
||||
<span>{{svg-jar "social-facebook" class="social-icon"}}</span>
|
||||
<div>
|
||||
<div class="gh-social-og-title">{{or this.settings.metaTitle this.settings.title}}</div>
|
||||
<div class="gh-social-og-time">12 hrs</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-column ma3 mt2">
|
||||
<span class="gh-social-og-desc w-100 mb2" />
|
||||
<span class="gh-social-og-desc w-100 mb2" />
|
||||
<span class="gh-social-og-desc w-60" />
|
||||
</div>
|
||||
<div
|
||||
class="gh-social-og-preview"
|
||||
{{on "mouseenter" (fn (mut this.facebookHovered) true)}}
|
||||
{{on "mouseleave" (fn (mut this.facebookHovered) false)}}
|
||||
>
|
||||
{{#if (and this.facebookHovered (not this.facebookImage))}}
|
||||
{{!-- only shown on hover when there's no image or fallback --}}
|
||||
<button class="gh-social-og-preview-img-add" type="button" {{on "click" (fn this.triggerFileDialog "facebook")}}>+ Add image</button>
|
||||
{{/if}}
|
||||
|
||||
<GhUploader
|
||||
@extensions={{this.imageExtensions}}
|
||||
@onComplete={{this.setFacebookImage}}
|
||||
as |uploader|
|
||||
>
|
||||
{{#each uploader.errors as |error|}}
|
||||
<div class="error pa2"><span class="response">{{or error.context error.message}}</span></div>
|
||||
{{/each}}
|
||||
|
||||
{{#if (or this.facebookImage uploader.isUploading)}}
|
||||
<div class="gh-social-og-preview-image relative" style={{background-image-style this.facebookImage}}>
|
||||
<div class="flex h-100 items-center justify-center">
|
||||
{{#if (or this.facebookHovered uploader.isUploading)}}
|
||||
{{#if uploader.isUploading}}
|
||||
{{uploader.progressBar}}
|
||||
{{else}}
|
||||
<button type="button" class="gh-btn gh-btn-white" {{on "click" (fn this.triggerFileDialog "facebook")}}><span>{{if @post.ogImage "Change" "Upload"}} image</span></button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if (and this.facebookHovered @post.ogImage)}}
|
||||
<button type="button" class="gh-btn gh-btn-black gh-btn-icon gh-social-preview-img-delete" title="Remove custom Facebook image" {{on "click" this.clearFacebookImage}}>
|
||||
<span>{{svg-jar "trash"}}</span>
|
||||
<span class="hidden">Remove custom Facebook image</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div style="display:none">
|
||||
<GhFileInput id="facebookFileInput" @multiple={{false}} @action={{uploader.setFiles}} @accept={{this.imageMimeTypes}} />
|
||||
</div>
|
||||
</GhUploader>
|
||||
|
||||
<div class="gh-social-og-preview-bookmark">
|
||||
{{!-- Ensures description is hidden if title exceeds one line --}}
|
||||
<div class="gh-social-og-preview-content {{if this.editingFacebookTitle 'edit-mode'}} {{if this.editingFacebookDescription 'edit-mode'}}">
|
||||
<div class="gh-social-og-preview-meta">
|
||||
{{this.config.blogDomain}}
|
||||
</div>
|
||||
{{#if this.editingFacebookTitle}}
|
||||
<input
|
||||
aria-label="Facebook title"
|
||||
type="text"
|
||||
class="gh-input"
|
||||
placeholder={{this.facebookTitle}}
|
||||
value={{@post.ogTitle}}
|
||||
maxlength="300"
|
||||
{{on "blur" this.setFacebookTitle}}
|
||||
{{on-key "Enter" this.blurElement}}
|
||||
{{on-key "Escape" (fn this.cancelEdit "ogTitle")}}
|
||||
{{autofocus}}
|
||||
data-prevent-escape-close-modal="true"
|
||||
/>
|
||||
{{else}}
|
||||
<div class="gh-social-og-preview-title editable pointer" role="button" aria-label="Edit Facebook title" {{on "click" this.editFacebookTitle}}>
|
||||
{{truncate this.facebookTitle}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.editingFacebookDescription}}
|
||||
<textarea
|
||||
aria-label="Facebook description"
|
||||
class="gh-input"
|
||||
maxlength="500"
|
||||
placeholder={{truncate this.facebookDescription 160}}
|
||||
{{on "blur" this.setFacebookDescription}}
|
||||
{{on-key "Enter" this.blurElement}}
|
||||
{{on-key "Escape" (fn this.cancelEdit "ogDescription")}}
|
||||
{{autofocus}}
|
||||
data-prevent-escape-close-modal="true"
|
||||
>{{@post.ogDescription}}</textarea>
|
||||
{{else}}
|
||||
<div class="gh-social-og-preview-desc editable pointer" role="button" aria-label="Edit Facebook description" {{on "click" this.editFacebookDescription}}>
|
||||
{{truncate this.facebookDescription}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-social-og-reactions">
|
||||
<span class="gh-social-og-likes">{{svg-jar "facebook-like" class="z-999"}}{{svg-jar "facebook-heart" class="nl1"}}182</span>
|
||||
<span class="gh-social-og-comments">7 comments</span>
|
||||
<span class="gh-social-og-comments ml2">2 shares</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gh-social-twitter-container">
|
||||
<div class="flex ma4">
|
||||
<span>{{svg-jar "social-twitter" class="social-icon"}}</span>
|
||||
<div>
|
||||
<span class="gh-social-og-title">{{or this.settings.metaTitle this.settings.title}}</span>
|
||||
<span class="gh-social-og-time">12 hrs</span>
|
||||
<div class="flex flex-column mt2 mb3">
|
||||
<span class="gh-social-og-desc w-100 mb2" />
|
||||
<span class="gh-social-og-desc w-60" />
|
||||
</div>
|
||||
<div class="gh-social-twitter-post-preview"
|
||||
{{on "mouseenter" (fn (mut this.twitterHovered) true)}}
|
||||
{{on "mouseleave" (fn (mut this.twitterHovered) false)}}
|
||||
>
|
||||
{{#if (and this.twitterHovered (not this.twitterImage))}}
|
||||
{{!-- only shown on hover when there's no image or fallback --}}
|
||||
<button class="gh-social-twitter-preview-img-add" type="button" {{on "click" (fn this.triggerFileDialog "twitter")}}>+ Add image</button>
|
||||
{{/if}}
|
||||
|
||||
<GhUploader
|
||||
@extensions={{this.imageExtensions}}
|
||||
@onComplete={{this.setTwitterImage}}
|
||||
as |uploader|
|
||||
>
|
||||
{{#each uploader.errors as |error|}}
|
||||
<div class="error pa2"><span class="response">{{or error.context error.message}}</span></div>
|
||||
{{/each}}
|
||||
|
||||
{{#if (or this.twitterImage uploader.isUploading)}}
|
||||
<div class="gh-social-twitter-preview-image relative" style={{background-image-style this.twitterImage}}>
|
||||
<div class="flex h-100 items-center justify-center">
|
||||
{{#if (or this.twitterHovered uploader.isUploading)}}
|
||||
{{#if uploader.isUploading}}
|
||||
{{uploader.progressBar}}
|
||||
{{else}}
|
||||
<button type="button" class="gh-btn gh-btn-white" {{on "click" (fn this.triggerFileDialog "twitter")}}><span>{{if @post.twitterImage "Change" "Upload"}} image</span></button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if (and this.twitterHovered @post.twitterImage)}}
|
||||
<button type="button" class="gh-btn gh-btn-black gh-btn-icon gh-social-preview-img-delete" title="Remove custom Twitter image" {{on "click" this.clearTwitterImage}}>
|
||||
<span>{{svg-jar "trash"}}</span>
|
||||
<span class="hidden">Remove custom Twitter image</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div style="display:none">
|
||||
<GhFileInput id="twitterFileInput" @multiple={{false}} @action={{uploader.setFiles}} @accept={{this.imageMimeTypes}} />
|
||||
</div>
|
||||
</GhUploader>
|
||||
|
||||
<div class="gh-social-twitter-preview-content">
|
||||
{{#if this.editingTwitterTitle}}
|
||||
<input
|
||||
aria-label="Twitter title"
|
||||
type="text"
|
||||
class="gh-input"
|
||||
placeholder={{this.twitterTitle}}
|
||||
value={{@post.twitterTitle}}
|
||||
maxlength="300"
|
||||
{{on "blur" this.setTwitterTitle}}
|
||||
{{on-key "Enter" this.blurElement}}
|
||||
{{on-key "Escape" (fn this.cancelEdit "twitterTitle")}}
|
||||
{{autofocus}}
|
||||
data-prevent-escape-close-modal="true"
|
||||
/>
|
||||
{{else}}
|
||||
<div class="gh-social-twitter-preview-title editable pointer" role="button" aria-label="Edit Twitter title" {{on "click" this.editTwitterTitle}}>{{this.twitterTitle}}</div>
|
||||
{{/if}}
|
||||
{{#if this.editingTwitterDescription}}
|
||||
<textarea
|
||||
aria-label="Twitter description"
|
||||
class="gh-input"
|
||||
maxlength="500"
|
||||
placeholder={{truncate this.twitterDescription 160}}
|
||||
{{on "blur" this.setTwitterDescription}}
|
||||
{{on-key "Enter" this.blurElement}}
|
||||
{{on-key "Escape" (fn this.cancelEdit "twitterDescription")}}
|
||||
{{autofocus}}
|
||||
data-prevent-escape-close-modal="true"
|
||||
>{{@post.twitterDescription}}</textarea>
|
||||
{{else}}
|
||||
<div class="gh-social-twitter-preview-desc editable pointer" role="button" aria-label="Edit Twitter description" {{on "click" this.editTwitterDescription}}>{{truncate this.twitterDescription}}</div>
|
||||
{{/if}}
|
||||
<div class="gh-social-twitter-preview-meta">
|
||||
{{svg-jar "twitter-link"}}
|
||||
{{this.config.blogDomain}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-social-twitter-reactions">
|
||||
<div class="flex items-center">{{svg-jar "twitter-comment"}}2</div>
|
||||
<div class="flex items-center">{{svg-jar "twitter-retweet"}}11</div>
|
||||
<div class="flex items-center">{{svg-jar "twitter-like"}}32</div>
|
||||
<div class="flex items-center">{{svg-jar "twitter-share"}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gh-seo-preview-container">
|
||||
{{svg-jar "google"}}
|
||||
<div class="gh-seo-preview">
|
||||
<div class="gh-seo-search-bar mb12">{{svg-jar "google-search"}}</div>
|
||||
<div class="gh-seo-preview-link">{{this.serpURL}}</div>
|
||||
{{#if this.editingMetaTitle}}
|
||||
<input
|
||||
aria-label="Meta title"
|
||||
type="text"
|
||||
class="gh-input"
|
||||
placeholder={{this.serpTitle}}
|
||||
value={{@post.metaTitle}}
|
||||
maxlength="300"
|
||||
{{on "blur" this.setMetaTitle}}
|
||||
{{on-key "Enter" this.blurElement}}
|
||||
{{on-key "Escape" (fn this.cancelEdit "metaTitle")}}
|
||||
{{autofocus}}
|
||||
data-prevent-escape-close-modal="true"
|
||||
>
|
||||
{{else}}
|
||||
<div class="gh-seo-preview-title editable pointer" role="button" aria-label="Edit meta title" {{on "click" this.editMetaTitle}}>
|
||||
{{this.serpTitle}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.editingMetaDescription}}
|
||||
<textarea
|
||||
aria-label="Meta description"
|
||||
class="gh-input"
|
||||
placeholder={{this.serpDescription}}
|
||||
maxlength="500"
|
||||
{{on "blur" this.setMetaDescription}}
|
||||
{{on-key "Enter" this.blurElement}}
|
||||
{{on-key "Escape" (fn this.cancelEdit "metaDescription")}}
|
||||
{{autofocus}}
|
||||
data-prevent-escape-close-modal="true"
|
||||
>{{@post.metaDescription}}</textarea>
|
||||
{{else}}
|
||||
<div class="gh-seo-preview-desc editable pointer" role="button" aria-label="Edit meta description" {{on "click" this.editMetaDescription}}>
|
||||
{{moment-format (now) "DD MMM YYYY"}} — {{truncate this.serpDescription 149}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
209
ghost/admin/app/components/editor/modals/preview/social.js
Normal file
209
ghost/admin/app/components/editor/modals/preview/social.js
Normal file
@ -0,0 +1,209 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {
|
||||
IMAGE_EXTENSIONS,
|
||||
IMAGE_MIME_TYPES
|
||||
} from 'ghost-admin/components/gh-image-uploader';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
export default class ModalPostPreviewSocialComponent extends Component {
|
||||
@service config;
|
||||
@service settings;
|
||||
@service ghostPaths;
|
||||
|
||||
@tracked editingFacebookTitle = false;
|
||||
@tracked editingFacebookDescription = false;
|
||||
@tracked editingTwitterTitle = false;
|
||||
@tracked editingTwitterDescription = false;
|
||||
@tracked editingMetaTitle = false;
|
||||
@tracked editingMetaDescription = false;
|
||||
|
||||
imageExtensions = IMAGE_EXTENSIONS;
|
||||
imageMimeTypes = IMAGE_MIME_TYPES;
|
||||
|
||||
get _fallbackDescription() {
|
||||
return this.args.post.customExcerpt ||
|
||||
this.serpDescription ||
|
||||
this.settings.get('description');
|
||||
}
|
||||
|
||||
@action
|
||||
blurElement(event) {
|
||||
if (!event.shiftKey) {
|
||||
event.preventDefault();
|
||||
event.target.blur();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
triggerFileDialog(name) {
|
||||
const input = document.querySelector(`#${name}FileInput input`);
|
||||
if (input) {
|
||||
input.click();
|
||||
}
|
||||
}
|
||||
|
||||
// SERP
|
||||
|
||||
get serpTitle() {
|
||||
return this.args.post.metaTitle || this.args.post.title || '(Untitled)';
|
||||
}
|
||||
|
||||
get serpURL() {
|
||||
const urlParts = [];
|
||||
|
||||
if (this.args.post.canonicalUrl) {
|
||||
const canonicalUrl = new URL(this.args.post.canonicalUrl);
|
||||
urlParts.push(canonicalUrl.host);
|
||||
urlParts.push(...canonicalUrl.pathname.split('/').reject(p => !p));
|
||||
} else {
|
||||
const blogUrl = new URL(this.config.get('blogUrl'));
|
||||
urlParts.push(blogUrl.host);
|
||||
urlParts.push(...blogUrl.pathname.split('/').reject(p => !p));
|
||||
urlParts.push(this.args.post.slug);
|
||||
}
|
||||
|
||||
return urlParts.join(' > ');
|
||||
}
|
||||
|
||||
get serpDescription() {
|
||||
return this.args.post.metaDescription || this.args.post.excerpt;
|
||||
}
|
||||
|
||||
@action
|
||||
editMetaTitle() {
|
||||
this.editingMetaTitle = true;
|
||||
}
|
||||
|
||||
@action
|
||||
setMetaTitle(event) {
|
||||
const title = event.target.value;
|
||||
this.args.post.metaTitle = title.trim();
|
||||
this.args.post.save();
|
||||
this.editingMetaTitle = false;
|
||||
}
|
||||
|
||||
@action
|
||||
editMetaDescription() {
|
||||
this.editingMetaDescription = true;
|
||||
}
|
||||
|
||||
@action
|
||||
setMetaDescription(event) {
|
||||
const description = event.target.value;
|
||||
this.args.post.metaDescription = description.trim();
|
||||
this.args.post.save();
|
||||
this.editingMetaDescription = false;
|
||||
}
|
||||
|
||||
// Facebook
|
||||
|
||||
get facebookTitle() {
|
||||
return this.args.post.ogTitle || this.serpTitle;
|
||||
}
|
||||
|
||||
get facebookDescription() {
|
||||
return this.args.post.ogDescription || this._fallbackDescription;
|
||||
}
|
||||
|
||||
get facebookImage() {
|
||||
return this.args.post.ogImage || this.args.post.featureImage || this.settings.get('ogImage') || this.settings.get('coverImage');
|
||||
}
|
||||
|
||||
@action
|
||||
editFacebookTitle() {
|
||||
this.editingFacebookTitle = true;
|
||||
}
|
||||
|
||||
@action
|
||||
cancelEdit(property, event) {
|
||||
event.preventDefault();
|
||||
event.target.value = this.args.post[property];
|
||||
event.target.blur();
|
||||
}
|
||||
|
||||
@action
|
||||
setFacebookTitle(event) {
|
||||
const title = event.target.value;
|
||||
this.args.post.ogTitle = title.trim();
|
||||
this.args.post.save();
|
||||
this.editingFacebookTitle = false;
|
||||
}
|
||||
|
||||
@action
|
||||
editFacebookDescription() {
|
||||
this.editingFacebookDescription = true;
|
||||
}
|
||||
|
||||
@action
|
||||
setFacebookDescription() {
|
||||
const description = event.target.value;
|
||||
this.args.post.ogDescription = description.trim();
|
||||
this.args.post.save();
|
||||
this.editingFacebookDescription = false;
|
||||
}
|
||||
|
||||
@action
|
||||
setFacebookImage([image]) {
|
||||
this.args.post.ogImage = image.url;
|
||||
this.args.post.save();
|
||||
}
|
||||
|
||||
@action
|
||||
clearFacebookImage() {
|
||||
this.args.post.ogImage = null;
|
||||
this.args.post.save();
|
||||
}
|
||||
|
||||
// Twitter
|
||||
|
||||
get twitterTitle() {
|
||||
return this.args.post.twitterTitle || this.serpTitle;
|
||||
}
|
||||
|
||||
get twitterDescription() {
|
||||
return this.args.post.twitterDescription || this._fallbackDescription;
|
||||
}
|
||||
|
||||
get twitterImage() {
|
||||
return this.args.post.twitterImage || this.args.post.featureImage || this.settings.get('twitterImage') || this.settings.get('coverImage');
|
||||
}
|
||||
|
||||
@action
|
||||
editTwitterTitle() {
|
||||
this.editingTwitterTitle = true;
|
||||
}
|
||||
|
||||
@action
|
||||
setTwitterTitle(event) {
|
||||
const title = event.target.value;
|
||||
this.args.post.twitterTitle = title.trim();
|
||||
this.args.post.save();
|
||||
this.editingTwitterTitle = false;
|
||||
}
|
||||
|
||||
@action
|
||||
editTwitterDescription() {
|
||||
this.editingTwitterDescription = true;
|
||||
}
|
||||
|
||||
@action
|
||||
setTwitterDescription() {
|
||||
const description = event.target.value;
|
||||
this.args.post.twitterDescription = description.trim();
|
||||
this.args.post.save();
|
||||
this.editingTwitterDescription = false;
|
||||
}
|
||||
|
||||
@action
|
||||
setTwitterImage([image]) {
|
||||
this.args.post.twitterImage = image.url;
|
||||
this.args.post.save();
|
||||
}
|
||||
|
||||
@action
|
||||
clearTwitterImage() {
|
||||
this.args.post.twitterImage = null;
|
||||
this.args.post.save();
|
||||
}
|
||||
}
|
62
ghost/admin/app/components/editor/modals/publish-flow.hbs
Normal file
62
ghost/admin/app/components/editor/modals/publish-flow.hbs
Normal file
@ -0,0 +1,62 @@
|
||||
<div class="flex flex-column h-100 items-center overflow-auto" data-test-modal="publish-flow">
|
||||
<header class="gh-publish-header">
|
||||
<button class="gh-btn-editor gh-publish-back-button" title="Close" type="button" {{on "click" @close}} data-test-button="close-publish-flow">
|
||||
<span>{{svg-jar "arrow-left"}} Editor</span>
|
||||
</button>
|
||||
|
||||
{{#if (and (not this.emailErrorMessage) (not this.isComplete))}}
|
||||
<div class="flex items-center pe-auto h-100">
|
||||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor gh-editor-preview-trigger"
|
||||
{{on "click" @data.togglePreviewPublish}}
|
||||
data-test-button="publish-flow-preview"
|
||||
>
|
||||
<span>Preview</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor darkgrey gh-publish-trigger active"
|
||||
title="Close"
|
||||
{{on "click" @close}}
|
||||
data-test-button="publish-flow-publish"
|
||||
>
|
||||
<span>Publish</span>
|
||||
</button>
|
||||
<div class="settings-menu-toggle-spacer"></div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</header>
|
||||
|
||||
<div class="gh-publish-settings-container {{unless @data.skipAnimation "fade-in"}}">
|
||||
{{#if this.emailErrorMessage}}
|
||||
<Editor::Modals::PublishFlow::CompleteWithEmailError
|
||||
@emailErrorMessage={{this.emailErrorMessage}}
|
||||
@publishOptions={{@data.publishOptions}}
|
||||
@close={{@close}}
|
||||
/>
|
||||
{{else if this.isConfirming}}
|
||||
<Editor::Modals::PublishFlow::Confirm
|
||||
@publishOptions={{@data.publishOptions}}
|
||||
@recipientType={{this.recipientType}}
|
||||
@saveTask={{this.saveTask}}
|
||||
@cancel={{this.toggleConfirm}}
|
||||
@close={{@close}}
|
||||
/>
|
||||
{{else if this.isComplete}}
|
||||
<Editor::Modals::PublishFlow::Complete
|
||||
@publishOptions={{@data.publishOptions}}
|
||||
@recipientType={{this.recipientType}}
|
||||
@postCount={{this.postCount}}
|
||||
@close={{@close}}
|
||||
/>
|
||||
{{else}}
|
||||
<Editor::Modals::PublishFlow::Options
|
||||
@publishOptions={{@data.publishOptions}}
|
||||
@recipientType={{this.recipientType}}
|
||||
@confirm={{this.toggleConfirm}}
|
||||
@close={{@close}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
87
ghost/admin/app/components/editor/modals/publish-flow.js
Normal file
87
ghost/admin/app/components/editor/modals/publish-flow.js
Normal file
@ -0,0 +1,87 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class PublishModalComponent extends Component {
|
||||
static modalOptions = {
|
||||
className: 'fullscreen-modal-total-overlay publish-modal',
|
||||
omitBackdrop: true,
|
||||
ignoreBackdropClick: true
|
||||
};
|
||||
|
||||
@service store;
|
||||
|
||||
@tracked emailErrorMessage;
|
||||
@tracked isConfirming = false;
|
||||
@tracked isComplete = false;
|
||||
@tracked postCount = null;
|
||||
|
||||
get recipientType() {
|
||||
const filter = this.args.data.publishOptions.recipientFilter;
|
||||
|
||||
if (!filter) {
|
||||
return 'none';
|
||||
}
|
||||
|
||||
if (filter === 'status:free') {
|
||||
return 'free';
|
||||
}
|
||||
|
||||
if (filter === 'status:-free') {
|
||||
return 'paid';
|
||||
}
|
||||
|
||||
if (filter.includes('status:free') && filter.includes('status:-free')) {
|
||||
return 'all';
|
||||
}
|
||||
|
||||
return 'specific';
|
||||
}
|
||||
|
||||
@action
|
||||
toggleConfirm() {
|
||||
this.isConfirming = !this.isConfirming;
|
||||
|
||||
if (this.isConfirming) {
|
||||
this.fetchPostCountTask.perform();
|
||||
}
|
||||
}
|
||||
|
||||
@task
|
||||
*saveTask() {
|
||||
try {
|
||||
yield this.args.data.saveTask.perform();
|
||||
|
||||
this.isConfirming = false;
|
||||
this.isComplete = true;
|
||||
} catch (e) {
|
||||
if (e?.name === 'EmailFailedError') {
|
||||
return this.emailErrorMessage = e.message;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// we fetch the new post count in advance when reaching the confirm step
|
||||
// to avoid a copy flash when reaching the complete step
|
||||
@task
|
||||
*fetchPostCountTask() {
|
||||
const publishOptions = this.args.data.publishOptions;
|
||||
|
||||
// no count is shown for pages, scheduled posts, or email-only posts
|
||||
if (publishOptions.post.isPage || publishOptions.isScheduled || !publishOptions.willPublish) {
|
||||
this.postCount = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const result = yield this.store.query('post', {filter: 'status:published', limit: 1});
|
||||
let count = result.meta.pagination.total;
|
||||
|
||||
count += 1; // account for the new post
|
||||
|
||||
this.postCount = count;
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{{#let @publishOptions.post as |post|}}
|
||||
<div class="gh-publish-title">
|
||||
<span class="red">Uh-oh.</span>
|
||||
|
||||
{{#if @publishOptions.willOnlyEmail}}
|
||||
Your post has been created but the email failed to send.
|
||||
{{else}}
|
||||
Your {{post.displayName}} has been published but the email failed to send.
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<p class="gh-publish-confirmation">
|
||||
{{this.emailErrorMessage}}
|
||||
<br><br>
|
||||
Please verify your email settings if the error persists.
|
||||
</p>
|
||||
|
||||
{{#if this.retryErrorMessage}}
|
||||
<p class="error gh-box gh-box-error mt3 mb3">
|
||||
{{this.retryErrorMessage}}
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
<div class="gh-publish-cta">
|
||||
<GhTaskButton
|
||||
@task={{this.retryEmailTask}}
|
||||
@buttonText="Retry sending email"
|
||||
@runningText="Sending"
|
||||
@successText="Sent"
|
||||
@class="gh-btn gh-btn-large gh-btn-red"
|
||||
@showIcon={{false}}
|
||||
/>
|
||||
</div>
|
||||
{{/let}}
|
||||
|
@ -0,0 +1,71 @@
|
||||
import Component from '@glimmer/component';
|
||||
import EmailFailedError from 'ghost-admin/errors/email-failed-error';
|
||||
import {CONFIRM_EMAIL_MAX_POLL_LENGTH, CONFIRM_EMAIL_POLL_LENGTH} from '../../publish-management';
|
||||
import {htmlSafe} from '@ember/template';
|
||||
import {isServerUnreachableError} from 'ghost-admin/services/ajax';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
function isString(str) {
|
||||
return toString.call(str) === '[object String]';
|
||||
}
|
||||
|
||||
export default class PublishFlowCompleteWithEmailError extends Component {
|
||||
@tracked newEmailErrorMessage;
|
||||
@tracked retryErrorMessage;
|
||||
|
||||
get emailErrorMessage() {
|
||||
return this.newEmailErrorMessage || this.args.emailErrorMessage;
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*retryEmailTask() {
|
||||
this.retryErrorMessage = null;
|
||||
|
||||
try {
|
||||
let email = yield this.args.publishOptions.post.email.retry();
|
||||
|
||||
let pollTimeout = 0;
|
||||
if (email && email.status !== 'submitted') {
|
||||
while (pollTimeout < CONFIRM_EMAIL_MAX_POLL_LENGTH) {
|
||||
yield timeout(CONFIRM_EMAIL_POLL_LENGTH);
|
||||
pollTimeout += CONFIRM_EMAIL_POLL_LENGTH;
|
||||
|
||||
email = yield email.reload();
|
||||
|
||||
if (email.status === 'submitted') {
|
||||
break;
|
||||
}
|
||||
if (email.status === 'failed') {
|
||||
throw new EmailFailedError(email.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return email;
|
||||
} catch (e) {
|
||||
// update "failed" state if email fails again
|
||||
if (e && e.name === 'EmailFailedError') {
|
||||
this.newEmailErrorMessage = e.message;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e) {
|
||||
let errorMessage = '';
|
||||
|
||||
if (isServerUnreachableError(e)) {
|
||||
errorMessage = 'Unable to connect, please check your internet connection and try again.';
|
||||
} else if (e && isString(e)) {
|
||||
errorMessage = e;
|
||||
} else if (e?.payload?.errors?.[0].message) {
|
||||
errorMessage = e.payload.errors[0].message;
|
||||
} else {
|
||||
errorMessage = 'Unknown Error occurred when attempting to resend';
|
||||
}
|
||||
|
||||
this.retryErrorMessage = htmlSafe(errorMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
{{#let @publishOptions.post as |post|}}
|
||||
<div class="gh-publish-title" data-test-publish-flow="complete" data-test-complete-title>
|
||||
{{#if post.isScheduled}}
|
||||
<span class="green">All set!</span>
|
||||
|
||||
Your {{if post.emailOnly "email" post.displayName}} will be
|
||||
|
||||
{{#if post.emailOnly}}
|
||||
sent
|
||||
{{else if post.willEmail}}
|
||||
published and sent
|
||||
{{else}}
|
||||
published
|
||||
{{/if}}
|
||||
|
||||
{{#let (moment-site-tz post.publishedAtUTC) as |scheduledAt|}}
|
||||
{{#if (is-moment-today scheduledAt)}}
|
||||
today
|
||||
{{else}}
|
||||
on {{moment-format scheduledAt "MMMM Do"}}
|
||||
{{/if}}
|
||||
at {{moment-format scheduledAt "HH:mm"}}.
|
||||
{{/let}}
|
||||
{{else}}
|
||||
<span class="green">Boom. It’s out there.</span>
|
||||
|
||||
{{#if post.emailOnly}}
|
||||
Your email has been sent.
|
||||
{{else if (and post.isPost @postCount)}}
|
||||
That’s {{gh-pluralize @postCount "post"}} published, keep going!
|
||||
{{else}}
|
||||
Your {{post.displayName}} has been published.
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if post.emailOnly}}
|
||||
<div class="gh-publish-confirmation" data-test-complete-details>
|
||||
<p>
|
||||
Your post
|
||||
{{if post.isScheduled "will be" "was"}}
|
||||
sent to
|
||||
|
||||
{{#let (members-count-fetcher query=(hash filter=post.fullRecipientFilter)) as |countFetcher|}}
|
||||
<strong>
|
||||
{{if (eq @recipientType "all") "all"}}
|
||||
|
||||
{{countFetcher.count}}
|
||||
|
||||
{{!-- @recipientType = free/paid/all/specific --}}
|
||||
{{if (not-eq @recipientType "all") @recipientType}}
|
||||
|
||||
{{gh-pluralize countFetcher.count "subscriber" without-count=true}}
|
||||
</strong>
|
||||
|
||||
{{#unless @publishOptions.onlyDefaultNewsletter}}
|
||||
of <strong>{{@publishOptions.newsletter.name}}</strong>{{if post.isScheduled "," "."}}
|
||||
{{/unless}}
|
||||
{{/let}}
|
||||
|
||||
{{#let (moment-site-tz post.publishedAtUTC) as |publishedAt|}}
|
||||
on
|
||||
{{moment-format publishedAt "D MMM YYYY"}}
|
||||
at
|
||||
{{moment-format publishedAt "HH:mm"}}.
|
||||
{{/let}}
|
||||
</p>
|
||||
{{#if post.isScheduled}}
|
||||
<p>
|
||||
Need to make a change?
|
||||
<button
|
||||
type="button"
|
||||
class="gh-revert-to-draft"
|
||||
{{on "click" (fn @close (hash afterTask="revertToDraftTask"))}}
|
||||
>
|
||||
<span>Unschedule and revert to draft →</span>
|
||||
</button>
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<a href={{post.url}} class="gh-post-bookmark-wrapper" target="_blank" rel="noopener noreferrer" data-test-complete-bookmark>
|
||||
<GhPostBookmark @post={{post}} />
|
||||
</a>
|
||||
|
||||
{{#if post.isScheduled}}
|
||||
<p class="gh-publish-confirmation">
|
||||
Need to make a change?
|
||||
<button
|
||||
type="button"
|
||||
class="gh-revert-to-draft"
|
||||
{{on "click" (fn @close (hash afterTask="revertToDraftTask"))}}
|
||||
data-test-button="revert-to-draft"
|
||||
>
|
||||
<span>Unschedule and revert to draft →</span>
|
||||
</button>
|
||||
</p>
|
||||
{{else}}
|
||||
<p class="gh-publish-confirmation">
|
||||
<button
|
||||
type="button"
|
||||
class="gh-back-to-editor"
|
||||
{{on "click" @close}}
|
||||
data-test-button="back-to-editor"
|
||||
>
|
||||
<span>Back to editor</span>
|
||||
</button>
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{/let}}
|
@ -0,0 +1,73 @@
|
||||
<div class="gh-publish-title" data-test-publish-flow="confirm">
|
||||
<div class="green">Ready, set, publish.</div>
|
||||
<div>Share it with the world.</div>
|
||||
</div>
|
||||
<p class="gh-publish-confirmation" data-test-text="confirm-details">
|
||||
{{#if @publishOptions.isScheduled}}
|
||||
{{#let (moment-site-tz @publishOptions.scheduledAtUTC) as |scheduledAt|}}
|
||||
On
|
||||
<strong>
|
||||
{{moment-format scheduledAt "D MMM YYYY"}}
|
||||
at
|
||||
{{moment-format scheduledAt "HH:mm"}}
|
||||
</strong>
|
||||
your
|
||||
{{/let}}
|
||||
{{else}}
|
||||
Your
|
||||
{{/if}}
|
||||
|
||||
{{@publishOptions.post.displayName}}
|
||||
|
||||
{{#if this.willPublish}}
|
||||
will be published on your site{{#if this.willEmail}}, and delivered to{{else}}.{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if this.willEmail}}
|
||||
{{#unless this.willPublish}}
|
||||
will be delivered to
|
||||
{{/unless}}
|
||||
|
||||
{{#let (members-count-fetcher query=(hash filter=@publishOptions.fullRecipientFilter)) as |countFetcher|}}
|
||||
<strong>
|
||||
{{if (eq @recipientType "all") "all"}}
|
||||
|
||||
{{format-number countFetcher.count}}
|
||||
|
||||
{{!-- @recipientType = none/free/paid/all/specific --}}
|
||||
{{if (not-eq @recipientType "all") @recipientType}}
|
||||
</strong>
|
||||
|
||||
{{#if @publishOptions.onlyDefaultNewsletter}}
|
||||
<strong>{{gh-pluralize countFetcher.count "subscriber" without-count=true}}</strong>{{#if this.willPublish}}.{{else}},{{/if}}
|
||||
{{else}}
|
||||
<strong>{{gh-pluralize countFetcher.count "subscriber" without-count=true}}</strong>
|
||||
of <strong>{{@publishOptions.newsletter.name}}</strong>{{#if this.willPublish}}.{{else}},{{/if}}
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
|
||||
{{#unless this.willPublish}}
|
||||
and will <strong>not</strong> be published on your site.
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</p>
|
||||
|
||||
{{#if this.errorMessage}}
|
||||
<p class="error gh-box gh-box-error mt3 mb3" data-test-confirm-error>
|
||||
{{this.errorMessage}}
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
<div class="gh-publish-cta">
|
||||
<GhTaskButton
|
||||
@task={{this.confirmTask}}
|
||||
@buttonText={{this.confirmButtonText}}
|
||||
@runningText={{this.confirmRunningText}}
|
||||
@successText={{this.confirmSuccessText}}
|
||||
@class="gh-btn gh-btn-large"
|
||||
@idleClass="gh-btn-pulse"
|
||||
@runningClass="gh-btn-green gh-btn-icon"
|
||||
data-test-button="confirm-publish"
|
||||
/>
|
||||
<button type="button" class="gh-btn gh-btn-link gh-btn-large gh-publish-cta-secondary" {{on "click" @cancel}} data-test-button="back-to-options">Back to settings</button>
|
||||
</div>
|
122
ghost/admin/app/components/editor/modals/publish-flow/confirm.js
Normal file
122
ghost/admin/app/components/editor/modals/publish-flow/confirm.js
Normal file
@ -0,0 +1,122 @@
|
||||
import Component from '@glimmer/component';
|
||||
import moment from 'moment';
|
||||
import {htmlSafe} from '@ember/template';
|
||||
import {isArray} from '@ember/array';
|
||||
import {isServerUnreachableError} from 'ghost-admin/services/ajax';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
function isString(str) {
|
||||
return toString.call(str) === '[object String]';
|
||||
}
|
||||
|
||||
export default class PublishFlowOptions extends Component {
|
||||
@service settings;
|
||||
|
||||
@tracked errorMessage;
|
||||
|
||||
// store any derived state from PublishOptions on creation so the copy
|
||||
// doesn't change whilst the post is saving
|
||||
willEmail = this.args.publishOptions.willEmail;
|
||||
willPublish = this.args.publishOptions.willPublish;
|
||||
|
||||
buttonTextMap = {
|
||||
'publish+send': {
|
||||
idle: 'Publish & send',
|
||||
running: 'Publishing & sending',
|
||||
success: 'Published & sent'
|
||||
},
|
||||
send: {
|
||||
idle: 'Send email',
|
||||
running: 'Sending',
|
||||
success: 'Sent'
|
||||
},
|
||||
publish: {
|
||||
idle: 'Publish',
|
||||
running: 'Publishing',
|
||||
success: 'Published'
|
||||
},
|
||||
schedule: {
|
||||
// idle: '', - uses underlying publish type text
|
||||
running: 'Scheduling',
|
||||
success: 'Scheduled'
|
||||
}
|
||||
};
|
||||
|
||||
get publishType() {
|
||||
const {publishOptions} = this.args;
|
||||
|
||||
if (this.willPublish && this.willEmail) {
|
||||
return 'publish+send';
|
||||
} else if (publishOptions.willOnlyEmail) {
|
||||
return 'send';
|
||||
} else {
|
||||
return 'publish';
|
||||
}
|
||||
}
|
||||
|
||||
get confirmButtonText() {
|
||||
let buttonText = '';
|
||||
|
||||
buttonText = this.buttonTextMap[this.publishType].idle;
|
||||
|
||||
if (this.publishType === 'publish') {
|
||||
buttonText += ` ${this.args.publishOptions.post.displayName}`;
|
||||
}
|
||||
|
||||
if (this.args.publishOptions.isScheduled) {
|
||||
const scheduleMoment = moment.tz(this.args.publishOptions.scheduledAtUTC, this.settings.get('timezone'));
|
||||
buttonText += `, on ${scheduleMoment.format('MMMM Do')}`;
|
||||
} else {
|
||||
buttonText += ', right now';
|
||||
}
|
||||
|
||||
return buttonText;
|
||||
}
|
||||
|
||||
get confirmRunningText() {
|
||||
const publishType = this.args.publishOptions.isScheduled ? 'schedule' : this.publishType;
|
||||
return this.buttonTextMap[publishType].running;
|
||||
}
|
||||
|
||||
get confirmSuccessText() {
|
||||
const publishType = this.args.publishOptions.isScheduled ? 'schedule' : this.publishType;
|
||||
return this.buttonTextMap[publishType].success;
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*confirmTask() {
|
||||
this.errorMessage = null;
|
||||
|
||||
try {
|
||||
yield this.args.saveTask.perform();
|
||||
} catch (e) {
|
||||
if (e === undefined && this.args.publishOptions.post.errors.length !== 0) {
|
||||
// validation error
|
||||
const validationError = this.args.publishOptions.post.errors.messages[0];
|
||||
this.errorMessage = `Validation failed: ${validationError}`;
|
||||
return false;
|
||||
}
|
||||
|
||||
let errorMessage = '';
|
||||
|
||||
if (isServerUnreachableError(e)) {
|
||||
errorMessage = 'Unable to connect, please check your internet connection and try again.';
|
||||
} else if (e && isString(e)) {
|
||||
errorMessage = e;
|
||||
} else if (e && isArray(e)) {
|
||||
// This is here because validation errors are returned as an array
|
||||
// TODO: remove this once validations are fixed
|
||||
errorMessage = e[0];
|
||||
} else if (e?.payload?.errors?.[0].message) {
|
||||
errorMessage = e.payload.errors[0].message;
|
||||
} else {
|
||||
errorMessage = 'Unknown Error';
|
||||
}
|
||||
|
||||
this.errorMessage = htmlSafe(errorMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
<div class="gh-publish-title" data-test-publish-flow="options">
|
||||
<div class="green">Ready, set, publish.</div>
|
||||
<div>Share it with the world.</div>
|
||||
</div>
|
||||
<div class="gh-publish-settings">
|
||||
<div class="gh-publish-setting" data-test-setting="publish-type">
|
||||
{{#if @publishOptions.emailUnavailable}}
|
||||
<div class="gh-publish-setting-title" data-test-setting-title>
|
||||
{{svg-jar "send-email"}}
|
||||
<div class="gh-publish-setting-trigger">Publish on site</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<button type="button" class="gh-publish-setting-title" {{on "click" (fn this.toggleSection "publishType")}} data-test-setting-title>
|
||||
{{svg-jar "send-email"}}
|
||||
<div class="gh-publish-setting-trigger">
|
||||
<span>{{@publishOptions.selectedPublishTypeOption.display}}</span>
|
||||
</div>
|
||||
<span class="{{if (eq this.openSection "publishType") "expanded"}}">
|
||||
{{svg-jar "arrow-down" class="icon-expand"}}
|
||||
</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
{{#liquid-if (eq this.openSection "publishType")}}
|
||||
<div class="gh-publish-setting-form">
|
||||
<Editor::PublishOptions::PublishType
|
||||
@publishOptions={{@publishOptions}}
|
||||
/>
|
||||
</div>
|
||||
{{/liquid-if}}
|
||||
</div>
|
||||
|
||||
{{#unless @publishOptions.emailUnavailable}}
|
||||
<div class="gh-publish-setting" data-test-setting="email-recipients">
|
||||
{{#if (not-eq @publishOptions.publishType "publish")}}
|
||||
<button
|
||||
type="button"
|
||||
class="gh-publish-setting-title"
|
||||
{{on "click" (fn this.toggleSection "emailRecipients")}}
|
||||
data-test-setting-title
|
||||
>
|
||||
{{svg-jar "member"}}
|
||||
<div class="gh-publish-setting-trigger">
|
||||
{{#if @publishOptions.recipientFilter}}
|
||||
{{#let (members-count-fetcher query=(hash filter=@publishOptions.fullRecipientFilter)) as |countFetcher|}}
|
||||
{{#if (or (gt countFetcher.count 1) (is-empty countFetcher.count))}}
|
||||
{{if (eq @recipientType "all") "All"}}
|
||||
{{/if}}
|
||||
|
||||
{{format-number countFetcher.count}}
|
||||
|
||||
{{!-- @recipientType = all/free/paid/all/specific --}}
|
||||
{{#if (not-eq @recipientType "all")}}
|
||||
{{if (is-empty countFetcher.count) (capitalize @recipientType) @recipientType}}
|
||||
{{/if}}
|
||||
|
||||
{{gh-pluralize countFetcher.count "subscriber" without-count=true}}
|
||||
|
||||
{{#unless @publishOptions.onlyDefaultNewsletter}}
|
||||
of <span class="gh-selected-newsletter">{{@publishOptions.newsletter.name}}</span>
|
||||
{{/unless}}
|
||||
{{/let}}
|
||||
{{else}}
|
||||
Not sent as newsletter
|
||||
{{/if}}
|
||||
</div>
|
||||
<span class="{{if (eq this.openSection "emailRecipients") "expanded"}}">
|
||||
{{svg-jar "arrow-down" class="icon-expand"}}
|
||||
</span>
|
||||
</button>
|
||||
{{else}}
|
||||
<button
|
||||
type="button"
|
||||
class="gh-publish-setting-title disabled"
|
||||
data-test-setting-title
|
||||
>
|
||||
{{svg-jar "member"}}
|
||||
<div class="gh-publish-setting-trigger">
|
||||
Not sent as newsletter
|
||||
</div>
|
||||
<span>
|
||||
{{svg-jar "arrow-down" class="icon-expand"}}
|
||||
</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
{{#liquid-if (eq this.openSection "emailRecipients")}}
|
||||
<div class="gh-publish-setting-form">
|
||||
<Editor::PublishOptions::EmailRecipients
|
||||
@publishOptions={{@publishOptions}}
|
||||
/>
|
||||
</div>
|
||||
{{/liquid-if}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
{{#if (and @publishOptions.post.email (not @publishOptions.emailDisabledInSettings))}}
|
||||
<div class="gh-publish-setting" data-test-setting="email-recipients">
|
||||
<div class="gh-publish-setting-title disabled" data-test-setting-title>
|
||||
{{svg-jar "member"}}
|
||||
<div class="gh-publish-setting-trigger">
|
||||
Already sent to
|
||||
{{@publishOptions.post.email.emailCount}}
|
||||
{{if (not-eq @recipientType "all") @recipientType}} {{!-- free/paid/specific --}}
|
||||
{{gh-pluralize @publishOptions.post.email.emailCount "subscriber" without-count=true}}
|
||||
{{#unless @publishOptions.onlyDefaultNewsletter}}
|
||||
of {{@publishOptions.post.newsletter.name}}
|
||||
{{/unless}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="gh-publish-setting last" data-test-setting="publish-at">
|
||||
<button type="button" class="gh-publish-setting-title" {{on "click" (fn this.toggleSection "publishAt")}} data-test-setting-title>
|
||||
{{svg-jar "clock"}}
|
||||
<div class="gh-publish-setting-trigger">
|
||||
<span>
|
||||
{{#if @publishOptions.isScheduled}}
|
||||
{{capitalize (gh-format-post-time @publishOptions.scheduledAtUTC draft=true)}}
|
||||
{{else}}
|
||||
Right now
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
<span class="{{if (eq this.openSection "publishAt") "expanded"}}">
|
||||
{{svg-jar "arrow-down" class="icon-expand"}}
|
||||
</span>
|
||||
</button>
|
||||
{{#liquid-if (eq this.openSection "publishAt")}}
|
||||
<div class="gh-publish-setting-form last">
|
||||
<Editor::PublishOptions::PublishAt
|
||||
@publishOptions={{@publishOptions}}
|
||||
/>
|
||||
</div>
|
||||
{{/liquid-if}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="gh-publish-cta">
|
||||
<button type="button" class="gh-btn gh-btn-black gh-btn-large" {{on "click" @confirm}} data-test-button="continue">
|
||||
<span>Continue, final review →</span>
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,16 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class PublishFlowOptions extends Component {
|
||||
@tracked openSection = null;
|
||||
|
||||
@action
|
||||
toggleSection(section) {
|
||||
if (section === this.openSection) {
|
||||
this.openSection = null;
|
||||
} else {
|
||||
this.openSection = section;
|
||||
}
|
||||
}
|
||||
}
|
112
ghost/admin/app/components/editor/modals/update-flow.hbs
Normal file
112
ghost/admin/app/components/editor/modals/update-flow.hbs
Normal file
@ -0,0 +1,112 @@
|
||||
<div class="flex flex-column h-100 items-center overflow-auto" data-test-modal="update-flow">
|
||||
<header class="gh-publish-header">
|
||||
<button class="gh-btn-editor gh-publish-back-button" title="Close" type="button" {{on "click" @close}}>
|
||||
<span>{{svg-jar "arrow-left"}} Editor</span>
|
||||
</button>
|
||||
<div class="flex">
|
||||
{{#let @data.publishOptions.post as |post|}}
|
||||
{{#if (not (and post.isSent (not post.isPublished)))}}
|
||||
<button class="gh-btn gh-btn-editor darkgrey gh-publish-trigger active" title="Close" type="button" {{on "click" @close}}>
|
||||
<span>{{if post.isScheduled "Unschedule" "Unpublish"}}</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
<div class="settings-menu-toggle-spacer"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{#let @data.publishOptions.post as |post|}}
|
||||
<div class="gh-publish-settings-container gh-update-flow fade-in">
|
||||
<div class="gh-publish-title" data-test-update-flow-title>
|
||||
{{#if (and post.isSent (not post.isPublished))}}
|
||||
This {{post.displayName}} was
|
||||
<span class="green">{{post.status}} by email</span>
|
||||
{{else}}
|
||||
This {{post.displayName}} has been
|
||||
<span class="green">{{post.status}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="gh-publish-confirmation" data-test-update-flow-confirmation>
|
||||
<p>
|
||||
Your
|
||||
{{post.displayName}}
|
||||
|
||||
{{if post.isScheduled "will be" "was"}}
|
||||
|
||||
{{#if
|
||||
(or post.isSent
|
||||
(and post.isPublished post.email)
|
||||
post.willEmail
|
||||
)
|
||||
}}
|
||||
{{#if post.emailOnly}}
|
||||
sent to
|
||||
{{else}}
|
||||
published and sent to
|
||||
{{/if}}
|
||||
|
||||
{{#if post.isScheduled}}
|
||||
{{#let (members-count-fetcher query=(hash filter=post.fullRecipientFilter)) as |countFetcher|}}
|
||||
<strong>{{gh-pluralize countFetcher.count "subscriber"}}</strong>
|
||||
{{#if this.showNewsletterName}}of <strong>{{post.newsletter.name}}</strong>{{/if}}
|
||||
{{/let}}
|
||||
{{else}}
|
||||
<strong>{{gh-pluralize post.email.emailCount "subscriber"}}</strong>
|
||||
{{#if this.showNewsletterName}}of <strong>{{post.newsletter.name}}</strong>{{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
published on your site
|
||||
{{/if}}
|
||||
|
||||
{{#let (moment-site-tz post.publishedAtUTC) as |publishedAt|}}
|
||||
on
|
||||
{{moment-format publishedAt "D MMM YYYY"}}
|
||||
at
|
||||
{{moment-format publishedAt "HH:mm"}}.
|
||||
{{/let}}
|
||||
|
||||
{{#if post.isScheduled}}
|
||||
{{#if (and post.isScheduled post.email)}}
|
||||
This post was previously emailed to
|
||||
|
||||
<strong>{{pluralize post.email.emailCount "subscriber"}}</strong>
|
||||
|
||||
{{#if this.showNewsletterName}}
|
||||
of <strong>{{post.newsletter.name}}</strong>
|
||||
{{/if}}
|
||||
|
||||
{{#let (moment-site-tz post.email.createdAtUTC) as |sentAt|}}
|
||||
on
|
||||
{{moment-format sentAt "D MMM YYYY"}}
|
||||
at
|
||||
{{moment-format sentAt "HH:mm"}}.
|
||||
{{/let}}
|
||||
{{/if}}
|
||||
|
||||
<br><br>
|
||||
Need to make a change?
|
||||
<button
|
||||
type="button"
|
||||
class="gh-revert-to-draft"
|
||||
{{on "click" (fn @close (hash afterTask="revertToDraftTask"))}}
|
||||
data-test-button="revert-to-draft"
|
||||
>
|
||||
<span>Unschedule and revert to draft →</span>
|
||||
</button>
|
||||
{{else if (not post.emailOnly)}}
|
||||
<br><br>
|
||||
<button
|
||||
type="button"
|
||||
class="gh-revert-to-draft"
|
||||
{{on "click" (fn @close (hash afterTask="revertToDraftTask"))}}
|
||||
data-test-button="revert-to-draft"
|
||||
>
|
||||
<span>Unpublish and revert to private draft →</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/let}}
|
||||
</div>
|
27
ghost/admin/app/components/editor/modals/update-flow.js
Normal file
27
ghost/admin/app/components/editor/modals/update-flow.js
Normal file
@ -0,0 +1,27 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default class UpdateFlowModalComponent extends Component {
|
||||
static modalOptions = {
|
||||
className: 'fullscreen-modal-total-overlay publish-modal',
|
||||
omitBackdrop: true,
|
||||
ignoreBackdropClick: true
|
||||
};
|
||||
|
||||
// We only show the newsletter name in the app if there's more than the single default newsletter.
|
||||
// However, here we can show historic email data so it could have been sent to a now-archived
|
||||
// newsletter in which case we want to force display of the newsletter name to avoid confusion.
|
||||
get showNewsletterName() {
|
||||
const {publishOptions} = this.args.data;
|
||||
|
||||
return !publishOptions.onlyDefaultNewsletter
|
||||
|| publishOptions.post.newsletter?.status === 'archived';
|
||||
}
|
||||
|
||||
@task
|
||||
*saveTask() {
|
||||
yield this.args.data.saveTask.perform();
|
||||
this.args.close();
|
||||
return true;
|
||||
}
|
||||
}
|
72
ghost/admin/app/components/editor/publish-buttons.hbs
Normal file
72
ghost/admin/app/components/editor/publish-buttons.hbs
Normal file
@ -0,0 +1,72 @@
|
||||
{{#if @publishManagement.publishOptions.user.isContributor}}
|
||||
{{#if @publishManagement.post.isDraft}}
|
||||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor gh-editor-preview-trigger"
|
||||
{{on "click" @publishManagement.openPreview}}
|
||||
{{on-key "cmd+p" @publishManagement.togglePreview}}
|
||||
data-test-button="contributor-preview"
|
||||
>
|
||||
<span>Preview</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
<GhTaskButton
|
||||
@buttonText="Save"
|
||||
@task={{@publishManagement.saveTask}}
|
||||
@runningText="Saving"
|
||||
@class="gh-btn gh-btn-icon gh-btn-editor gh-editor-save-trigger contributor-save-button"
|
||||
data-test-button="contributor-save" />
|
||||
{{else}}
|
||||
{{#if @publishManagement.post.isDraft}}
|
||||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor gh-editor-preview-trigger"
|
||||
{{on "click" @publishManagement.openPreview}}
|
||||
{{on-key "cmd+p" @publishManagement.togglePreview}}
|
||||
data-test-button="publish-preview"
|
||||
>
|
||||
<span>Preview</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor darkgrey gh-publish-trigger"
|
||||
{{on "click" @publishManagement.openPublishFlow}}
|
||||
{{on-key "cmd+shift+p"}}
|
||||
disabled={{@publishManagement.publishOptions.isLoading}}
|
||||
data-test-button="publish-flow"
|
||||
>
|
||||
<span>Publish</span>
|
||||
</button>
|
||||
{{else}}
|
||||
<GhTaskButton
|
||||
@task={{@publishManagement.saveTask}}
|
||||
@buttonText="Update"
|
||||
@runningText="Updating..."
|
||||
@successText="Updated"
|
||||
@class="gh-btn gh-btn-editor gh-editor-save-trigger gh-publish-trigger"
|
||||
@idleClass="green"
|
||||
@runningClass="midlightgrey"
|
||||
@successClass="midlightgrey"
|
||||
@failureClass="red"
|
||||
@showIcon={{false}}
|
||||
@disabled={{not @publishManagement.hasUnsavedChanges}}
|
||||
@autoReset={{true}}
|
||||
data-test-button="publish-save"
|
||||
/>
|
||||
|
||||
{{#if (not (and @publishManagement.post.isSent (not @publishManagement.post.isPublished)))}}
|
||||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor darkgrey gh-publish-trigger"
|
||||
{{on "click" @publishManagement.openUpdateFlow}}
|
||||
data-test-button="update-flow"
|
||||
>
|
||||
<span>
|
||||
{{if @publishManagement.post.isScheduled "Unschedule" "Unpublish"}}
|
||||
</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
11
ghost/admin/app/components/editor/publish-management.hbs
Normal file
11
ghost/admin/app/components/editor/publish-management.hbs
Normal file
@ -0,0 +1,11 @@
|
||||
{{yield (hash
|
||||
post=@post
|
||||
publishOptions=this.publishOptions
|
||||
openPreview=this.openPreview
|
||||
togglePreview=this.togglePreview
|
||||
saveTask=this.saveTask
|
||||
saveButtonTaskGroup=this.saveButtonTaskGroup
|
||||
hasUnsavedChanges=@hasUnsavedChanges
|
||||
openPublishFlow=this.openPublishFlow
|
||||
openUpdateFlow=this.openUpdateFlow
|
||||
)}}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user