first commit
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled

This commit is contained in:
2026-04-22 19:51:20 +07:00
commit 93d1b7c3d3
579 changed files with 99797 additions and 0 deletions
+26
View 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
View 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
View 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
+70
View File
@@ -0,0 +1,70 @@
/* eslint-env node */
module.exports = {
root: true,
parser: '@babel/eslint-parser',
parserOptions: {
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: {
'ghost/filenames/match-exported-class': ['off'],
// Enforce kebab-case (lowercase with hyphens) for all filenames
'ghost/filenames/match-regex': ['error', '^[a-z0-9.-]+$', false],
'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'
},
overrides: [{
files: 'tests/**/*.js',
env: {
embertest: true,
mocha: true
},
extends: [
'plugin:ghost/test'
],
rules: {
'ghost/ember/no-invalid-debug-function-arguments': 'off',
'ghost/mocha/no-setup-in-describe': 'off'
}
}]
};
+169
View File
@@ -0,0 +1,169 @@
add|ember-template-lint|no-action|5|11|5|11|8eaebb48eca1563c6e0b18581df84ab59188d971|1746489600000|||app/components/gh-cm-editor.hbs
add|ember-template-lint|no-passed-in-event-handlers|5|4|5|4|3a763e253744b070633bb8bd424b6c8e55f6b20a|1746489600000|||app/components/gh-cm-editor.hbs
add|ember-template-lint|no-invalid-interactive|1|103|1|103|534029ab0ba1b74eff4a2f31c8b4dd9f1460316a|1746489600000|||app/components/gh-context-menu.hbs
add|ember-template-lint|no-invalid-interactive|5|53|5|53|9647ef6afba919b2af04fe551b0fdf0fb63be849|1746489600000|||app/components/gh-context-menu.hbs
add|ember-template-lint|no-action|5|18|5|18|0c80a75b2a80d404755333991c266c81c97c9cda|1746489600000|||app/components/gh-date-time-picker.hbs
add|ember-template-lint|no-action|9|19|9|19|d12bcf1144bfb2fe70e7ab0f66836f1c6207a589|1746489600000|||app/components/gh-editor.hbs
add|ember-template-lint|no-action|10|20|10|20|16d94650de2ffbe8ee3f2ce3ba5ca97a6304b739|1746489600000|||app/components/gh-editor.hbs
add|ember-template-lint|no-action|11|17|11|17|30d64b1bf8990e2f84c52665690739fcc726e9f7|1746489600000|||app/components/gh-editor.hbs
add|ember-template-lint|no-action|2|45|2|45|55b33496610b2f4ef6b9fe62713f4b4ddee37f19|1746489600000|||app/components/gh-fullscreen-modal.hbs
add|ember-template-lint|no-action|10|29|10|29|ebbd89a393bcec7f537f2104ace2a6b1941a19a7|1746489600000|||app/components/gh-fullscreen-modal.hbs
add|ember-template-lint|no-action|11|32|11|32|ab89b6f10c519be1271386203e9439d261eecd67|1746489600000|||app/components/gh-fullscreen-modal.hbs
add|ember-template-lint|no-action|13|36|13|36|3776877637b49b65deef537c68ae490b2fb081a9|1746489600000|||app/components/gh-fullscreen-modal.hbs
add|ember-template-lint|no-invalid-interactive|2|45|2|45|918fdec8490009c6197091e319d6ec52ee95b983|1746489600000|||app/components/gh-fullscreen-modal.hbs
add|ember-template-lint|link-href-attributes|8|8|8|8|2078c6e43d1e5548ae745b7ac8cb736f7d2aaf68|1746489600000|||app/components/gh-image-uploader-with-preview.hbs
add|ember-template-lint|no-invalid-interactive|8|60|8|60|2078c6e43d1e5548ae745b7ac8cb736f7d2aaf68|1746489600000|||app/components/gh-image-uploader-with-preview.hbs
add|ember-template-lint|require-valid-alt-text|3|13|3|13|079fc89fa5c7c47f6b0b219820cdda3819c44e26|1746489600000|||app/components/gh-image-uploader-with-preview.hbs
add|ember-template-lint|no-action|12|58|12|58|031d04576149d3034549aa3d14bc26da704cd70c|1746489600000|||app/components/gh-image-uploader.hbs
add|ember-template-lint|no-action|17|75|17|75|bbc5d9a459cd07e56d8c79dde619775b89d7cc89|1746489600000|||app/components/gh-image-uploader.hbs
add|ember-template-lint|no-action|22|52|22|52|b1bd53a513ad82434d5a0a9a96441a45d416d7ad|1746489600000|||app/components/gh-image-uploader.hbs
add|ember-template-lint|no-action|31|16|31|16|4c64ddeaf795ee831d0d7346667a305814ca9855|1746489600000|||app/components/gh-image-uploader.hbs
add|ember-template-lint|no-action|32|15|32|15|b1bd53a513ad82434d5a0a9a96441a45d416d7ad|1746489600000|||app/components/gh-image-uploader.hbs
add|ember-template-lint|no-invalid-interactive|22|52|22|52|74dea739bef284d6557987ff3da53fa1278030e2|1746489600000|||app/components/gh-image-uploader.hbs
add|ember-template-lint|no-action|10|16|10|16|8d8dd8c2cb5f9910c2de5ca6acc76ee4262a876e|1746489600000|||app/components/gh-members-import-mapping-input.hbs
add|ember-template-lint|no-action|9|36|9|36|05358b6ec6e9afbaa47416266c49f40a3ffb4490|1746489600000|||app/components/gh-members-import-table.hbs
add|ember-template-lint|no-action|10|36|10|36|c5ce93cf577ec47970f715ed83ed11a63adb7a63|1746489600000|||app/components/gh-members-import-table.hbs
add|ember-template-lint|no-redundant-role|6|20|6|20|bc4fbabe3d468440d8a2c70e92cec991caca41e2|1746489600000|||app/components/gh-post-bookmark.hbs
add|ember-template-lint|no-redundant-role|14|64|14|64|ce988c0098c6e4845fc3307eb523b8b1687a3ecf|1746489600000|||app/components/gh-post-bookmark.hbs
add|ember-template-lint|no-action|36|43|36|43|66cebfc8448eced0bccf3faa57b4af0cd633e65e|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|37|47|37|47|70f3e9aa9a9aa52142c4e0c015a8f173d12b05ed|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|56|41|56|41|43cc094c1a6b50ecd24c8af325dfdfcac863bb14|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|57|41|57|41|e7f429eefd04a55d4ef1875fe601da1b69964364|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|87|50|87|50|0e827c1770073f988535ceb9d6c49808a153d896|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|104|39|104|39|5cbc9d2abf29e108bfc207579df1206485e2a74d|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|105|43|105|43|1f2dd4961e9757ede9706bb0aa9b8baf9c49b22b|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|116|132|116|132|90dbb84741c72aa86a601e1cb95c066bd53898bd|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|123|46|123|46|56ca074eb0236b8becc4084423056a8ffa4453e9|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|158|73|158|73|b07bf9d50af5f00861571521b150cf6504b90704|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|172|56|172|56|38755451c044c56040684339301c21b2aab49ecb|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|190|50|190|50|9f1c3c88187e5db774f97704269e372f8331eba5|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|196|50|196|50|97acfb2045b33a97621258596b631362ad4c56d5|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|202|51|202|51|358336432fcb413c522c5f1ff3062a68ef9f449f|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|208|50|208|50|78a45cbb7eafdda134d96f6035b0026f339e75ad|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|214|50|214|50|693211baf08c011e77284b483c30e28ecaf63520|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|223|105|223|105|de5b68b49193c72f85c0e478f151a00180321d91|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|236|167|236|167|0145b67f0faef0aad141c6a4269c35c6ef8f0a47|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|601|163|601|163|0145b67f0faef0aad141c6a4269c35c6ef8f0a47|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|607|34|607|34|3d44a2ad21d60d18bed6f79265caa4887361aaa4|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|616|47|616|47|67551960e57b2ad06d24289e0d6f256dc5635cae|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|617|51|617|51|50e3029f4c845049ff10130cf0dc2ea06c7a3d2d|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|632|47|632|47|0d852775b4028d61c2c91b5d821c79de3cdbd1de|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|633|51|633|51|194439ec4cb10e176eecb1f55c2995c00f6c67b4|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|647|47|647|47|3be93048908ab43d74726c3983982fcb9a3570e3|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|648|51|648|51|40871d259cc307345b8096e4215ae58e5886b724|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|674|166|674|166|0145b67f0faef0aad141c6a4269c35c6ef8f0a47|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|681|34|681|34|3d44a2ad21d60d18bed6f79265caa4887361aaa4|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|686|44|686|44|d2abbb4bb55b6cb1131ca0bc56c31632b32b980c|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|687|44|687|44|3ede83bd64d206d665edb4f1b73f03b4122edfad|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|697|47|697|47|13b7064b520a924d9e57a4a680621ce979d23923|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|698|51|698|51|b0fc38b818f4ca2613684588e4122e12da0090b9|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|713|47|713|47|f34f084dc72c079be9ba3eb93c255bd8853612bd|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|714|51|714|51|c5c09d001fa96a623649cc88ef13b3c6c164f05b|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|742|167|742|167|0145b67f0faef0aad141c6a4269c35c6ef8f0a47|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|748|34|748|34|3d44a2ad21d60d18bed6f79265caa4887361aaa4|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|753|44|753|44|32101c0834d9e8d2caa87dee9cb1a92fe633cfde|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|754|44|754|44|c8a82286cfe4220cb2d8a059b1daf7f6c8d54cf5|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|764|47|764|47|31800fb83f1797de5d91b7007c48432cf2fae803|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|765|51|765|51|5ce0788874489341385f031810e9f7f62724aaf8|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|778|47|778|47|1cbd7d922a9d202772a9dca214d97bed18777d6e|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|779|51|779|51|16fb2c7c87dc282c6ee49a032aaaf22628b88084|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|810|168|810|168|0145b67f0faef0aad141c6a4269c35c6ef8f0a47|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|816|34|816|34|3d44a2ad21d60d18bed6f79265caa4887361aaa4|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|823|50|823|50|d03e7bbf2b6de94b08fae1c33bd5a2f16874e03a|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|825|48|825|48|bca167f0250b40d56ff1ed40ec21bc88f8e27ec3|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|836|50|836|50|93ae447fb1054de4dbe501fc60167a6ff3273687|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|838|48|838|48|ecb9ed2577be7ea0300563f22d8e9fde2fed12a6|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-passed-in-event-handlers|36|36|36|36|fed655605208b290a18b9f7da51a6cf7b40c0e9a|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-passed-in-event-handlers|104|32|104|32|187e85e14470b453a1e6df8c5f18549d11c441a0|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-passed-in-event-handlers|616|40|616|40|f290a04fd56f613d80d61244d161631f630185a8|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-passed-in-event-handlers|632|40|632|40|c1af88109f705acc93fab4ee7b4d89096136ffe1|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-passed-in-event-handlers|647|40|647|40|852cbb2c63e19a28d700d3b18e6f887edef303fa|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-passed-in-event-handlers|697|40|697|40|58185c92e8b6261ea1483a70296d9fa837d3f2f5|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-passed-in-event-handlers|713|40|713|40|c4353acd715c396564c69389edd0a52246d3b966|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-passed-in-event-handlers|764|40|764|40|29f26e46559dc40d9724a05b7516cb52f1481aaa|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-passed-in-event-handlers|778|40|778|40|42e0617f832585eaa056c9c66dffe1a126318faf|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-passed-in-event-handlers|823|40|823|40|d1dccfeee5b103fac3b14d5b4567bc65ee08fd5a|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-passed-in-event-handlers|836|40|836|40|055c4b70aa8daba6b97077eefa95ebed7f7f5315|1746489600000|||app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|4|14|4|14|c8f6146f9286ec1b289e28983ab446386f390f01|1746489600000|||app/components/gh-psm-authors-input.hbs
add|ember-template-lint|no-action|9|24|9|24|2e612be5e2b8e0b792f951c3c75b2f71df880365|1746489600000|||app/components/gh-psm-template-select.hbs
add|ember-template-lint|no-action|7|16|7|16|86819b20fcc42bc1d4607519c1d45532ee337884|1746489600000|||app/components/gh-psm-visibility-input.hbs
add|ember-template-lint|no-autofocus-attribute|13|16|13|16|fa0ffb960072633b72117849e3927673be0059af|1746489600000|||app/components/gh-search-input.hbs
add|ember-template-lint|require-iframe-title|1|0|1|0|956ab219134ac63aec3fd2d35582076c21631b75|1746489600000|||app/components/gh-site-iframe.hbs
add|ember-template-lint|no-yield-only|1|0|1|0|a5fa6e8c1e0f03fb31b5cd17770e4368d44932e4|1746489600000|||app/components/gh-text-input.hbs
add|ember-template-lint|no-action|1|51|1|51|674a942db62e014b25156b02d014f6c63b2f1cb2|1746489600000|||app/components/gh-theme-error-li.hbs
add|ember-template-lint|no-action|2|11|2|11|2b1317e72b94ec31bf2700acc3f041e6be974b72|1746489600000|||app/components/gh-uploader.hbs
add|ember-template-lint|no-action|7|13|7|13|add4b57d48167f809045245535d45bedb00cb753|1746489600000|||app/components/gh-uploader.hbs
add|ember-template-lint|no-action|8|22|8|22|ce1eba2b791c0f120baf10871fd3949c1b9f6a79|1746489600000|||app/components/gh-uploader.hbs
add|ember-template-lint|no-action|9|22|9|22|0209f233628ec084b87894b4be9073c29f2a4f47|1746489600000|||app/components/gh-uploader.hbs
add|ember-template-lint|no-passed-in-event-handlers|4|4|4|4|3dedc53191768d81765eac4a7a049a9aba7df442|1746489600000|||app/components/gh-url-input.hbs
add|ember-template-lint|no-action|1|71|1|71|2e6351f546807d88cc8eb9dbe8baa149468b5cb9|1746489600000|||app/components/gh-view-title.hbs
add|ember-template-lint|no-invalid-role|1|0|1|0|3e651d38e0110e1be20e5082075db1b879b59a36|1746489600000|||app/components/gh-view-title.hbs
add|ember-template-lint|no-action|5|50|5|50|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-impersonate-member.hbs
add|ember-template-lint|no-action|5|74|5|74|d465b362b15b90cf42a093e72895155f49cdf6f2|1746489600000|||app/components/modal-impersonate-member.hbs
add|ember-template-lint|no-action|43|57|43|57|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|50|56|50|56|190532b9954beb4e7e9e0f1c51d170e64d4a5315|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|56|34|56|34|ff609af61e9159dfc5386543d559f6d217d39531|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|57|29|57|29|9a0a72738b6e1a4f46415b2328c37d1814562717|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|110|89|110|89|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|116|91|116|91|031d04576149d3034549aa3d14bc26da704cd70c|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|119|116|119|116|7e581bf2ffd5254ae851201e1f23cb9eaa9b198b|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|129|120|129|120|031d04576149d3034549aa3d14bc26da704cd70c|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|132|103|132|103|7e581bf2ffd5254ae851201e1f23cb9eaa9b198b|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|143|112|143|112|031d04576149d3034549aa3d14bc26da704cd70c|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|147|110|147|110|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|153|97|153|97|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|156|112|156|112|031d04576149d3034549aa3d14bc26da704cd70c|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|160|99|160|99|031d04576149d3034549aa3d14bc26da704cd70c|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|163|110|163|110|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|171|91|171|91|031d04576149d3034549aa3d14bc26da704cd70c|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|174|102|174|102|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|181|95|181|95|031d04576149d3034549aa3d14bc26da704cd70c|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-action|185|102|185|102|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-import-members.hbs
add|ember-template-lint|no-invalid-interactive|28|24|28|24|42a29ae16e22270f0590c9ce5caa7bfec541ca0b|1746489600000|||app/components/modal-member-tier.hbs
add|ember-template-lint|no-action|5|57|5|57|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-members-label-form.hbs
add|ember-template-lint|no-action|14|45|14|45|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-members-label-form.hbs
add|ember-template-lint|no-action|23|59|23|59|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-members-label-form.hbs
add|ember-template-lint|no-action|23|83|23|83|d465b362b15b90cf42a093e72895155f49cdf6f2|1746489600000|||app/components/modal-members-label-form.hbs
add|ember-template-lint|no-action|34|31|34|31|a8ad062b8379233b7970fe5ea74296fdf5011567|1746489600000|||app/components/modal-members-label-form.hbs
add|ember-template-lint|no-action|47|82|47|82|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-members-label-form.hbs
add|ember-template-lint|no-action|49|16|49|16|d465b362b15b90cf42a093e72895155f49cdf6f2|1746489600000|||app/components/modal-members-label-form.hbs
add|ember-template-lint|no-action|55|113|55|113|c259009ff744c2e9f7bb273e0eb3a3879036ba85|1746489600000|||app/components/modal-members-label-form.hbs
add|ember-template-lint|no-redundant-role|85|24|85|24|ce988c0098c6e4845fc3307eb523b8b1687a3ecf|1746489600000|||app/components/modal-post-success.hbs
add|ember-template-lint|no-redundant-role|136|24|136|24|ce988c0098c6e4845fc3307eb523b8b1687a3ecf|1746489600000|||app/components/modal-post-success.hbs
add|ember-template-lint|no-action|4|53|4|53|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-unsubscribe-members.hbs
add|ember-template-lint|no-action|50|89|50|89|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-unsubscribe-members.hbs
add|ember-template-lint|no-action|54|71|54|71|141d456b03124abca146e58e4ae15825fdd040bb|1746489600000|||app/components/modal-unsubscribe-members.hbs
add|ember-template-lint|require-valid-alt-text|4|12|4|12|8369d1b06deac93e8c8e05444670c15182aea434|1746489600000|||app/templates/application-error.hbs
add|ember-template-lint|no-redundant-role|91|40|91|40|9d2eded16257516b455504637aab4057b3c0c9ef|1746489600000|||app/templates/mentions.hbs
add|ember-template-lint|no-redundant-role|113|20|113|20|0418319e2dce98b674dbb6657d185f465d21f87d|1746489600000|||app/templates/mentions.hbs
add|ember-template-lint|no-action|20|31|20|31|3aa834e53af871a821ebb1b47f7a04f925c2b593|1746489600000|||app/templates/setup.hbs
add|ember-template-lint|no-action|21|35|21|35|ae65e93ed2b79a307e0eba50b154fe84ee1ae210|1746489600000|||app/templates/setup.hbs
add|ember-template-lint|no-action|37|31|37|31|eefdee43411bdd45c2786b9e826e6b550fd6212d|1746489600000|||app/templates/setup.hbs
add|ember-template-lint|no-action|38|35|38|35|a8e4bd4c57a8f2df65749b0cfa4349da22b2a0aa|1746489600000|||app/templates/setup.hbs
add|ember-template-lint|no-action|55|31|55|31|1709109776bf3fc47aaecc21e6d3ec8a0489ad6b|1746489600000|||app/templates/setup.hbs
add|ember-template-lint|no-action|56|35|56|35|8000241a81189d3876d264331ec0c9b6476df076|1746489600000|||app/templates/setup.hbs
add|ember-template-lint|no-action|73|31|73|31|6756e119daad4aa143724e9b56a6aef744d65251|1746489600000|||app/templates/setup.hbs
add|ember-template-lint|no-action|74|35|74|35|26b1d89c0e5bbcd629a215903c0819d35f798aa6|1746489600000|||app/templates/setup.hbs
add|ember-template-lint|no-passed-in-event-handlers|20|24|20|24|f0b7babba7593639d68dadae2f19e7144dd8c63e|1746489600000|||app/templates/setup.hbs
add|ember-template-lint|no-passed-in-event-handlers|37|24|37|24|2692530760cb1f7156dcf9570aacf0f8baca2770|1746489600000|||app/templates/setup.hbs
add|ember-template-lint|no-passed-in-event-handlers|55|24|55|24|ce0d7e2e732b22ce3643fb9b1ac6ecef07c275ff|1746489600000|||app/templates/setup.hbs
add|ember-template-lint|no-passed-in-event-handlers|73|24|73|24|80681fcec2258c3d81a231bbd635aa1f03ff1452|1746489600000|||app/templates/setup.hbs
add|ember-template-lint|no-action|6|108|6|108|ccc38f66549f9baedaa3b9943ae6634ea8f99e69|1746489600000|||app/templates/tags.hbs
add|ember-template-lint|no-action|7|110|7|110|c3819ce2b6989e8596be570ed0c9fb82b5012521|1746489600000|||app/templates/tags.hbs
add|ember-template-lint|require-valid-alt-text|15|32|15|32|80c1ce6724481312363dc4e1db42bf28b41909f2|1746489600000|||app/templates/whatsnew.hbs
add|ember-template-lint|no-invalid-interactive|8|51|8|51|66a27dbed218d15e49e91c72a93215ad0d90f778|1746489600000|||app/components/gh-power-select/trigger.hbs
add|ember-template-lint|no-yield-only|1|0|1|0|a5fa6e8c1e0f03fb31b5cd17770e4368d44932e4|1746489600000|||app/components/gh-token-input/label-token.hbs
add|ember-template-lint|no-yield-only|1|0|1|0|a5fa6e8c1e0f03fb31b5cd17770e4368d44932e4|1746489600000|||app/components/gh-token-input/tag-token.hbs
add|ember-template-lint|no-action|8|19|8|19|73ac7d3892fcbcf15c3d5c44fca14dd21016daea|1746489600000|||app/components/gh-token-input/trigger.hbs
add|ember-template-lint|no-positive-tabindex|46|8|46|8|6118264a9a0599fab6ad4da7264c1bfffa88687e|1746489600000|||app/components/gh-token-input/trigger.hbs
add|ember-template-lint|require-iframe-title|27|20|27|20|94e58d11848d5613900c2188ba63dac41f4c03bb|1746489600000|||app/components/modals/email-preview.hbs
add|ember-template-lint|require-iframe-title|42|16|42|16|a3292b469dc37f2f4791e7f224b0b65c8ecf5d18|1746489600000|||app/components/modals/email-preview.hbs
add|ember-template-lint|no-autofocus-attribute|21|20|21|20|942419d05c04ded6716f09faecd6b1ab55418121|1746489600000|||app/components/modals/new-custom-integration.hbs
add|ember-template-lint|no-invalid-interactive|2|37|2|37|e21ba31f54b631a428c28a1c9f88d0dc66f2f5fc|1746489600000|||app/components/modals/search.hbs
add|ember-template-lint|no-redundant-role|11|20|11|20|bc4fbabe3d468440d8a2c70e92cec991caca41e2|1746489600000|||app/components/dashboard/onboarding/share-modal.hbs
remove|ember-template-lint|no-action|6|108|6|108|ccc38f66549f9baedaa3b9943ae6634ea8f99e69|1746489600000|||app/templates/tags.hbs
remove|ember-template-lint|no-action|7|110|7|110|c3819ce2b6989e8596be570ed0c9fb82b5012521|1746489600000|||app/templates/tags.hbs
remove|ember-template-lint|require-valid-alt-text|4|12|4|12|8369d1b06deac93e8c8e05444670c15182aea434|1746489600000|||app/templates/application-error.hbs
remove|ember-template-lint|no-action|1|71|1|71|2e6351f546807d88cc8eb9dbe8baa149468b5cb9|1746489600000|||app/components/gh-view-title.hbs
remove|ember-template-lint|no-invalid-role|1|0|1|0|3e651d38e0110e1be20e5082075db1b879b59a36|1746489600000|||app/components/gh-view-title.hbs
add|ember-template-lint|no-yield-only|1|0|1|0|a5fa6e8c1e0f03fb31b5cd17770e4368d44932e4|1771200000000|||app/components/gh-view-title.hbs
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
'ember-template-lint': {
daysToDecay: null
}
};
+12
View File
@@ -0,0 +1,12 @@
module.exports = {
extends: "recommended",
rules: {
'no-forbidden-elements': ['meta', 'html', 'script'],
'no-implicit-this': {allow: ['noop', 'now', 'site-icon-style']},
'no-inline-styles': false,
'no-duplicate-landmark-elements': false,
'no-pointer-down-event-binding': false,
'no-triple-curlies': false
}
};
+3
View File
@@ -0,0 +1,3 @@
{
"ignore_dirs": ["tmp", "dist"]
}
+59
View File
@@ -0,0 +1,59 @@
# Ghost-Admin
This is the home of the Ember.js-based Admin app that ships with [Ghost](https://github.com/tryghost/ghost).
## Test
### Running tests in the browser
Run all tests in the browser by running `pnpm dev` in the Ghost monorepo and visiting http://localhost:4200/tests. The code is hotloaded on change and you can filter which tests to run.
[Testing public documentation](https://ghost.notion.site/Testing-Ember-560cec6700fc4d37a58b3ba9febb4b4b)
---
Tip: You can use `this.timeout(0); await this.pauseTest();` in your tests to temporarily pause the execution of browser tests. Use the browser console to inspect and debug the DOM, then resume tests by running `resumeTest()` directly in the browser console ([docs](https://guides.emberjs.com/v3.28.0/testing/testing-application/#toc_debugging-your-tests))
### Running tests in the CLI
To build and run tests in the CLI, you can use:
```bash
TZ=UTC pnpm test
```
_Note the `TZ=UTC` environment variable which is currently required to get tests working if your system timezone doesn't match UTC._
---
However, this is very slow when writing tests, as it requires the app to be rebuilt on every change. Instead, create a separate watching build with:
```bash
pnpm build --environment=test -w -o="dist-test"
```
Then run tests with:
```bash
TZ=UTC pnpm test 1 --reporter dot --path="dist-test"
```
The `--reporter dot` shows a dot (`.`) for every successful test, and `F` for every failed test. It renders the output of the failed tests only.
---
To run a specific test file:
```bash
TZ=UTC pnpm test 1 --reporter dot --path="dist-test" -mp=tests/unit/helpers/gh-count-characters-test.js
```
---
To have a full list of the available options, run
```bash
ember exam --help
```
# Copyright & License
Copyright (c) 2013-2026 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.
+30
View 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 JavaScript](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!)
+54
View File
@@ -0,0 +1,54 @@
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-timezone';
import {registerWarnHandler} from '@ember/debug';
moment.updateLocale('en', {
relativeTime: {
m: '1 minute'
}
});
const rootElement = document.getElementById('ember-app');
const App = Application.extend({
Resolver,
modulePrefix: config.modulePrefix,
podModulePrefix: config.podModulePrefix,
// eslint-disable-next-line
customEvents: {
touchstart: null,
touchmove: null,
touchend: null,
touchcancel: null
},
...(rootElement ? {
rootElement: '#ember-app'
} : {})
});
// 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;
+52
View File
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html class="no-js" lang="en">
<head>
<meta charset="UTF-8" />
<title>Ghost</title>
{{content-for "head"}}
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1, minimal-ui, viewport-fit=cover" />
<meta name="pinterest" content="nopin" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="application-name" content="Ghost" />
<meta name="apple-mobile-web-app-title" content="Ghost" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<link rel="shortcut icon" href="assets/img/favicon.ico" />
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon.png" />
<!-- variables that we don't want postcss-custom-properties to remove -->
<style>
:root {
--editor-sidebar-width: 0px;
}
</style>
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/vendor.css">
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/ghost.css" title="light">
{{content-for "head-footer"}}
</head>
<body>
<div class="ember-load-indicator">
<div class="gh-loading-content">
<video width="100" height="100" loop autoplay muted playsinline preload="metadata" style="width: 100px; height: 100px;">
<source src="assets/videos/logo-loader.mp4" type="video/mp4" />
<div class="gh-loading-spinner"></div>
</video>
</div>
</div>
{{content-for "body"}}
{{content-for "body-footer"}}
<script src="{{rootURL}}assets/vendor.js"></script>
<script src="{{rootURL}}assets/ghost.js"></script>
</body>
</html>
+70
View File
@@ -0,0 +1,70 @@
import EmberRouter from '@ember/routing/router';
import config from 'ghost-admin/config/environment';
import ghostPaths from 'ghost-admin/utils/ghost-paths';
const Router = EmberRouter.extend({
location: config.locationType, // use HTML5 History API instead of hash-tag based URLs
rootURL: ghostPaths().adminRoot // admin interface lives under sub-directory /ghost
});
// eslint-disable-next-line array-callback-return
Router.map(function () {
this.route('home', {path: '/'});
this.route('setup');
this.route('setup.done', {path: '/setup/done'});
this.route('signin');
this.route('signin-verify', {path: '/signin/verify'});
this.route('signout');
this.route('signup', {path: '/signup/:token'});
this.route('reset', {path: '/reset/:token'});
this.route('site');
this.route('dashboard');
this.route('launch');
this.route('pro', function () {
this.route('pro-sub', {path: '/*sub'});
});
this.route('posts');
this.route('posts.debug', {path: '/posts/analytics/:post_id/debug'});
this.route('restore-posts', {path: '/restore'});
this.route('pages');
this.route('lexical-editor', {path: 'editor'}, function () {
this.route('new', {path: ':type'});
this.route('edit', {path: ':type/:post_id'});
});
this.route('tag.new', {path: '/tags/new'});
this.route('tag', {path: '/tags/:tag_slug'});
this.route('explore', function () {
// actual Ember route, not rendered in iframe
this.route('connect');
// iframe sub pages, used for categories
this.route('explore-sub', {path: '/*sub'}, function () {
// needed to allow search to work, as it uses URL
// params for search queries. They don't need to
// be visible, but may not be cut off.
this.route('explore-query', {path: '/*query'});
});
});
this.route('migrate', function () {
this.route('migrate', {path: '/*platform'});
});
this.route('member.new', {path: '/members/new'});
this.route('member', {path: '/members/:member_id'});
this.route('members-activity');
this.route('react-fallback', {path: '/*path'});
this.route('designsandbox');
});
export default Router;
+21
View File
@@ -0,0 +1,21 @@
export default function () {
this.transition(
this.hasClass('fullscreen-modal-container'),
this.toValue(true),
this.use('fade', {duration: 150}),
this.reverse('fade', {duration: 150})
);
this.transition(
this.hasClass('fade-transition'),
this.use('crossFade', {duration: 100})
);
// TODO: Maybe animate with explode. gh-unsplash-window should ideally slide in from bottom to top of screen
// this.transition(
// this.hasClass('gh-unsplash-window'),
// this.toValue(true),
// this.use('toUp', {duration: 500}),
// this.reverse('toDown', {duration: 500})
// );
}
@@ -0,0 +1,11 @@
self.deprecationWorkflow = self.deprecationWorkflow || {};
self.deprecationWorkflow.config = {
workflow: [
// remove once ember-drag-drop removes usage of Component#sendAction
// https://github.com/mharris717/ember-drag-drop/issues/155
{handler: 'silence', matchId: 'ember-component.send-action'},
// remove once liquid-fire and liquid-wormhole remove uses of `this.$()`
{handler: 'silence', matchId: 'ember-views.curly-components.jquery-element'}
]
};
+82
View File
@@ -0,0 +1,82 @@
/* eslint-env node */
'use strict';
module.exports = function (environment) {
let ENV = {
modulePrefix: 'ghost-admin',
environment,
editorUrl: process.env.EDITOR_URL || '',
rootURL: '',
locationType: 'trailing-hash',
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
},
// @TODO verify that String/Function need to be enabled
EXTEND_PROTOTYPES: {
Date: false,
Array: true,
String: true,
Function: false
}
},
APP: {
// Here you can pass flags/options to your application instance
// when it is created
// override the default version string which contains git info from
// https://github.com/cibernox/git-repo-version. Only include the
// `major.minor` version numbers
version: require('../package.json').version.match(/^(\d+\.)?(\d+)/)[0]
},
'ember-simple-auth': { },
'@sentry/ember': {
disablePerformance: true,
sentry: {}
}
};
if (environment === 'development') {
// ENV.APP.LOG_RESOLVER = true;
ENV.APP.LOG_ACTIVE_GENERATION = true;
ENV.APP.LOG_TRANSITIONS = true;
ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
ENV.APP.LOG_VIEW_LOOKUPS = true;
// Enable mirage here in order to mock API endpoints during development
ENV['ember-cli-mirage'] = {
enabled: false
};
}
if (environment === 'test') {
// Testem prefers this...
ENV.rootURL = '/';
ENV.locationType = 'none';
// keep test console output quieter
ENV.APP.LOG_ACTIVE_GENERATION = false;
ENV.APP.LOG_VIEW_LOOKUPS = false;
ENV.APP.rootElement = '#ember-testing';
ENV.APP.autoboot = false;
// Without manually setting this, pretender won't track requests
ENV['ember-cli-mirage'] = {
trackRequests: true
};
// We copy the dynamically loaded editor file into the ghost assets
// directory in the dev/test env so that tests can load it. We need to
// set the config appropriately here so that the fetchKoenigLexical
// utility creates the right URL
ENV.editorFilename = 'koenig-lexical.umd.js';
ENV.editorHash = 'test';
}
return ENV;
};
@@ -0,0 +1,5 @@
{
"application-template-wrapper": false,
"jquery-integration": true,
"template-only-glimmer-components": true
}
+12
View File
@@ -0,0 +1,12 @@
/* eslint-env node */
const browsers = [
'last 2 Chrome versions',
'last 2 Firefox versions',
'last 3 Safari versions',
'last 2 Edge versions'
];
module.exports = {
browsers
};
+275
View File
@@ -0,0 +1,275 @@
/* eslint-env node */
'use strict';
// Check Node.js version compatibility before building
require('./lib/check-node-version')();
const EmberApp = require('ember-cli/lib/broccoli/ember-app');
const concat = require('broccoli-concat');
const mergeTrees = require('broccoli-merge-trees');
const Terser = require('broccoli-terser-sourcemap');
const Funnel = require('broccoli-funnel');
const webpack = require('webpack');
const environment = EmberApp.env();
const isDevelopment = environment === 'development';
const isProduction = environment === 'production';
const isTesting = environment === 'test';
const postcssImport = require('postcss-import');
const postcssCustomProperties = require('postcss-custom-properties');
const postcssColorModFunction = require('postcss-color-mod-function');
const postcssCustomMedia = require('postcss-custom-media');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const codemirrorAssets = function () {
let codemirrorFiles = [
'lib/codemirror.js',
'mode/htmlmixed/htmlmixed.js',
'mode/xml/xml.js',
'mode/css/css.js',
'mode/javascript/javascript.js'
];
if (environment === 'test') {
return {import: codemirrorFiles};
}
let config = {};
config.public = {
include: codemirrorFiles,
destDir: '/',
processTree(tree) {
let jsTree = concat(tree, {
outputFile: 'assets/codemirror/codemirror.js',
headerFiles: ['lib/codemirror.js'],
inputFiles: ['mode/**/*'],
sourceMapConfig: {enabled: false}
});
if (isProduction) {
jsTree = new Terser(jsTree);
}
let mergedTree = mergeTrees([tree, jsTree]);
return new Funnel(mergedTree, {include: ['assets/**/*', 'theme/**/*']});
}
};
// put the files in vendor ready for importing into the test-support file
if (environment === 'development') {
config.vendor = codemirrorFiles;
}
return config;
};
let denylist = [];
if (process.env.CI) {
denylist.push('ember-cli-eslint');
}
let publicAssetURL;
if (isTesting) {
publicAssetURL = undefined;
} else if (process.env.GHOST_CDN_URL) {
publicAssetURL = process.env.GHOST_CDN_URL + 'assets/';
} else {
publicAssetURL = 'assets/';
}
module.exports = function (defaults) {
let app = new EmberApp(defaults, {
addons: {denylist},
babel: {
plugins: [
require.resolve('babel-plugin-transform-react-jsx')
]
},
'ember-cli-babel': {
optional: ['es6.spec.symbols'],
includePolyfill: false
},
'ember-composable-helpers': {
only: ['join', 'optional', 'pick', 'toggle', 'toggle-action', 'compute']
},
'ember-promise-modals': {
excludeCSS: true
},
outputPaths: {
app: {
js: 'assets/ghost.js',
css: {
app: 'assets/ghost.css',
// TODO: find a way to use the .min file with the lazyLoader
'app-dark': 'assets/ghost-dark.css'
}
}
},
fingerprint: {
enabled: isProduction,
extensions: [
'js',
'css',
'png',
'jpg',
'jpeg',
'gif',
'map',
'svg',
'ttf',
'woff2',
'mp4',
'ico'
],
exclude: ['**/chunk*.map']
},
minifyJS: {
options: {
output: {
semicolons: true
}
}
},
minifyCSS: {
// postcss already handles minification and this was stripping required CSS
enabled: false
},
nodeAssets: {
codemirror: codemirrorAssets()
},
postcssOptions: {
compile: {
enabled: true,
plugins: [
{
module: postcssImport,
options: {
path: ['app/styles']
}
},
{
module: postcssCustomProperties,
options: {
preserve: false
}
},
{
module: postcssColorModFunction
},
{
module: postcssCustomMedia
},
{
module: autoprefixer
},
{
module: cssnano,
options: {
zindex: false,
// cssnano sometimes minifies animations incorrectly causing them to break
// See: https://github.com/ben-eb/gulp-cssnano/issues/33#issuecomment-210518957
reduceIdents: {
keyframes: false
},
discardUnused: {
keyframes: false
}
}
}
]
}
},
sourcemaps: {enabled: true},
svgJar: {
strategy: 'inline',
stripPath: false,
sourceDirs: [
'public/assets/icons'
],
optimizer: {
plugins: [
{prefixIds: true},
{cleanupIds: false},
{removeDimensions: true},
{removeTitle: !isDevelopment},
{removeXMLNS: true},
// Transforms on groups are necessary to work around Firefox
// not supporting transform-origin on line/path elements.
{convertPathData: {
applyTransforms: false
}},
{moveGroupAttrsToElems: false}
]
}
},
autoImport: {
publicAssetURL,
alias: {
'sentry-testkit/browser': 'sentry-testkit/dist/browser'
},
webpack: {
devtool: 'source-map',
resolve: {
fallback: {
util: require.resolve('util'),
path: require.resolve('path-browserify'),
fs: false
}
},
...(isDevelopment && {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
}
}),
plugins: [
new webpack.ProvidePlugin({
process: 'process/browser'
})
],
// disable verbose logging about webpack resolution mismatches
// - this is a known issue with mismatched versions of dependencies resulting in duplication rather than a single hoisted version
// - we don't plan on fixing this in the short term, so we just silence the noise
infrastructureLogging: {
level: 'error'
}
}
},
'ember-test-selectors': {
strip: false
}
});
// Stop: Normalize
app.import('node_modules/normalize.css/normalize.css');
// 'dem Styles
// import codemirror styles rather than lazy-loading so that
// our overrides work correctly
app.import('node_modules/codemirror/lib/codemirror.css');
app.import('node_modules/codemirror/theme/xq-light.css');
// 'dem Scripts
app.import('node_modules/google-caja-bower/html-css-sanitizer-bundle.js');
app.import('node_modules/keymaster/keymaster.js');
app.import('node_modules/reframe.js/dist/noframe.js');
// pull things we rely on via lazy-loading into the test-support.js file so
// that tests don't break when running via http://localhost:4200/tests
if (app.env === 'development') {
app.import('vendor/codemirror/lib/codemirror.js', {type: 'test'});
}
if (app.env === 'development' || app.env === 'test') {
// pull dynamic imports into the assets folder so that they can be lazy-loaded in tests
// also done in development env so http://localhost:4200/tests works
app.import('node_modules/@tryghost/koenig-lexical/dist/koenig-lexical.umd.js', {outputFile: 'ghost/assets/koenig-lexical/koenig-lexical.umd.js'});
}
return app.toTree();
};
+18
View File
@@ -0,0 +1,18 @@
{
"schemaVersion": "1.0.0",
"packages": [
{
"name": "ember-cli",
"version": "3.20.0",
"blueprints": [
{
"name": "app",
"outputRepo": "https://github.com/ember-cli/ember-new-output",
"codemodsSource": "ember-app-codemods-manifest@1",
"isBaseBlueprint": true,
"options": []
}
]
}
]
}
+1
View File
@@ -0,0 +1 @@
{"compilerOptions":{"target":"es6","experimentalDecorators":true},"exclude":["node_modules","bower_components","tmp","vendor",".git","dist"]}
+53
View File
@@ -0,0 +1,53 @@
/* eslint-env node */
'use strict';
const chalk = require('chalk');
const semver = require('semver');
/**
* Check Node.js version compatibility for Ember admin build
*
* The esm module (required by dependencies) has compatibility issues with
* Node.js versions 22.10.0 to 22.17.x. We previously patched esm to work
* around this, but to avoid maintaining patches, we now check the version
* and provide clear guidance.
*/
function checkNodeVersion() {
const nodeVersion = process.version;
const parsedVersion = semver.parse(nodeVersion);
/* eslint-disable no-console */
if (!parsedVersion) {
console.warn(chalk.yellow(`Warning: Could not parse Node.js version: ${nodeVersion}`));
return;
}
// Check if version is in the problematic range: >=22.10.0 <22.18.0
const isProblematicVersion = semver.satisfies(nodeVersion, '>=22.10.0 <22.18.0');
if (isProblematicVersion) {
console.error('\n');
console.error(chalk.red('='.repeat(80)));
console.error(chalk.red('ERROR: Incompatible Node.js version detected'));
console.error(chalk.red('='.repeat(80)));
console.error();
console.error(chalk.yellow(`Current Node.js version: ${chalk.bold(nodeVersion)}`));
console.error();
console.error(chalk.white('The Ember admin build requires the esm module, which has compatibility'));
console.error(chalk.white('issues with Node.js versions 22.10.0 through 22.17.x.'));
console.error();
console.error(chalk.white('Please use one of the following Node.js versions:'));
console.error(chalk.green(' • Node.js 22.18.0 or later'));
console.error();
console.error(chalk.white('To switch Node.js versions, you can use a version manager:'));
console.error(chalk.cyan(' • nvm: nvm install 22.18.0 && nvm use 22.18.0'));
console.error();
console.error(chalk.red('='.repeat(80)));
console.error();
process.exit(1);
}
}
module.exports = checkNodeVersion;
+6
View File
@@ -0,0 +1,6 @@
/* eslint-env node */
module.exports = {
rules: {
'brace-style': 'off'
}
};
+26
View File
@@ -0,0 +1,26 @@
/* eslint-disable ghost/ember/no-test-import-export */
import {applyEmberDataSerializers, discoverEmberDataModels} from 'ember-cli-mirage';
import {createServer} from 'miragejs';
import {isTesting, macroCondition} from '@embroider/macros';
import devRoutes from './routes-dev';
import testRoutes from './routes-test';
export default function (config) {
let finalConfig = {
...config,
models: {...discoverEmberDataModels(), ...config.models},
serializers: applyEmberDataSerializers(config.serializers),
routes
};
return createServer(finalConfig);
}
function routes() {
if (macroCondition(isTesting())) {
testRoutes.call(this);
} else {
devRoutes.call(this);
}
}
+25
View File
@@ -0,0 +1,25 @@
import ghostPaths from 'ghost-admin/utils/ghost-paths';
export default function () {
// allow any local requests outside of the namespace (configured below) to hit the real server
// _must_ be called before the namespace property is set
this.passthrough('/ghost/assets/**');
this.namespace = ghostPaths().apiRoot;
this.timing = 1000; // delay for each request, automatically set to 0 during testing
this.logging = true;
// Mock endpoints here to override real API requests during development, eg...
// this.put('/posts/:id/', versionMismatchResponse);
// mockTags(this);
// this.loadFixtures('settings');
// keep this line, it allows all other API requests to hit the real server
this.passthrough();
// add any external domains to make sure those get passed through too
this.passthrough('http://www.gravatar.com/**');
this.passthrough('https://cdn.jsdelivr.net/**');
this.passthrough('https://api.unsplash.com/**');
this.passthrough('https://ghost.org/**');
}
+143
View File
@@ -0,0 +1,143 @@
import ghostPaths from 'ghost-admin/utils/ghost-paths';
import mockApiKeys from './config/api-keys';
import mockAuthentication from './config/authentication';
import mockConfig from './config/config';
import mockEmailPreview from './config/email-preview';
import mockEmails from './config/emails';
import mockIntegrations from './config/integrations';
import mockInvites from './config/invites';
import mockLabels from './config/labels';
import mockMembers from './config/members';
import mockNewsletters from './config/newsletters';
import mockOffers from './config/offers';
import mockPages from './config/pages';
import mockPosts from './config/posts';
import mockRoles from './config/roles';
import mockSearchIndex from './config/search-index';
import mockSettings from './config/settings';
import mockSite from './config/site';
import mockSlugs from './config/slugs';
import mockSnippets from './config/snippets';
import mockStats from './config/stats';
import mockTags from './config/tags';
import mockThemes from './config/themes';
import mockTiers from './config/tiers';
import mockUploads from './config/uploads';
import mockUsers from './config/users';
import mockWebhooks from './config/webhooks';
/* eslint-disable ghost/ember/no-test-import-export */
export default function () {
this.namespace = ghostPaths().apiRoot;
// this.timing = 400; // delay for each request, automatically set to 0 during testing
this.logging = false;
mockApiKeys(this);
mockAuthentication(this);
mockConfig(this);
mockEmailPreview(this);
mockEmails(this);
mockIntegrations(this);
mockInvites(this);
mockMembers(this);
mockLabels(this);
mockPages(this);
mockPosts(this);
mockRoles(this);
mockSearchIndex(this);
mockSettings(this);
mockSite(this);
mockSlugs(this);
mockTags(this);
mockThemes(this);
mockUploads(this);
mockUsers(this);
mockWebhooks(this);
mockTiers(this);
mockOffers(this);
mockSnippets(this);
mockNewsletters(this);
mockStats(this);
/* Notifications -------------------------------------------------------- */
this.get('/notifications/');
/* Integrations - Slack Test Notification ------------------------------- */
this.post('/slack/test', function () {
return {};
});
/* Delete all ----------------------------------------------------------- */
this.del('/db', function (db) {
db.posts.all().remove();
db.tags.all().remove();
}, 204);
/* limit=all blocker ---------------------------------------------------- */
const originalHandledRequest = this.pretender.handledRequest;
this.pretender.handledRequest = function (verb, path, request) {
originalHandledRequest.call(this, verb, path, request);
const url = new URL(request.url, window.location.origin);
const limit = url.searchParams.get('limit');
const ALLOWED_LIMIT_ALL = [
'/api/admin/members/upload/'
];
// limit=all is completely blocked, we shouldn't have any requests reach the server with this
if (limit === 'all' && !ALLOWED_LIMIT_ALL.some(allowed => path.includes(allowed))) {
throw new Error(`Blocked mirage request with limit=all: ${verb} ${path}.`);
}
// limit > 100 is also blocked, apart from some specific endpoints
if (limit && parseInt(limit, 10) > 100) {
const parsedLimit = parseInt(limit, 10);
const SEARCH_ENDPOINTS_REGEX = /\/api\/admin\/(posts|pages|tags|users)\//;
if (parsedLimit === 10000 && SEARCH_ENDPOINTS_REGEX.test(path)) {
return;
}
if (path.match(/\/emails\/.*\/batches\//)) {
return;
}
if (path.includes('/api/admin/posts/export/')) {
return;
}
throw new Error(`Blocked mirage request with limit > 100: ${verb} ${path}.`);
}
};
/* External sites ------------------------------------------------------- */
this.head('http://www.gravatar.com/avatar/:md5', function () {
return '';
}, 200);
this.get('http://www.gravatar.com/avatar/:md5', function () {
return '';
}, 200);
this.get('https://ghost.org/changelog.json', function () {
return {
posts: [
{
title: 'Custom image alt tags',
custom_excerpt: 'Alt tag support for images in the Ghost editor',
slug: 'image-alt-text-support',
published_at: '2019-08-05T07:46:16.000+00:00',
url: 'https://ghost.org/changelog/image-alt-text-support/',
featured: 'false'
}
],
changelogUrl: 'https://ghost.org/changelog/'
};
});
}
+160
View File
@@ -0,0 +1,160 @@
import {Response} from 'miragejs';
import {isArray} from '@ember/array';
export function paginatedResponse(modelName) {
return function (schema, request) {
let page = +request.queryParams.page || 1;
let limit = request.queryParams.limit;
let collection = schema[modelName].all();
if (limit !== 'all') {
limit = +request.queryParams.limit || 15;
}
return paginateModelCollection(modelName, collection, page, limit);
};
}
export function paginateModelCollection(modelName, collection, page, limit) {
let pages, next, prev, models;
page = parseInt(page, 10);
limit = parseInt(limit, 10);
if (limit === 'all') {
pages = 1;
} else {
limit = +limit;
let start = (page - 1) * limit;
let end = start + limit;
pages = Math.ceil(collection.models.length / limit);
models = collection.models.slice(start, end);
if (start > 0) {
prev = page - 1;
}
if (end < collection.models.length) {
next = page + 1;
}
}
collection.meta = {
pagination: {
page,
limit,
pages,
total: collection.models.length,
next: next || null,
prev: prev || null
}
};
if (models) {
collection.models = models;
}
return collection;
}
export function maintenanceResponse() {
return new Response(503, {}, {
errors: [{
type: 'Maintenance'
}]
});
}
export function versionMismatchResponse() {
return new Response(400, {}, {
errors: [{
type: 'VersionMismatchError'
}]
});
}
function normalizeBooleanParams(arr) {
if (!isArray(arr)) {
return arr;
}
return arr.map((i) => {
if (i === 'true') {
return true;
} else if (i === 'false') {
return false;
} else {
return i;
}
});
}
function normalizeStringParams(arr) {
if (!isArray(arr)) {
return arr;
}
return arr.map((i) => {
if (!i.replace) {
return i;
}
return i.replace(/^['"]|['"]$/g, '');
});
}
// TODO: use GQL to parse filter string?
export function extractFilterParam(param, filter = '') {
let filterRegex = new RegExp(`${param}:(.*?)(?:\\+|$)`);
let match;
let [, result] = filter.match(filterRegex) || [];
if (!result) {
return;
}
if (result.startsWith('[')) {
match = result.replace(/^\[|\]$/g, '').split(',');
} else if (result.startsWith('~')) {
match = result.replace(/^~/, '').replace(/\\'/g, `'`).replace(/^'|'$/g, '');
} else {
match = [result];
}
return normalizeBooleanParams(normalizeStringParams(match));
}
export function hasInvalidPermissions(allowedRoles) {
const {schema, request} = this;
// always allow dev requests through - the logged in user will be real so
// we can't check against it in the mocked db
if (!request.requestHeaders['X-Test-User']) {
return false;
}
const invalidPermsResponse = new Response(403, {}, {
errors: [{
type: 'NoPermissionError',
message: 'You do not have permission to perform this action'
}]
});
const user = schema.users.find(request.requestHeaders['X-Test-User']);
const adminRoles = user.roles.filter(role => allowedRoles.includes(role.name));
if (adminRoles.length === 0) {
return invalidPermsResponse;
}
}
export function withPermissionsCheck(allowedRoles, fn) {
return function () {
const boundPermsCheck = hasInvalidPermissions.bind(this);
const boundFn = fn.bind(this);
return boundPermsCheck(allowedRoles) || boundFn(...arguments);
};
}
+230
View File
@@ -0,0 +1,230 @@
{
"name": "ghost-admin",
"version": "6.33.0-rc.0",
"description": "Ember.js admin client for Ghost",
"author": "Ghost Foundation",
"homepage": "http://ghost.org",
"repository": {
"type": "git",
"url": "git://github.com/TryGhost/Admin.git"
},
"bugs": "https://github.com/TryGhost/Ghost/issues",
"contributors": "https://github.com/TryGhost/Admin/graphs/contributors",
"license": "MIT",
"private": true,
"directories": {
"test": "tests"
},
"scripts": {
"dev": "ember serve",
"build": "ember build --environment=production --silent",
"build:dev": "pnpm build --environment=development",
"test:unit": "true",
"test": "ember exam --split 2 --parallel",
"lint:js": "eslint . --cache",
"lint:hbs": "ember-template-lint .",
"lint": "pnpm lint:js && pnpm lint:hbs"
},
"engines": {
"node": "^22.13.1"
},
"devDependencies": {
"@babel/eslint-parser": "7.28.4",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-decorators": "7.28.0",
"@ember/jquery": "2.0.0",
"@ember/optional-features": "2.1.0",
"@ember/render-modifiers": "2.1.0",
"@ember/test-helpers": "2.9.6",
"@ember/test-waiters": "3.1.0",
"@embroider/macros": "1.16.13",
"@faker-js/faker": "7.6.0",
"@glimmer/component": "1.1.2",
"@html-next/vertical-collection": "3.0.0",
"@sentry/ember": "7.120.3",
"@sentry/integrations": "7.114.0",
"@sentry/replay": "7.116.0",
"@tryghost/admin-x-framework": "workspace:*",
"ghost": "workspace:*",
"@tryghost/color-utils": "0.2.16",
"@tryghost/ember-promise-modals": "2.0.1",
"@tryghost/helpers": "1.1.103",
"@tryghost/kg-clean-basic-html": "4.2.23",
"@tryghost/kg-converters": "1.1.21",
"@tryghost/koenig-lexical": "1.7.30",
"@tryghost/limit-service": "1.5.2",
"@tryghost/members-csv": "2.0.5",
"@tryghost/nql": "0.12.10",
"@tryghost/nql-lang": "0.6.4",
"@tryghost/string": "0.3.2",
"@tryghost/timezone-data": "0.4.18",
"animejs": "3.2.2",
"autoprefixer": "9.8.6",
"babel-plugin-transform-class-properties": "6.24.1",
"babel-plugin-transform-react-jsx": "6.24.1",
"broccoli-asset-rev": "3.0.0",
"broccoli-concat": "4.2.7",
"broccoli-funnel": "3.0.8",
"broccoli-merge-trees": "4.2.0",
"broccoli-terser-sourcemap": "4.1.1",
"chai": "4.5.0",
"chai-dom": "1.12.1",
"codemirror": "5.48.2",
"cssnano": "4.1.10",
"element-resize-detector": "1.2.4",
"ember-ajax": "5.1.2",
"ember-assign-helper": "0.5.0",
"ember-auto-import": "2.10.0",
"ember-classic-decorator": "3.0.1",
"ember-cli": "3.24.0",
"ember-cli-app-version": "5.0.0",
"ember-cli-babel": "8.2.0",
"ember-cli-chart": "3.7.2",
"ember-cli-code-coverage": "1.0.3",
"ember-cli-dependency-checker": "3.3.2",
"ember-cli-deprecation-workflow": "2.2.0",
"ember-cli-htmlbars": "6.3.0",
"ember-cli-inject-live-reload": "2.1.0",
"ember-cli-mirage": "2.4.0",
"ember-cli-node-assets": "0.2.2",
"ember-cli-postcss": "6.0.1",
"ember-cli-shims": "1.2.0",
"ember-cli-string-helpers": "6.1.0",
"ember-cli-terser": "4.0.1",
"ember-cli-test-loader": "3.1.0",
"ember-composable-helpers": "5.0.0",
"ember-concurrency": "2.3.7",
"ember-could-get-used-to-this": "1.0.1",
"ember-css-transitions": "4.4.1",
"ember-data": "3.24.0",
"ember-decorators": "6.1.1",
"ember-drag-drop": "0.4.8",
"ember-ella-sparse": "0.16.0",
"ember-exam": "6.0.1",
"ember-export-application-global": "2.0.1",
"ember-fetch": "8.1.2",
"ember-in-viewport": "4.1.0",
"ember-infinity": "2.3.0",
"ember-keyboard": "8.2.1",
"ember-load": "0.0.17",
"ember-load-initializers": "2.1.2",
"ember-mocha": "0.16.2",
"ember-modifier": "4.2.0",
"ember-moment": "10.0.1",
"ember-one-way-select": "4.0.1",
"ember-power-datepicker": "0.8.1",
"ember-power-select": "6.0.1",
"ember-resolver": "8.1.0",
"ember-simple-auth": "5.0.0",
"ember-sinon": "5.0.0",
"ember-source": "3.24.0",
"ember-svg-jar": "2.7.1",
"ember-template-lint": "5.13.0",
"ember-test-selectors": "6.0.0",
"ember-tooltips": "3.6.0",
"ember-truth-helpers": "3.1.1",
"eslint": "catalog:",
"eslint-plugin-babel": "5.3.1",
"flexsearch": "0.7.43",
"fs-extra": "11.3.4",
"glob": "8.1.0",
"google-caja-bower": "https://github.com/acburdine/google-caja-bower#ghost",
"keymaster": "https://github.com/madrobby/keymaster.git",
"liquid-fire": "0.34.0",
"liquid-wormhole": "3.0.1",
"loader.js": "4.7.0",
"microdiff": "1.5.0",
"miragejs": "0.1.48",
"moment-timezone": "0.5.45",
"normalize.css": "3.0.3",
"papaparse": "5.5.3",
"postcss-color-mod-function": "3.0.3",
"postcss-custom-media": "7.0.8",
"postcss-custom-properties": "10.0.0",
"postcss-import": "12.0.1",
"pretender": "3.4.7",
"process": "0.11.10",
"react": "18.3.1",
"react-dom": "18.3.1",
"reframe.js": "4.0.2",
"semver": "7.7.4",
"sentry-testkit": "5.0.10",
"sinon-chai": "4.0.1",
"testem": "3.19.1",
"tracked-built-ins": "3.4.0",
"util": "0.12.5",
"validator": "13.12.0",
"walk-sync": "3.0.0"
},
"ember-addon": {
"paths": [
"lib/asset-delivery",
"lib/ember-power-calendar-moment",
"lib/ember-power-calendar-utils"
]
},
"ember": {
"edition": "octane"
},
"lint-staged": {
"*.hbs": "ember-template-lint",
"*.js": "eslint"
},
"dependencies": {
"i18n-iso-countries": "7.14.0",
"lru-cache": "6.0.0",
"path-browserify": "1.0.1",
"webpack": "5.105.4"
},
"nx": {
"implicitDependencies": [
"!ghost"
],
"targets": {
"build:dev": {
"dependsOn": [
"build:dev",
{
"projects": [
"@tryghost/admin-x-framework",
"@tryghost/admin-x-settings",
"@tryghost/activitypub",
"@tryghost/posts",
"@tryghost/stats"
],
"target": "build"
}
]
},
"build": {
"outputs": [
"{projectRoot}/dist",
"{workspaceRoot}/ghost/core/core/built/admin"
],
"dependsOn": [
"build",
{
"projects": [
"@tryghost/admin-x-framework",
"@tryghost/admin-x-settings",
"@tryghost/activitypub",
"@tryghost/posts",
"@tryghost/stats"
],
"target": "build"
}
]
},
"test": {
"dependsOn": [
{
"projects": [
"@tryghost/admin-x-framework"
],
"target": "build"
}
]
}
}
}
}
+36
View File
@@ -0,0 +1,36 @@
/* eslint-env node */
/* eslint-disable camelcase */
let launch_in_ci = [process.env.BROWSER || 'Chrome'];
module.exports = {
framework: 'mocha',
browser_start_timeout: 120,
browser_disconnect_timeout: 60,
test_page: 'tests/index.html?hidepassed',
disable_watching: true,
parallel: process.env.EMBER_EXAM_SPLIT_COUNT || 1,
launch_in_ci,
launch_in_dev: [
'Chrome',
'Firefox'
],
browser_args: {
Chrome: {
ci: [
// --no-sandbox is needed when running Chrome inside a container
process.env.CI ? '--no-sandbox' : null,
'--headless',
'--disable-dev-shm-usage',
'--disable-software-rasterizer',
'--mute-audio',
'--remote-debugging-port=0',
'--window-size=1440,900'
].filter(Boolean)
},
Firefox: {
ci: ['-headless']
}
},
tap_failed_tests_only: true
};
+51
View File
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<base href="{{rootURL}}">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Ghost Tests</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{content-for "head"}}
{{content-for "test-head"}}
<link rel="stylesheet" href="{{rootURL}}assets/vendor.css">
<link rel="stylesheet" href="{{rootURL}}assets/ghost.css">
<link rel="stylesheet" href="{{rootURL}}assets/test-support.css">
{{content-for "head-footer"}}
{{content-for "test-head-footer"}}
<style>
/* fix to ensure we use full viewport height when testing */
#ember-testing {
height: 100%;
}
#ember-testing .gh-app {
position: relative;
}
/* fix firefox not supporting `zoom: 50%` */
_::-moz-range-track, body:last-child #ember-testing {
-moz-transform-origin: 0 0;
-moz-transform: scale(0.5);
width: 200%;
height: 200%;
}
</style>
</head>
<body>
{{content-for "body"}}
{{content-for "test-body"}}
<script src="/testem.js" integrity=""></script>
<script src="{{rootURL}}assets/vendor.js"></script>
<script src="{{rootURL}}assets/test-support.js"></script>
<script src="{{rootURL}}assets/ghost.js"></script>
<script src="{{rootURL}}assets/tests.js"></script>
{{content-for "body-footer"}}
{{content-for "test-body-footer"}}
</body>
</html>
+22
View File
@@ -0,0 +1,22 @@
import Application from 'ghost-admin/app';
import config from 'ghost-admin/config/environment';
import registerWaiter from 'ember-raf-scheduler/test-support/register-waiter';
import start from 'ember-exam/test-support/start';
import {setApplication} from '@ember/test-helpers';
import chai from 'chai';
import chaiDom from 'chai-dom';
import sinonChai from 'sinon-chai';
chai.use(chaiDom);
chai.use(sinonChai);
setApplication(Application.create(config.APP));
registerWaiter();
mocha.setup({
timeout: 15000,
slow: 500
});
start();