mirror of
https://github.com/flarum/framework.git
synced 2025-04-20 19:08:51 +08:00
Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5bd7e5dfe3 | ||
![]() |
956ac20c4c | ||
![]() |
b7e41ce82f | ||
![]() |
c737d7b8f5 | ||
![]() |
9295e7b96f | ||
![]() |
9377256409 | ||
![]() |
9c91c89326 | ||
![]() |
8169550f1c | ||
![]() |
d5a1653d24 | ||
![]() |
db605bdbaa | ||
![]() |
1665d47adc | ||
![]() |
1c71ee0968 | ||
![]() |
6846f4232c | ||
![]() |
88f182cc93 | ||
![]() |
ed72aa0128 | ||
![]() |
12d21cdbfc | ||
![]() |
359681f3c6 | ||
![]() |
1a4c4a0275 | ||
![]() |
ea2fd2cade | ||
![]() |
772852b3b3 | ||
![]() |
71717f9ebb | ||
![]() |
449ba48ba3 | ||
![]() |
4d75da36b8 | ||
![]() |
d4fe5f5a7a | ||
![]() |
256c1846b7 | ||
![]() |
1fee96aebe | ||
![]() |
b49b3104e4 | ||
![]() |
7d8cfdfaec | ||
![]() |
845c38d6cb | ||
![]() |
4912a2e059 | ||
![]() |
ca6d826f79 | ||
![]() |
dce2549ff7 | ||
![]() |
306d0bc124 | ||
![]() |
e3d07cb8cc | ||
![]() |
9bc8c7de99 | ||
![]() |
5076da9b38 | ||
![]() |
2c4d64cd20 | ||
![]() |
c9bd7dab1e | ||
![]() |
df14216e1b | ||
![]() |
de36551b45 | ||
![]() |
54fbcdedd5 | ||
![]() |
e9c8890686 | ||
![]() |
6dd0c0e915 | ||
![]() |
444df80caf | ||
![]() |
3ebd218588 | ||
![]() |
9038ff64f8 | ||
![]() |
f8c30c96dc | ||
![]() |
3743bc0886 | ||
![]() |
5855134b79 | ||
![]() |
7f657dac04 | ||
![]() |
84414c6699 | ||
![]() |
bf0d895106 | ||
![]() |
c942f3100d | ||
![]() |
d5882d9357 | ||
![]() |
7540ede897 | ||
![]() |
77d1a3d04b | ||
![]() |
c79d2892de | ||
![]() |
46cdaf5d1a | ||
![]() |
4d59ec4600 | ||
![]() |
e0adf90453 | ||
![]() |
07a1781181 | ||
![]() |
80e70f4980 | ||
![]() |
e43530e40a | ||
![]() |
24e88d12b8 | ||
![]() |
b3366e4c93 | ||
![]() |
8415d2233e | ||
![]() |
a52959ccf2 | ||
![]() |
b2044ff312 | ||
![]() |
50dd73b07c | ||
![]() |
4f4977b7a5 | ||
![]() |
24b7dcb102 | ||
![]() |
25beb7919d | ||
![]() |
207032f6ff | ||
![]() |
8205ae5bf5 | ||
![]() |
ac6f4d4d0c | ||
![]() |
56b2b3b2bc | ||
![]() |
7fb0e08c0a | ||
![]() |
2a693db1b6 | ||
![]() |
7d70328471 | ||
![]() |
45a8b572e3 | ||
![]() |
5d14f96c32 | ||
![]() |
2299541e4d | ||
![]() |
a131132654 | ||
![]() |
7743a2bcd4 | ||
![]() |
62080303bf | ||
![]() |
480093d023 | ||
![]() |
1c421fc266 | ||
![]() |
f07336e204 | ||
![]() |
95061a2ed4 | ||
![]() |
c3fadbf6b1 | ||
![]() |
82e08e3fa5 | ||
![]() |
2c4a2b8d9e | ||
![]() |
00866fbba9 | ||
![]() |
0d1d4d46d1 | ||
![]() |
b1383a955f | ||
![]() |
daeab48ae8 | ||
![]() |
e03ca4406d | ||
![]() |
7894c6a69b |
20
.github/workflows/REUSABLE_backend.yml
vendored
20
.github/workflows/REUSABLE_backend.yml
vendored
@ -25,7 +25,7 @@ on:
|
||||
description: Versions of PHP to test with. Should be array of strings encoded as JSON array
|
||||
type: string
|
||||
required: false
|
||||
default: '["7.3", "7.4", "8.0", "8.1", "8.2"]'
|
||||
default: '["7.3", "7.4", "8.0", "8.1", "8.2", "8.3"]'
|
||||
|
||||
php_extensions:
|
||||
description: PHP extensions to install.
|
||||
@ -45,13 +45,25 @@ on:
|
||||
required: false
|
||||
default: error_reporting=E_ALL
|
||||
|
||||
runner_type:
|
||||
description: The type of runner to use for the jobs. This should be one of the types supported by the `runs-on` keyword.
|
||||
type: string
|
||||
required: false
|
||||
default: 'ubuntu-latest'
|
||||
|
||||
secrets:
|
||||
composer_auth:
|
||||
description: The Composer auth tokens to use for private packages.
|
||||
required: false
|
||||
|
||||
env:
|
||||
COMPOSER_ROOT_VERSION: dev-main
|
||||
FLARUM_TEST_TMP_DIR_LOCAL: tests/integration/tmp
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ inputs.runner_type }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@ -91,6 +103,8 @@ jobs:
|
||||
# Include testing PHP 8.2 with deprecation warnings disabled.
|
||||
- php: 8.2
|
||||
php_ini_values: error_reporting=E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED
|
||||
- php: 8.3
|
||||
php_ini_values: error_reporting=E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED
|
||||
|
||||
# To reduce number of actions, we exclude some PHP versions from running with some DB versions.
|
||||
exclude:
|
||||
@ -158,7 +172,7 @@ jobs:
|
||||
COMPOSER_PROCESS_TIMEOUT: 600
|
||||
|
||||
phpstan:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ inputs.runner_type }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
|
12
.github/workflows/REUSABLE_frontend.yml
vendored
12
.github/workflows/REUSABLE_frontend.yml
vendored
@ -86,20 +86,30 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
|
||||
runner_type:
|
||||
description: The type of runner to use for the jobs. This should be one of the types supported by the `runs-on` keyword.
|
||||
type: string
|
||||
required: false
|
||||
default: 'ubuntu-latest'
|
||||
|
||||
secrets:
|
||||
bundlewatch_github_token:
|
||||
description: The GitHub token to use for Bundlewatch.
|
||||
required: false
|
||||
composer_auth:
|
||||
description: The Composer auth tokens to use for private packages.
|
||||
required: false
|
||||
|
||||
env:
|
||||
COMPOSER_ROOT_VERSION: dev-main
|
||||
ci_script: ${{ inputs.js_package_manager == 'yarn' && 'yarn install --immutable' || 'npm ci' }}
|
||||
cache_dependency_path: ${{ inputs.cache_dependency_path || format(inputs.js_package_manager == 'yarn' && '{0}/yarn.lock' || '{0}/package-lock.json', inputs.frontend_directory) }}
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Checks & Build
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ inputs.runner_type }}
|
||||
|
||||
if: >-
|
||||
((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) || github.event_name != 'pull_request')
|
||||
|
@ -1,4 +1,4 @@
|
||||
name: Package Manager PHP
|
||||
name: Extension Manager PHP
|
||||
|
||||
on: [workflow_dispatch, push, pull_request]
|
||||
|
2
.github/workflows/frontend.yml
vendored
2
.github/workflows/frontend.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
backend_directory: ./
|
||||
js_package_manager: yarn
|
||||
cache_dependency_path: ./yarn.lock
|
||||
main_git_branch: main
|
||||
main_git_branch: 1.x
|
||||
enable_tests: true
|
||||
# @TODO: fix bundlewatch
|
||||
enable_bundlewatch: false
|
||||
|
50
CHANGELOG.md
50
CHANGELOG.md
@ -1,5 +1,55 @@
|
||||
# Changelog
|
||||
|
||||
## [v1.8.7](https://github.com/flarum/framework/compare/v1.8.6...v1.8.7)
|
||||
### Fixed
|
||||
* BasicsPage not viewable if only one language pack enabled, and/or `flarum/nicknames` not enabled (https://github.com/flarum/framework/pull/4062)
|
||||
|
||||
## [v1.8.6](https://github.com/flarum/framework/compare/v1.8.5...v1.8.6)
|
||||
### Fixed
|
||||
* reset admin page save button in catch handler (https://github.com/flarum/framework/pull/3963)
|
||||
* suspended users can remove avatar (https://github.com/flarum/framework/pull/3998)
|
||||
* return null if content left empty in formatter (https://github.com/flarum/framework/pull/4059)
|
||||
### Changed
|
||||
* allow DiscussionsSearchSource to be extended (https://github.com/flarum/framework/pull/4025)
|
||||
* allow modifying the discussion title on PostsUserPage (https://github.com/flarum/framework/pull/4031)
|
||||
* make it easier to modify AppearancePage, BasicsPage, MailPage (https://github.com/flarum/framework/pull/4037)
|
||||
* point fontawesome links at v5 free (https://github.com/flarum/framework/pull/4038)
|
||||
* make WelcomeHero extensible (https://github.com/flarum/framework/pull/4039)
|
||||
* make PostMeta extensible (https://github.com/flarum/framework/pull/4040)
|
||||
* extensible TagHero (https://github.com/flarum/framework/pull/4041)
|
||||
* allow extending PostPreview content (https://github.com/flarum/framework/pull/4043)
|
||||
* allow classes that extends AbstractJob to be placed on a specified queue (https://github.com/flarum/framework/pull/4026)
|
||||
* use common component for ip address display (https://github.com/flarum/framework/pull/4042)
|
||||
* make it easier to add content after the first post (https://github.com/flarum/framework/pull/4050)
|
||||
* improve extensibility of IndexPage (https://github.com/flarum/framework/pull/4045)
|
||||
* improve extensibility of DiscussionPage (https://github.com/flarum/framework/pull/4046)
|
||||
* backport & improve extensibility of DiscussionListItem (https://github.com/flarum/framework/pull/4048)
|
||||
* improve & use extensibility of CommentPost & Post (https://github.com/flarum/framework/pull/4047)
|
||||
* allow labels of PostStreamScrubber to be customized (https://github.com/flarum/framework/pull/4049)
|
||||
* allow to customize time formats through translations (https://github.com/flarum/framework/pull/4053)
|
||||
### Added
|
||||
* Export all missing modules in compat (https://github.com/flarum/framework/pull/4044)
|
||||
* Add (some) missing shims (https://github.com/flarum/framework/pull/4027)
|
||||
* provide an 'actions' dropdown for extensions to add their additional buttons to the admin UserListPage (https://github.com/flarum/framework/pull/4054)
|
||||
|
||||
## [v1.8.5](https://github.com/flarum/framework/compare/v1.8.4...v1.8.5)
|
||||
### Fixed
|
||||
* Logout controller allows open redirects [#3948]
|
||||
|
||||
## [v1.8.4](https://github.com/flarum/framework/compare/v1.8.3...v1.8.4)
|
||||
### Fixed
|
||||
* `s9e/textformatter` 2.15 has breaking changes [#3946]
|
||||
|
||||
## [v1.8.3](https://github.com/flarum/framework/compare/v1.8.2...v1.8.3)
|
||||
### Fixed
|
||||
* Console extender does not accept ::class [#3900]
|
||||
* Conditional extender instantiation [#3898]
|
||||
|
||||
## [v1.8.2](https://github.com/flarum/framework/compare/v1.8.1...v1.8.2)
|
||||
### Fixed
|
||||
* suspended users can abuse avatar upload [#3890]
|
||||
* missing compat exports [#3888]
|
||||
|
||||
## [v1.8.1](https://github.com/flarum/framework/compare/v1.8.0...v1.8.1)
|
||||
### Fixed
|
||||
* recover temporary solution for html entities in browser title (e72541e35de4f71f9d870bbd9bb46ddf586bdf1d)
|
||||
|
@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<a href="https://flarum.org/"><img src="https://flarum.org/assets/img/logo.png"></a>
|
||||
<a href="https://flarum.org/"><img src="https://flarum.org/images/flarum.svg"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@ -7,7 +7,6 @@
|
||||
<a href="https://packagist.org/packages/flarum/core"><img src="https://img.shields.io/packagist/dt/flarum/core" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/flarum/core"><img src="https://img.shields.io/github/v/release/flarum/core?sort=semver" alt="Latest Version"></a>
|
||||
<a href="https://packagist.org/packages/flarum/core"><img src="https://img.shields.io/packagist/l/flarum/core" alt="License"></a>
|
||||
<a href="https://huntr.dev/bounties/disclose/?target=https://github.com/flarum/core"><img src="https://cdn.huntr.dev/huntr_security_badge_mono.svg" alt="huntr"></a>
|
||||
<a href="https://github.styleci.io/repos/28257573"><img src="https://github.styleci.io/repos/28257573/shield?style=flat" alt="StyleCI"></a>
|
||||
</p>
|
||||
|
||||
@ -38,3 +37,4 @@ If you discover a security vulnerability within Flarum, please send an e-mail to
|
||||
## License
|
||||
|
||||
Flarum is open-source software licensed under the [MIT License](https://github.com/flarum/flarum/blob/master/LICENSE).
|
||||
|
||||
|
@ -46,7 +46,7 @@
|
||||
"Flarum\\Lock\\": "extensions/lock/src",
|
||||
"Flarum\\Mentions\\": "extensions/mentions/src",
|
||||
"Flarum\\Nicknames\\": "extensions/nicknames/src",
|
||||
"Flarum\\PackageManager\\": "extensions/package-manager/src",
|
||||
"Flarum\\ExtensionManager\\": "extensions/package-manager/src",
|
||||
"Flarum\\Pusher\\": "extensions/pusher/src",
|
||||
"Flarum\\Statistics\\": "extensions/statistics/src",
|
||||
"Flarum\\Sticky\\": "extensions/sticky/src",
|
||||
@ -74,7 +74,7 @@
|
||||
"flarum/markdown": "self.version",
|
||||
"flarum/mentions": "self.version",
|
||||
"flarum/nicknames": "self.version",
|
||||
"flarum/package-manager": "self.version",
|
||||
"flarum/extension-manager": "self.version",
|
||||
"flarum/pusher": "self.version",
|
||||
"flarum/statistics": "self.version",
|
||||
"flarum/sticky": "self.version",
|
||||
@ -127,7 +127,7 @@
|
||||
"psr/http-server-handler": "^1.0",
|
||||
"psr/http-server-middleware": "^1.0",
|
||||
"pusher/pusher-php-server": "^2.2",
|
||||
"s9e/text-formatter": "^2.3.6",
|
||||
"s9e/text-formatter": ">=2.3.6 <2.15",
|
||||
"staudenmeir/eloquent-eager-limit": "^1.0",
|
||||
"sycho/json-api": "^0.5.0",
|
||||
"sycho/sourcemap": "^2.0.0",
|
||||
|
@ -15,6 +15,7 @@
|
||||
"declarationDir": "./dist-typings",
|
||||
"paths": {
|
||||
"flarum/*": ["../../../framework/core/js/dist-typings/*"],
|
||||
"@flarum/core/*": ["../../../framework/core/js/dist-typings/*"],
|
||||
"flarum/flags/*": ["../../flags/js/dist-typings/*"]
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ class Akismet
|
||||
$client = new Client();
|
||||
|
||||
return $client->request('POST', "$this->apiUrl/$type", [
|
||||
'headers' => [
|
||||
'headers' => [
|
||||
'User-Agent' => "Flarum/$this->flarumVersion | Akismet/$this->extensionVersion",
|
||||
],
|
||||
'form_params' => $this->params,
|
||||
|
12
extensions/approval/js/src/@types/shims.d.ts
vendored
Normal file
12
extensions/approval/js/src/@types/shims.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
declare module 'flarum/common/models/Discussion' {
|
||||
export default interface Discussion {
|
||||
isApproved(): boolean;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'flarum/common/models/Post' {
|
||||
export default interface Post {
|
||||
isApproved(): boolean;
|
||||
canApprove(): boolean;
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ namespace Flarum\Approval\Listener;
|
||||
|
||||
use Flarum\Approval\Event\PostWasApproved;
|
||||
use Flarum\Post\Event\Saving;
|
||||
use Flarum\User\Exception\PermissionDeniedException;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
class ApproveContent
|
||||
@ -23,23 +24,42 @@ class ApproveContent
|
||||
$events->listen(Saving::class, [$this, 'approvePost']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PermissionDeniedException
|
||||
*/
|
||||
public function approvePost(Saving $event)
|
||||
{
|
||||
$attributes = $event->data['attributes'];
|
||||
$post = $event->post;
|
||||
|
||||
// Nothing to do if it is already approved.
|
||||
if ($post->is_approved) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* We approve a post in one of two cases:
|
||||
* - The post was unapproved and the allowed action is approving it. We trigger an event.
|
||||
* - The post was unapproved and the allowed actor is hiding or un-hiding it.
|
||||
* We approve it silently if the action is unhiding.
|
||||
*/
|
||||
$approvingSilently = false;
|
||||
|
||||
if (isset($attributes['isApproved'])) {
|
||||
$event->actor->assertCan('approve', $post);
|
||||
|
||||
$isApproved = (bool) $attributes['isApproved'];
|
||||
} elseif (! empty($attributes['isHidden']) && $event->actor->can('approve', $post)) {
|
||||
} elseif (isset($attributes['isHidden']) && $event->actor->can('approve', $post)) {
|
||||
$isApproved = true;
|
||||
$approvingSilently = $attributes['isHidden'];
|
||||
}
|
||||
|
||||
if (! empty($isApproved)) {
|
||||
$post->is_approved = true;
|
||||
|
||||
$post->raise(new PostWasApproved($post, $event->actor));
|
||||
if (! $approvingSilently) {
|
||||
$post->raise(new PostWasApproved($post, $event->actor));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
extensions/emoji/js/dist/forum.js
generated
vendored
2
extensions/emoji/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/emoji/js/dist/forum.js.map
generated
vendored
2
extensions/emoji/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
7
extensions/emoji/js/src/forum/compat.ts
Normal file
7
extensions/emoji/js/src/forum/compat.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import AutocompleteDropdown from './fragments/AutocompleteDropdown';
|
||||
import getEmojiIconCode from './helpers/getEmojiIconCode';
|
||||
|
||||
export default {
|
||||
'emoji/fragments/AutocompleteDropdown': AutocompleteDropdown,
|
||||
'emoji/helpers/getEmojiIconCode': getEmojiIconCode,
|
||||
};
|
@ -11,3 +11,9 @@ app.initializers.add('flarum-emoji', () => {
|
||||
// render emoji as image in Posts content and title.
|
||||
renderEmoji();
|
||||
});
|
||||
|
||||
// Expose compat API
|
||||
import emojiCompat from './compat';
|
||||
import { compat } from '@flarum/core/forum';
|
||||
|
||||
Object.assign(compat, emojiCompat);
|
||||
|
@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.8"
|
||||
"flarum/core": "^1.8.6"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
2
extensions/flags/js/dist-typings/forum/compat.d.ts
generated
vendored
2
extensions/flags/js/dist-typings/forum/compat.d.ts
generated
vendored
@ -7,6 +7,7 @@ declare const _default: {
|
||||
'flags/components/FlagPostModal': typeof FlagPostModal;
|
||||
'flags/components/FlagsPage': typeof FlagsPage;
|
||||
'flags/components/FlagsDropdown': typeof FlagsDropdown;
|
||||
'flags/states/FlagListState': typeof FlagListState;
|
||||
};
|
||||
export default _default;
|
||||
import addFlagsToPosts from "./addFlagsToPosts";
|
||||
@ -17,3 +18,4 @@ import FlagList from "./components/FlagList";
|
||||
import FlagPostModal from "./components/FlagPostModal";
|
||||
import FlagsPage from "./components/FlagsPage";
|
||||
import FlagsDropdown from "./components/FlagsDropdown";
|
||||
import FlagListState from "./states/FlagListState";
|
||||
|
2
extensions/flags/js/dist/forum.js
generated
vendored
2
extensions/flags/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/flags/js/dist/forum.js.map
generated
vendored
2
extensions/flags/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@ -75,7 +75,7 @@ export default function () {
|
||||
return items;
|
||||
};
|
||||
|
||||
extend(Post.prototype, 'content', function (vdom) {
|
||||
extend(Post.prototype, 'viewItems', function (items) {
|
||||
const post = this.attrs.post;
|
||||
const flags = post.flags();
|
||||
|
||||
@ -83,7 +83,8 @@ export default function () {
|
||||
|
||||
if (post.isHidden()) this.revealContent = true;
|
||||
|
||||
vdom.unshift(
|
||||
items.add(
|
||||
'flagged',
|
||||
<div className="Post-flagged">
|
||||
<div className="Post-flagged-flags">
|
||||
{flags.map((flag) => (
|
||||
@ -91,7 +92,8 @@ export default function () {
|
||||
))}
|
||||
</div>
|
||||
<div className="Post-flagged-actions">{this.flagActionItems().toArray()}</div>
|
||||
</div>
|
||||
</div>,
|
||||
110
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -6,6 +6,7 @@ import FlagList from './components/FlagList';
|
||||
import FlagPostModal from './components/FlagPostModal';
|
||||
import FlagsPage from './components/FlagsPage';
|
||||
import FlagsDropdown from './components/FlagsDropdown';
|
||||
import FlagListState from './states/FlagListState';
|
||||
|
||||
export default {
|
||||
'flags/addFlagsToPosts': addFlagsToPosts,
|
||||
@ -16,4 +17,5 @@ export default {
|
||||
'flags/components/FlagPostModal': FlagPostModal,
|
||||
'flags/components/FlagsPage': FlagsPage,
|
||||
'flags/components/FlagsDropdown': FlagsDropdown,
|
||||
'flags/states/FlagListState': FlagListState,
|
||||
};
|
||||
|
@ -40,6 +40,7 @@ class AddCanFlagAttribute
|
||||
// If $actor is the post author, check to see if the setting is enabled
|
||||
return (bool) $this->settings->get('flarum-flags.can_flag_own');
|
||||
}
|
||||
|
||||
// $actor is not the post author
|
||||
return true;
|
||||
}
|
||||
|
@ -31,10 +31,10 @@ class FlagSerializer extends AbstractSerializer
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => $flag->type,
|
||||
'reason' => $flag->reason,
|
||||
'type' => $flag->type,
|
||||
'reason' => $flag->reason,
|
||||
'reasonDetail' => $flag->reason_detail,
|
||||
'createdAt' => $this->formatDate($flag->created_at),
|
||||
'createdAt' => $this->formatDate($flag->created_at),
|
||||
];
|
||||
}
|
||||
|
||||
|
2
extensions/likes/js/dist/forum.js
generated
vendored
2
extensions/likes/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/likes/js/dist/forum.js.map
generated
vendored
2
extensions/likes/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/likes/js/src/@types/shims.d.ts
vendored
2
extensions/likes/js/src/@types/shims.d.ts
vendored
@ -1,9 +1,9 @@
|
||||
import Post from 'flarum/common/models/Post';
|
||||
import User from 'flarum/common/models/User';
|
||||
|
||||
declare module 'flarum/common/models/Post' {
|
||||
export default interface Post {
|
||||
likes(): User[];
|
||||
likesCount(): number;
|
||||
canLike(): boolean;
|
||||
}
|
||||
}
|
||||
|
11
extensions/likes/js/src/forum/compat.ts
Normal file
11
extensions/likes/js/src/forum/compat.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import LikesUserPage from './components/LikesUserPage';
|
||||
import PostLikedNotification from './components/PostLikedNotification';
|
||||
import PostLikesModal from './components/PostLikesModal';
|
||||
import PostLikesModalState from './states/PostLikesModalState';
|
||||
|
||||
export default {
|
||||
'likes/components/LikesUserPage': LikesUserPage,
|
||||
'likes/components/PostLikedNotification': PostLikedNotification,
|
||||
'likes/components/PostLikesModal': PostLikesModal,
|
||||
'likes/states/PostLikesModalState': PostLikesModalState,
|
||||
};
|
@ -24,3 +24,9 @@ app.initializers.add('flarum-likes', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Expose compat API
|
||||
import likesCompat from './compat';
|
||||
import { compat } from '@flarum/core/forum';
|
||||
|
||||
Object.assign(compat, likesCompat);
|
||||
|
2
extensions/lock/js/dist/forum.js
generated
vendored
2
extensions/lock/js/dist/forum.js
generated
vendored
@ -1,2 +1,2 @@
|
||||
(()=>{var o={n:t=>{var n=t&&t.__esModule?()=>t.default:()=>t;return o.d(n,{a:n}),n},d:(t,n)=>{for(var e in n)o.o(n,e)&&!o.o(t,e)&&Object.defineProperty(t,e,{enumerable:!0,get:n[e]})},o:(o,t)=>Object.prototype.hasOwnProperty.call(o,t),r:o=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(o,"__esModule",{value:!0})}},t={};(()=>{"use strict";o.r(t),o.d(t,{extend:()=>j});const n=flarum.core.compat["common/extend"],e=flarum.core.compat["forum/app"];var c=o.n(e);const r=flarum.core.compat["forum/components/NotificationGrid"];var s=o.n(r);function a(o,t){return a=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(o,t){return o.__proto__=t,o},a(o,t)}function i(o,t){o.prototype=Object.create(t.prototype),o.prototype.constructor=o,a(o,t)}const u=flarum.core.compat["forum/components/Notification"];var f=function(o){function t(){return o.apply(this,arguments)||this}i(t,o);var n=t.prototype;return n.icon=function(){return"fas fa-lock"},n.href=function(){var o=this.attrs.notification;return c().route.discussion(o.subject(),o.content().postNumber)},n.content=function(){return c().translator.trans("flarum-lock.forum.notifications.discussion_locked_text",{user:this.attrs.notification.fromUser()})},t}(o.n(u)());const l=flarum.core.compat["common/models/Discussion"];var d=o.n(l);const p=flarum.core.compat["common/components/Badge"];var k=o.n(p);const y=flarum.core.compat["forum/utils/DiscussionControls"];var b=o.n(y);const _=flarum.core.compat["forum/components/DiscussionPage"];var v=o.n(_);const h=flarum.core.compat["common/components/Button"];var g=o.n(h);const L=flarum.core.compat["common/extenders"];var x=o.n(L);const O=flarum.core.compat["forum/components/EventPost"];var P=function(o){function t(){return o.apply(this,arguments)||this}i(t,o);var n=t.prototype;return n.icon=function(){return this.attrs.post.content().locked?"fas fa-lock":"fas fa-unlock"},n.descriptionKey=function(){return this.attrs.post.content().locked?"flarum-lock.forum.post_stream.discussion_locked_text":"flarum-lock.forum.post_stream.discussion_unlocked_text"},t}(o.n(O)());const j=[(new(x().PostTypes)).add("discussionLocked",P),new(x().Model)(d()).attribute("isLocked").attribute("canLock")];c().initializers.add("flarum-lock",(function(){c().notificationComponents.discussionLocked=f,(0,n.extend)(d().prototype,"badges",(function(o){this.isLocked()&&o.add("locked",m(k(),{type:"locked",label:c().translator.trans("flarum-lock.forum.badge.locked_tooltip"),icon:"fas fa-lock"}))})),(0,n.extend)(b(),"moderationControls",(function(o,t){t.canLock()&&o.add("lock",m(g(),{icon:"fas fa-lock",onclick:this.lockAction.bind(t)},c().translator.trans("flarum-lock.forum.discussion_controls."+(t.isLocked()?"unlock":"lock")+"_button")))})),b().lockAction=function(){this.save({isLocked:!this.isLocked()}).then((function(){c().current.matches(v())&&c().current.get("stream").update(),m.redraw()}))},(0,n.extend)(s().prototype,"notificationTypes",(function(o){o.add("discussionLocked",{name:"discussionLocked",icon:"fas fa-lock",label:c().translator.trans("flarum-lock.forum.settings.notify_discussion_locked_label")})}))}))})(),module.exports=t})();
|
||||
(()=>{var o={n:t=>{var n=t&&t.__esModule?()=>t.default:()=>t;return o.d(n,{a:n}),n},d:(t,n)=>{for(var c in n)o.o(n,c)&&!o.o(t,c)&&Object.defineProperty(t,c,{enumerable:!0,get:n[c]})},o:(o,t)=>Object.prototype.hasOwnProperty.call(o,t),r:o=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(o,"__esModule",{value:!0})}},t={};(()=>{"use strict";o.r(t),o.d(t,{extend:()=>j});const n=flarum.core.compat["common/extend"],c=flarum.core.compat["forum/app"];var e=o.n(c);const r=flarum.core.compat["forum/components/NotificationGrid"];var s=o.n(r);function a(o,t){return a=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(o,t){return o.__proto__=t,o},a(o,t)}function i(o,t){o.prototype=Object.create(t.prototype),o.prototype.constructor=o,a(o,t)}const u=flarum.core.compat["forum/components/Notification"];var l=function(o){function t(){return o.apply(this,arguments)||this}i(t,o);var n=t.prototype;return n.icon=function(){return"fas fa-lock"},n.href=function(){var o=this.attrs.notification;return e().route.discussion(o.subject(),o.content().postNumber)},n.content=function(){return e().translator.trans("flarum-lock.forum.notifications.discussion_locked_text",{user:this.attrs.notification.fromUser()})},t}(o.n(u)());const f=flarum.core.compat["common/models/Discussion"];var d=o.n(f);const p=flarum.core.compat["common/components/Badge"];var k=o.n(p);const y=flarum.core.compat["forum/utils/DiscussionControls"];var b=o.n(y);const _=flarum.core.compat["forum/components/DiscussionPage"];var v=o.n(_);const h=flarum.core.compat["common/components/Button"];var L=o.n(h);const g=flarum.core.compat["common/extenders"];var O=o.n(g);const x=flarum.core.compat["forum/components/EventPost"];var P=function(o){function t(){return o.apply(this,arguments)||this}i(t,o);var n=t.prototype;return n.icon=function(){return this.attrs.post.content().locked?"fas fa-lock":"fas fa-unlock"},n.descriptionKey=function(){return this.attrs.post.content().locked?"flarum-lock.forum.post_stream.discussion_locked_text":"flarum-lock.forum.post_stream.discussion_unlocked_text"},t}(o.n(x)());const j=[(new(O().PostTypes)).add("discussionLocked",P),new(O().Model)(d()).attribute("isLocked").attribute("canLock")],D={"lock/components/DiscussionLockedNotification":l,"lock/components/DiscussionLockedPost":P},S=flarum.core;e().initializers.add("flarum-lock",(function(){e().notificationComponents.discussionLocked=l,(0,n.extend)(d().prototype,"badges",(function(o){this.isLocked()&&o.add("locked",m(k(),{type:"locked",label:e().translator.trans("flarum-lock.forum.badge.locked_tooltip"),icon:"fas fa-lock"}))})),(0,n.extend)(b(),"moderationControls",(function(o,t){t.canLock()&&o.add("lock",m(L(),{icon:"fas fa-lock",onclick:this.lockAction.bind(t)},e().translator.trans("flarum-lock.forum.discussion_controls."+(t.isLocked()?"unlock":"lock")+"_button")))})),b().lockAction=function(){this.save({isLocked:!this.isLocked()}).then((function(){e().current.matches(v())&&e().current.get("stream").update(),m.redraw()}))},(0,n.extend)(s().prototype,"notificationTypes",(function(o){o.add("discussionLocked",{name:"discussionLocked",icon:"fas fa-lock",label:e().translator.trans("flarum-lock.forum.settings.notify_discussion_locked_label")})}))})),Object.assign(S.compat,D)})(),module.exports=t})();
|
||||
//# sourceMappingURL=forum.js.map
|
2
extensions/lock/js/dist/forum.js.map
generated
vendored
2
extensions/lock/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
6
extensions/lock/js/src/@types/shims.d.ts
vendored
Normal file
6
extensions/lock/js/src/@types/shims.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
declare module 'flarum/common/models/Discussion' {
|
||||
export default interface Discussion {
|
||||
isLocked(): boolean;
|
||||
canLock(): boolean;
|
||||
}
|
||||
}
|
7
extensions/lock/js/src/forum/compat.ts
Normal file
7
extensions/lock/js/src/forum/compat.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import DiscussionLockedNotification from './components/DiscussionLockedNotification';
|
||||
import DiscussionLockedPost from './components/DiscussionLockedPost';
|
||||
|
||||
export default {
|
||||
'lock/components/DiscussionLockedNotification': DiscussionLockedNotification,
|
||||
'lock/components/DiscussionLockedPost': DiscussionLockedPost,
|
||||
};
|
@ -22,3 +22,9 @@ app.initializers.add('flarum-lock', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Expose compat API
|
||||
import lockCompat from './compat';
|
||||
import { compat } from '@flarum/core/forum';
|
||||
|
||||
Object.assign(compat, lockCompat);
|
||||
|
2
extensions/markdown/js/dist/admin.js
generated
vendored
2
extensions/markdown/js/dist/admin.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/markdown/js/dist/admin.js.map
generated
vendored
2
extensions/markdown/js/dist/admin.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/markdown/js/dist/forum.js
generated
vendored
2
extensions/markdown/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/markdown/js/dist/forum.js.map
generated
vendored
2
extensions/markdown/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
5
extensions/markdown/js/src/admin/compat.ts
Normal file
5
extensions/markdown/js/src/admin/compat.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import commonCompat from '../common/compat';
|
||||
|
||||
export default {
|
||||
...commonCompat,
|
||||
};
|
@ -2,3 +2,9 @@ import app from 'flarum/admin/app';
|
||||
import { initialize } from '../common/index';
|
||||
|
||||
app.initializers.add('flarum-markdown', initialize);
|
||||
|
||||
// Expose compat API
|
||||
import markdownCompat from './compat';
|
||||
import { compat } from '@flarum/core/admin';
|
||||
|
||||
Object.assign(compat, markdownCompat);
|
||||
|
7
extensions/markdown/js/src/common/compat.ts
Normal file
7
extensions/markdown/js/src/common/compat.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import MarkdownButton from './components/MarkdownButton';
|
||||
import MarkdownToolbar from './components/MarkdownToolbar';
|
||||
|
||||
export default {
|
||||
'markdown/components/MarkdownButton': MarkdownButton,
|
||||
'markdown/components/MarkdownToolbar': MarkdownToolbar,
|
||||
};
|
5
extensions/markdown/js/src/forum/compat.ts
Normal file
5
extensions/markdown/js/src/forum/compat.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import commonCompat from '../common/compat';
|
||||
|
||||
export default {
|
||||
...commonCompat,
|
||||
};
|
@ -2,3 +2,9 @@ import app from 'flarum/forum/app';
|
||||
import { initialize } from '../common/index';
|
||||
|
||||
app.initializers.add('flarum-markdown', initialize);
|
||||
|
||||
// Expose compat API
|
||||
import markdownCompat from './compat';
|
||||
import { compat } from '@flarum/core/forum';
|
||||
|
||||
Object.assign(compat, markdownCompat);
|
||||
|
2
extensions/mentions/js/dist/forum.js
generated
vendored
2
extensions/mentions/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/mentions/js/dist/forum.js.map
generated
vendored
2
extensions/mentions/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@ -1,4 +1,6 @@
|
||||
import GroupMentionedNotification from './components/GroupMentionedNotification';
|
||||
import MentionedByModal from './components/MentionedByModal';
|
||||
import MentionsDropdownItem from './components/MentionsDropdownItem';
|
||||
import MentionsUserPage from './components/MentionsUserPage';
|
||||
import PostMentionedNotification from './components/PostMentionedNotification';
|
||||
import UserMentionedNotification from './components/UserMentionedNotification';
|
||||
@ -9,13 +11,24 @@ import getMentionText from './utils/getMentionText';
|
||||
import * as reply from './utils/reply';
|
||||
import selectedText from './utils/selectedText';
|
||||
import * as textFormatter from './utils/textFormatter';
|
||||
import GroupMention from './mentionables/GroupMention';
|
||||
import MentionableModel from './mentionables/MentionableModel';
|
||||
import MentionableModels from './mentionables/MentionableModels';
|
||||
import PostMention from './mentionables/PostMention';
|
||||
import TagMention from './mentionables/TagMention';
|
||||
import UserMention from './mentionables/UserMention';
|
||||
import AtMentionFormat from './mentionables/formats/AtMentionFormat';
|
||||
import HashMentionFormat from './mentionables/formats/HashMentionFormat';
|
||||
import MentionFormat from './mentionables/formats/MentionFormat';
|
||||
import MentionFormats from './mentionables/formats/MentionFormats';
|
||||
import Mentionables from './extenders/Mentionables';
|
||||
import MentionedByModalState from './state/MentionedByModalState';
|
||||
|
||||
export default {
|
||||
'mentions/components/MentionsUserPage': MentionsUserPage,
|
||||
'mentions/components/PostMentionedNotification': PostMentionedNotification,
|
||||
'mentions/components/MentionedByModal': MentionedByModal,
|
||||
'mentions/components/MentionsDropdownItem': MentionsDropdownItem,
|
||||
'mentions/components/UserMentionedNotification': UserMentionedNotification,
|
||||
'mentions/components/GroupMentionedNotification': GroupMentionedNotification,
|
||||
'mentions/fragments/AutocompleteDropdown': AutocompleteDropdown,
|
||||
@ -25,7 +38,16 @@ export default {
|
||||
'mentions/utils/reply': reply,
|
||||
'mentions/utils/selectedText': selectedText,
|
||||
'mentions/utils/textFormatter': textFormatter,
|
||||
'mentions/mentionables/GroupMention': GroupMention,
|
||||
'mentions/mentionables/MentionableModel': MentionableModel,
|
||||
'mentions/mentionables/MentionableModels': MentionableModels,
|
||||
'mentions/mentionables/PostMention': PostMention,
|
||||
'mentions/mentionables/TagMention': TagMention,
|
||||
'mentions/mentionables/UserMention': UserMention,
|
||||
'mentions/mentionables/formats/AtMentionFormat': AtMentionFormat,
|
||||
'mentions/mentionables/formats/HashMentionFormat': HashMentionFormat,
|
||||
'mentions/mentionables/formats/MentionFormat': MentionFormat,
|
||||
'mentions/mentionables/formats/MentionFormats': MentionFormats,
|
||||
'mentions/extenders/Mentionables': Mentionables,
|
||||
'mentions/state/MentionedByModalState': MentionedByModalState,
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ namespace Flarum\Mentions\Formatter;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Http\SlugManager;
|
||||
use Flarum\Post\CommentPost;
|
||||
use Flarum\Post\Post;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use s9e\TextFormatter\Renderer;
|
||||
use s9e\TextFormatter\Utils;
|
||||
@ -40,16 +40,17 @@ class FormatPostMentions
|
||||
*
|
||||
* @param \s9e\TextFormatter\Renderer $renderer
|
||||
* @param mixed $context
|
||||
* @param string|null $xml
|
||||
* @param \Psr\Http\Message\ServerRequestInterface $request
|
||||
* @return string
|
||||
* @param string $xml
|
||||
* @param \Psr\Http\Message\ServerRequestInterface|null $request
|
||||
* @return string $xml to be rendered
|
||||
*/
|
||||
public function __invoke(Renderer $renderer, $context, $xml, Request $request = null)
|
||||
{
|
||||
$post = $context;
|
||||
return Utils::replaceAttributes($xml, 'POSTMENTION', function ($attributes) use ($context) {
|
||||
$post = (($context && isset($context->getRelations()['mentionsPosts'])) || $context instanceof Post)
|
||||
? $context->mentionsPosts->find($attributes['id'])
|
||||
: Post::find($attributes['id']);
|
||||
|
||||
return Utils::replaceAttributes($xml, 'POSTMENTION', function ($attributes) use ($post) {
|
||||
$post = $post->mentionsPosts->find($attributes['id']);
|
||||
if ($post && $post->user) {
|
||||
$attributes['displayname'] = $post->user->display_name;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\Mentions\Formatter;
|
||||
|
||||
use Flarum\Post\Post;
|
||||
use s9e\TextFormatter\Utils;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
@ -27,12 +28,16 @@ class UnparsePostMentions
|
||||
/**
|
||||
* Configure rendering for user mentions.
|
||||
*
|
||||
* @param string $xml
|
||||
* @param string|null $xml
|
||||
* @param mixed $context
|
||||
* @return string $xml to be unparsed
|
||||
* @return mixed $xml to be unparsed
|
||||
*/
|
||||
public function __invoke($context, string $xml)
|
||||
public function __invoke($context, $xml)
|
||||
{
|
||||
if ($xml === null) {
|
||||
return $xml;
|
||||
}
|
||||
|
||||
$xml = $this->updatePostMentionTags($context, $xml);
|
||||
$xml = $this->unparsePostMentionTags($xml);
|
||||
|
||||
@ -50,8 +55,11 @@ class UnparsePostMentions
|
||||
{
|
||||
$post = $context;
|
||||
|
||||
return Utils::replaceAttributes($xml, 'POSTMENTION', function ($attributes) use ($post) {
|
||||
$post = $post->mentionsPosts->find($attributes['id']);
|
||||
return Utils::replaceAttributes($xml, 'POSTMENTION', function ($attributes) use ($context) {
|
||||
$post = (($context && isset($context->getRelations()['mentionsPosts'])) || $context instanceof Post)
|
||||
? $context->mentionsPosts->find($attributes['id'])
|
||||
: Post::find($attributes['id']);
|
||||
|
||||
if ($post && $post->user) {
|
||||
$attributes['displayname'] = $post->user->display_name;
|
||||
}
|
||||
|
@ -18,12 +18,16 @@ class UnparseTagMentions
|
||||
/**
|
||||
* Configure rendering for user mentions.
|
||||
*
|
||||
* @param string $xml
|
||||
* @param string|null $xml
|
||||
* @param mixed $context
|
||||
* @return string $xml to be unparsed
|
||||
* @return mixed $xml to be unparsed
|
||||
*/
|
||||
public function __invoke($context, string $xml)
|
||||
public function __invoke($context, $xml)
|
||||
{
|
||||
if ($xml === null) {
|
||||
return $xml;
|
||||
}
|
||||
|
||||
$xml = $this->updateTagMentionTags($context, $xml);
|
||||
$xml = $this->unparseTagMentionTags($xml);
|
||||
|
||||
|
@ -29,12 +29,16 @@ class UnparseUserMentions
|
||||
/**
|
||||
* Configure rendering for user mentions.
|
||||
*
|
||||
* @param string $xml
|
||||
* @param string|null $xml
|
||||
* @param mixed $context
|
||||
* @return string $xml to be unparsed
|
||||
* @return mixed $xml to be unparsed
|
||||
*/
|
||||
public function __invoke($context, string $xml)
|
||||
public function __invoke($context, $xml)
|
||||
{
|
||||
if ($xml === null) {
|
||||
return $xml;
|
||||
}
|
||||
|
||||
$xml = $this->updateUserMentionTags($context, $xml);
|
||||
$xml = $this->unparseUserMentionTags($xml);
|
||||
|
||||
|
@ -47,6 +47,8 @@ class SendMentionsNotificationsJob extends AbstractJob
|
||||
|
||||
public function __construct(CommentPost $post, array $userMentions, array $postMentions, array $groupMentions)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->post = $post;
|
||||
$this->userMentions = $userMentions;
|
||||
$this->postMentions = $postMentions;
|
||||
|
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Mentions\Tests\integration\api;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
|
||||
class CreateDiscussionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->extension('flarum-mentions');
|
||||
|
||||
$this->extend(
|
||||
(new Extend\Event())
|
||||
->listen(\Flarum\Post\Event\Saving::class, function ($event) {
|
||||
$event->post->content;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function cannot_create_discussion_with_empty_string()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/discussions', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'title' => 'Test post',
|
||||
'content' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(422, $response->getStatusCode());
|
||||
|
||||
$body = (string) $response->getBody();
|
||||
$this->assertJson($body);
|
||||
$this->assertEquals([
|
||||
'errors' => [
|
||||
[
|
||||
'status' => '422',
|
||||
'code' => 'validation_error',
|
||||
'detail' => 'The content field is required.',
|
||||
'source' => ['pointer' => '/data/attributes/content'],
|
||||
],
|
||||
],
|
||||
], json_decode($body, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function cannot_create_discussion_without_content_property()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/discussions', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'title' => 'Test post',
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(422, $response->getStatusCode());
|
||||
|
||||
$body = (string) $response->getBody();
|
||||
$this->assertJson($body);
|
||||
$this->assertEquals([
|
||||
'errors' => [
|
||||
[
|
||||
'status' => '422',
|
||||
'code' => 'validation_error',
|
||||
'detail' => 'The content field is required.',
|
||||
'source' => ['pointer' => '/data/attributes/content'],
|
||||
],
|
||||
],
|
||||
], json_decode($body, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function cannot_create_discussion_with_content_set_to_null()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/discussions', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'title' => 'Test post',
|
||||
'content' => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(422, $response->getStatusCode());
|
||||
|
||||
$body = (string) $response->getBody();
|
||||
$this->assertJson($body);
|
||||
$this->assertEquals([
|
||||
'errors' => [
|
||||
[
|
||||
'status' => '422',
|
||||
'code' => 'validation_error',
|
||||
'detail' => 'The content field is required.',
|
||||
'source' => ['pointer' => '/data/attributes/content'],
|
||||
],
|
||||
],
|
||||
], json_decode($body, true));
|
||||
}
|
||||
}
|
110
extensions/mentions/tests/integration/api/EditPostTest.php
Normal file
110
extensions/mentions/tests/integration/api/EditPostTest.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Mentions\Tests\integration\api;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
|
||||
class EditPostTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->extension('flarum-mentions');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'discussions' => [
|
||||
['id' => 1, 'title' => 'Discussion with post', 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1],
|
||||
],
|
||||
'posts' => [
|
||||
['id' => 1, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>Text</p></t>'],
|
||||
]
|
||||
]);
|
||||
|
||||
$this->extend(
|
||||
(new Extend\Event())
|
||||
->listen(\Flarum\Post\Event\Saving::class, function ($event) {
|
||||
$event->post->content;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function cannot_update_post_with_empty_string()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('PATCH', '/api/posts/1', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(422, $response->getStatusCode());
|
||||
|
||||
$body = (string) $response->getBody();
|
||||
$this->assertJson($body);
|
||||
$this->assertEquals([
|
||||
'errors' => [
|
||||
[
|
||||
'status' => '422',
|
||||
'code' => 'validation_error',
|
||||
'detail' => 'The content field is required.',
|
||||
'source' => ['pointer' => '/data/attributes/content'],
|
||||
],
|
||||
],
|
||||
], json_decode($body, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function cannot_update_post_with_invalid_content_type()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('PATCH', '/api/posts/1', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(422, $response->getStatusCode());
|
||||
|
||||
$body = (string) $response->getBody();
|
||||
$this->assertJson($body);
|
||||
$this->assertEquals([
|
||||
'errors' => [
|
||||
[
|
||||
'status' => '422',
|
||||
'code' => 'validation_error',
|
||||
'detail' => 'The content field is required.',
|
||||
'source' => ['pointer' => '/data/attributes/content'],
|
||||
],
|
||||
],
|
||||
], json_decode($body, true));
|
||||
}
|
||||
}
|
@ -11,7 +11,9 @@ namespace Flarum\Mentions\Tests\integration\api;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Extend;
|
||||
use Flarum\Formatter\Formatter;
|
||||
use Flarum\Post\CommentPost;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Flarum\User\DisplayName\DriverInterface;
|
||||
@ -538,6 +540,40 @@ class PostMentionsTest extends TestCase
|
||||
$this->assertStringContainsString('PostMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsPosts->find(11));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function rendering_post_mention_with_a_post_context_works()
|
||||
{
|
||||
/** @var Formatter $formatter */
|
||||
$formatter = $this->app()->getContainer()->make(Formatter::class);
|
||||
|
||||
$post = Post::find(4);
|
||||
$user = User::find(1);
|
||||
|
||||
$xml = $formatter->parse($post->content, $post, $user);
|
||||
$renderedHtml = $formatter->render($xml, $post);
|
||||
|
||||
$this->assertStringContainsString('TOBY$', $renderedHtml);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function rendering_post_mention_without_a_context_works()
|
||||
{
|
||||
/** @var Formatter $formatter */
|
||||
$formatter = $this->app()->getContainer()->make(Formatter::class);
|
||||
|
||||
$post = Post::find(4);
|
||||
$user = User::find(1);
|
||||
|
||||
$xml = $formatter->parse($post->content, null, $user);
|
||||
$renderedHtml = $formatter->render($xml);
|
||||
|
||||
$this->assertStringContainsString('TOBY$', $renderedHtml);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomOtherDisplayNameDriver implements DriverInterface
|
||||
|
2
extensions/nicknames/js/dist/forum.js
generated
vendored
2
extensions/nicknames/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/nicknames/js/dist/forum.js.map
generated
vendored
2
extensions/nicknames/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
5
extensions/nicknames/js/src/@types/shims.d.ts
vendored
Normal file
5
extensions/nicknames/js/src/@types/shims.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
declare module 'flarum/common/models/User' {
|
||||
export default interface User {
|
||||
canEditNickname(): boolean;
|
||||
}
|
||||
}
|
5
extensions/nicknames/js/src/forum/compat.ts
Normal file
5
extensions/nicknames/js/src/forum/compat.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import NicknameModal from './components/NicknameModal';
|
||||
|
||||
export default {
|
||||
'nicknames/components/NicknameModal': NicknameModal,
|
||||
};
|
@ -110,3 +110,9 @@ app.initializers.add('flarum/nicknames', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Expose compat API
|
||||
import nicknamesCompat from './compat';
|
||||
import { compat } from '@flarum/core/forum';
|
||||
|
||||
Object.assign(compat, nicknamesCompat);
|
||||
|
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Sami Mazouz
|
||||
Copyright (c) 2024 Stichting Flarum (Flarum 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
|
||||
|
@ -1,5 +1,18 @@
|
||||
# Package Manager
|
||||
# Extension Manager
|
||||
|
||||
*An Experiment.*
|
||||
The extension manager is a tool that allows you to easily install and manage extensions. It runs [composer](https://getcomposer.org/) under the hood.
|
||||
|
||||
Read: https://github.com/flarum/package-manager/wiki
|
||||
## Security
|
||||
|
||||
If admin access is given to untrustworthy users, they can install malicious extensions. Please be careful.
|
||||
|
||||
This extension is optional and can be removed for those who prefer to manually manage installs and updates through the command line interface.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you have many extensions installed, you may run into memory issues when using the extension manager. If this happens, you can use an asynchronous queue that will run the extension manager in the background.
|
||||
|
||||
* Simple database queue guide: https://discuss.flarum.org/d/28151-database-queue-the-simplest-queue-even-for-shared-hosting
|
||||
* (Advanced) Redis queue: https://discuss.flarum.org/d/21873-redis-sessions-cache-queues
|
||||
|
||||
You can find detailed logs on the extension manager operations in the `storage/logs/composer` directory. Please include the latest log file when reporting issues in the [Flarum support forum](https://discuss.flarum.org/t/support).
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "flarum/package-manager",
|
||||
"description": "A Flarum Package Manager.",
|
||||
"name": "flarum/extension-manager",
|
||||
"description": "An extension manager to install, update and remove extension packages from the interface (Wrapper around composer).",
|
||||
"keywords": [
|
||||
"extensions",
|
||||
"composer",
|
||||
@ -18,12 +18,12 @@
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/flarum/package-manager/issues",
|
||||
"source": "https://github.com/flarum/package-manager"
|
||||
"issues": "https://github.com/flarum/framework/issues",
|
||||
"source": "https://github.com/flarum/extension-manager"
|
||||
},
|
||||
"require": {
|
||||
"flarum/core": "^1.8",
|
||||
"composer/composer": "^2.3"
|
||||
"composer/composer": "^2.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"flarum/testing": "^1.0.0",
|
||||
@ -31,7 +31,7 @@
|
||||
},
|
||||
"extra": {
|
||||
"flarum-extension": {
|
||||
"title": "Package Manager",
|
||||
"title": "Extension Manager",
|
||||
"icon": {
|
||||
"name": "fas fa-box-open",
|
||||
"backgroundColor": "#117187",
|
||||
@ -69,12 +69,12 @@
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Flarum\\PackageManager\\": "src/"
|
||||
"Flarum\\ExtensionManager\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Flarum\\PackageManager\\Tests\\": "tests/"
|
||||
"Flarum\\ExtensionManager\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -7,32 +7,26 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager;
|
||||
namespace Flarum\ExtensionManager;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Foundation\Paths;
|
||||
use Flarum\Frontend\Document;
|
||||
use Flarum\PackageManager\Exception\ComposerCommandFailedException;
|
||||
use Flarum\PackageManager\Exception\ComposerRequireFailedException;
|
||||
use Flarum\PackageManager\Exception\ComposerUpdateFailedException;
|
||||
use Flarum\PackageManager\Exception\ExceptionHandler;
|
||||
use Flarum\PackageManager\Exception\MajorUpdateFailedException;
|
||||
use Flarum\PackageManager\Settings\LastUpdateCheck;
|
||||
use Flarum\PackageManager\Settings\LastUpdateRun;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
use Illuminate\Queue\SyncQueue;
|
||||
|
||||
return [
|
||||
(new Extend\Routes('api'))
|
||||
->post('/package-manager/extensions', 'package-manager.extensions.require', Api\Controller\RequireExtensionController::class)
|
||||
->patch('/package-manager/extensions/{id}', 'package-manager.extensions.update', Api\Controller\UpdateExtensionController::class)
|
||||
->delete('/package-manager/extensions/{id}', 'package-manager.extensions.remove', Api\Controller\RemoveExtensionController::class)
|
||||
->post('/package-manager/check-for-updates', 'package-manager.check-for-updates', Api\Controller\CheckForUpdatesController::class)
|
||||
->post('/package-manager/why-not', 'package-manager.why-not', Api\Controller\WhyNotController::class)
|
||||
->post('/package-manager/minor-update', 'package-manager.minor-update', Api\Controller\MinorUpdateController::class)
|
||||
->post('/package-manager/major-update', 'package-manager.major-update', Api\Controller\MajorUpdateController::class)
|
||||
->post('/package-manager/global-update', 'package-manager.global-update', Api\Controller\GlobalUpdateController::class)
|
||||
->get('/package-manager-tasks', 'package-manager.tasks.index', Api\Controller\ListTasksController::class),
|
||||
->post('/extension-manager/extensions', 'extension-manager.extensions.require', Api\Controller\RequireExtensionController::class)
|
||||
->patch('/extension-manager/extensions/{id}', 'extension-manager.extensions.update', Api\Controller\UpdateExtensionController::class)
|
||||
->delete('/extension-manager/extensions/{id}', 'extension-manager.extensions.remove', Api\Controller\RemoveExtensionController::class)
|
||||
->post('/extension-manager/check-for-updates', 'extension-manager.check-for-updates', Api\Controller\CheckForUpdatesController::class)
|
||||
->post('/extension-manager/why-not', 'extension-manager.why-not', Api\Controller\WhyNotController::class)
|
||||
->post('/extension-manager/minor-update', 'extension-manager.minor-update', Api\Controller\MinorUpdateController::class)
|
||||
->post('/extension-manager/major-update', 'extension-manager.major-update', Api\Controller\MajorUpdateController::class)
|
||||
->post('/extension-manager/global-update', 'extension-manager.global-update', Api\Controller\GlobalUpdateController::class)
|
||||
->get('/extension-manager-tasks', 'extension-manager.tasks.index', Api\Controller\ListTasksController::class)
|
||||
->post('/extension-manager/composer', 'extension-manager.composer', Api\Controller\ConfigureComposerController::class),
|
||||
|
||||
(new Extend\Frontend('admin'))
|
||||
->css(__DIR__.'/less/admin.less')
|
||||
@ -40,31 +34,34 @@ return [
|
||||
->content(function (Document $document) {
|
||||
$paths = resolve(Paths::class);
|
||||
|
||||
$document->payload['flarum-package-manager.writable_dirs'] = is_writable($paths->vendor)
|
||||
$document->payload['flarum-extension-manager.writable_dirs'] = is_writable($paths->vendor)
|
||||
&& is_writable($paths->storage)
|
||||
&& (! file_exists($paths->storage.'/.composer') || is_writable($paths->storage.'/.composer'))
|
||||
&& is_writable($paths->base.'/composer.json')
|
||||
&& is_writable($paths->base.'/composer.lock');
|
||||
|
||||
$document->payload['flarum-package-manager.using_sync_queue'] = resolve(Queue::class) instanceof SyncQueue;
|
||||
$document->payload['flarum-extension-manager.using_sync_queue'] = resolve(Queue::class) instanceof SyncQueue;
|
||||
}),
|
||||
|
||||
new Extend\Locales(__DIR__.'/locale'),
|
||||
|
||||
(new Extend\Settings())
|
||||
->default(LastUpdateCheck::key(), json_encode(LastUpdateCheck::default()))
|
||||
->default(LastUpdateRun::key(), json_encode(LastUpdateRun::default()))
|
||||
->default('flarum-package-manager.queue_jobs', false),
|
||||
->default(Settings\LastUpdateCheck::key(), json_encode(Settings\LastUpdateCheck::default()))
|
||||
->default(Settings\LastUpdateRun::key(), json_encode(Settings\LastUpdateRun::default()))
|
||||
->default('flarum-extension-manager.queue_jobs', '0')
|
||||
->default('flarum-extension-manager.minimum_stability', 'stable')
|
||||
->default('flarum-extension-manager.task_retention_days', 7),
|
||||
|
||||
(new Extend\ServiceProvider)
|
||||
->register(PackageManagerServiceProvider::class),
|
||||
->register(ExtensionManagerServiceProvider::class),
|
||||
|
||||
(new Extend\ErrorHandling)
|
||||
->handler(ComposerCommandFailedException::class, ExceptionHandler::class)
|
||||
->handler(ComposerRequireFailedException::class, ExceptionHandler::class)
|
||||
->handler(ComposerUpdateFailedException::class, ExceptionHandler::class)
|
||||
->handler(MajorUpdateFailedException::class, ExceptionHandler::class)
|
||||
->handler(Exception\ComposerCommandFailedException::class, Exception\ExceptionHandler::class)
|
||||
->handler(Exception\ComposerRequireFailedException::class, Exception\ExceptionHandler::class)
|
||||
->handler(Exception\ComposerUpdateFailedException::class, Exception\ExceptionHandler::class)
|
||||
->handler(Exception\MajorUpdateFailedException::class, Exception\ExceptionHandler::class)
|
||||
->status('extension_already_installed', 409)
|
||||
->status('extension_not_installed', 409)
|
||||
->status('no_new_major_version', 409),
|
||||
->status('no_new_major_version', 409)
|
||||
->status('extension_not_directly_dependency', 409),
|
||||
];
|
||||
|
49
extensions/package-manager/js/dist-typings/compat.d.ts
generated
vendored
Normal file
49
extensions/package-manager/js/dist-typings/compat.d.ts
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
import AuthMethodModal from './components/AuthMethodModal';
|
||||
import ConfigureAuth from './components/ConfigureAuth';
|
||||
import ConfigureComposer from './components/ConfigureComposer';
|
||||
import ConfigureJson from './components/ConfigureJson';
|
||||
import ControlSection from './components/ControlSection';
|
||||
import ExtensionItem from './components/ExtensionItem';
|
||||
import Installer from './components/Installer';
|
||||
import Label from './components/Label';
|
||||
import MajorUpdater from './components/MajorUpdater';
|
||||
import Pagination from './components/Pagination';
|
||||
import QueueSection from './components/QueueSection';
|
||||
import RepositoryModal from './components/RepositoryModal';
|
||||
import SettingsPage from './components/SettingsPage';
|
||||
import TaskOutputModal from './components/TaskOutputModal';
|
||||
import Updater from './components/Updater';
|
||||
import WhyNotModal from './components/WhyNotModal';
|
||||
import Task from './models/Task';
|
||||
import ControlSectionState from './states/ControlSectionState';
|
||||
import ExtensionManagerState from './states/ExtensionManagerState';
|
||||
import QueueState from './states/QueueState';
|
||||
import errorHandler from './utils/errorHandler';
|
||||
import humanDuration from './utils/humanDuration';
|
||||
import jumpToQueue from './utils/jumpToQueue';
|
||||
declare const _default: {
|
||||
'extension-manager/components/AuthMethodModal': typeof AuthMethodModal;
|
||||
'extension-manager/components/ConfigureAuth': typeof ConfigureAuth;
|
||||
'extension-manager/components/ConfigureComposer': typeof ConfigureComposer;
|
||||
'extension-manager/components/ConfigureJson': typeof ConfigureJson;
|
||||
'extension-manager/components/ControlSection': typeof ControlSection;
|
||||
'extension-manager/components/ExtensionItem': typeof ExtensionItem;
|
||||
'extension-manager/components/Installer': typeof Installer;
|
||||
'extension-manager/components/Label': typeof Label;
|
||||
'extension-manager/components/MajorUpdater': typeof MajorUpdater;
|
||||
'extension-manager/components/Pagination': typeof Pagination;
|
||||
'extension-manager/components/QueueSection': typeof QueueSection;
|
||||
'extension-manager/components/RepositoryModal': typeof RepositoryModal;
|
||||
'extension-manager/components/SettingsPage': typeof SettingsPage;
|
||||
'extension-manager/components/TaskOutputModal': typeof TaskOutputModal;
|
||||
'extension-manager/components/Updater': typeof Updater;
|
||||
'extension-manager/components/WhyNotModal': typeof WhyNotModal;
|
||||
'extension-manager/models/Task': typeof Task;
|
||||
'extension-manager/states/ControlSectionState': typeof ControlSectionState;
|
||||
'extension-manager/states/ExtensionManagerState': typeof ExtensionManagerState;
|
||||
'extension-manager/states/QueueState': typeof QueueState;
|
||||
'extension-manager/utils/errorHandler': typeof errorHandler;
|
||||
'extension-manager/utils/humanDuration': typeof humanDuration;
|
||||
'extension-manager/utils/jumpToQueue': typeof jumpToQueue;
|
||||
};
|
||||
export default _default;
|
19
extensions/package-manager/js/dist-typings/components/AuthMethodModal.d.ts
generated
vendored
Normal file
19
extensions/package-manager/js/dist-typings/components/AuthMethodModal.d.ts
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
import Modal, { IInternalModalAttrs } from 'flarum/common/components/Modal';
|
||||
import Mithril from 'mithril';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
export interface IAuthMethodModalAttrs extends IInternalModalAttrs {
|
||||
onsubmit: (type: string, host: string, token: string) => void;
|
||||
type?: string;
|
||||
host?: string;
|
||||
token?: string;
|
||||
}
|
||||
export default class AuthMethodModal<CustomAttrs extends IAuthMethodModalAttrs = IAuthMethodModalAttrs> extends Modal<CustomAttrs> {
|
||||
protected type: Stream<string>;
|
||||
protected host: Stream<string>;
|
||||
protected token: Stream<string>;
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>): void;
|
||||
className(): string;
|
||||
title(): Mithril.Children;
|
||||
content(): Mithril.Children;
|
||||
submit(): void;
|
||||
}
|
10
extensions/package-manager/js/dist-typings/components/ConfigureAuth.d.ts
generated
vendored
Normal file
10
extensions/package-manager/js/dist-typings/components/ConfigureAuth.d.ts
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
import type Mithril from 'mithril';
|
||||
import ConfigureJson, { IConfigureJson } from './ConfigureJson';
|
||||
export default class ConfigureAuth extends ConfigureJson<IConfigureJson> {
|
||||
protected type: string;
|
||||
title(): Mithril.Children;
|
||||
className(): string;
|
||||
content(): Mithril.Children;
|
||||
submitButton(): Mithril.Children[];
|
||||
onchange(oldHost: string | null, type: string, host: string, token: string): void;
|
||||
}
|
14
extensions/package-manager/js/dist-typings/components/ConfigureComposer.d.ts
generated
vendored
Normal file
14
extensions/package-manager/js/dist-typings/components/ConfigureComposer.d.ts
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import type Mithril from 'mithril';
|
||||
import ConfigureJson, { type IConfigureJson } from './ConfigureJson';
|
||||
export declare type Repository = {
|
||||
type: 'composer' | 'vcs' | 'path';
|
||||
url: string;
|
||||
};
|
||||
export default class ConfigureComposer extends ConfigureJson<IConfigureJson> {
|
||||
protected type: string;
|
||||
title(): Mithril.Children;
|
||||
className(): string;
|
||||
content(): Mithril.Children;
|
||||
submitButton(): Mithril.Children[];
|
||||
onchange(repository: Repository, name: string): void;
|
||||
}
|
24
extensions/package-manager/js/dist-typings/components/ConfigureJson.d.ts
generated
vendored
Normal file
24
extensions/package-manager/js/dist-typings/components/ConfigureJson.d.ts
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
import type Mithril from 'mithril';
|
||||
import Component, { type ComponentAttrs } from 'flarum/common/Component';
|
||||
import { CommonSettingsItemOptions, type SettingsComponentOptions } from '@flarum/core/src/admin/components/AdminPage';
|
||||
import type ItemList from 'flarum/common/utils/ItemList';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
export interface IConfigureJson extends ComponentAttrs {
|
||||
buildSettingComponent: (entry: ((this: this) => Mithril.Children) | SettingsComponentOptions) => Mithril.Children;
|
||||
}
|
||||
export default abstract class ConfigureJson<CustomAttrs extends IConfigureJson = IConfigureJson> extends Component<CustomAttrs> {
|
||||
protected settings: Record<string, Stream<any>>;
|
||||
protected initialSettings: Record<string, any> | null;
|
||||
protected loading: boolean;
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>): void;
|
||||
protected abstract type: string;
|
||||
abstract title(): Mithril.Children;
|
||||
abstract content(): Mithril.Children;
|
||||
className(): string;
|
||||
view(): Mithril.Children;
|
||||
submitButton(): Mithril.Children[];
|
||||
customSettingComponents(): ItemList<(attributes: CommonSettingsItemOptions) => Mithril.Children>;
|
||||
setting(key: string): Stream<any>;
|
||||
submit(readOnly: boolean): void;
|
||||
isDirty(): boolean;
|
||||
}
|
5
extensions/package-manager/js/dist-typings/components/ExtensionItem.d.ts
generated
vendored
5
extensions/package-manager/js/dist-typings/components/ExtensionItem.d.ts
generated
vendored
@ -5,7 +5,10 @@ import { UpdatedPackage } from '../states/ControlSectionState';
|
||||
export interface ExtensionItemAttrs extends ComponentAttrs {
|
||||
extension: Extension;
|
||||
updates: UpdatedPackage;
|
||||
onClickUpdate: CallableFunction;
|
||||
onClickUpdate: CallableFunction | {
|
||||
soft: CallableFunction;
|
||||
hard: CallableFunction;
|
||||
};
|
||||
whyNotWarning?: boolean;
|
||||
isCore?: boolean;
|
||||
updatable?: boolean;
|
||||
|
18
extensions/package-manager/js/dist-typings/components/RepositoryModal.d.ts
generated
vendored
Normal file
18
extensions/package-manager/js/dist-typings/components/RepositoryModal.d.ts
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
import Modal, { IInternalModalAttrs } from 'flarum/common/components/Modal';
|
||||
import Mithril from 'mithril';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
import { type Repository } from './ConfigureComposer';
|
||||
export interface IRepositoryModalAttrs extends IInternalModalAttrs {
|
||||
onsubmit: (repository: Repository, key: string) => void;
|
||||
name?: string;
|
||||
repository?: Repository;
|
||||
}
|
||||
export default class RepositoryModal<CustomAttrs extends IRepositoryModalAttrs = IRepositoryModalAttrs> extends Modal<CustomAttrs> {
|
||||
protected name: Stream<string>;
|
||||
protected repository: Stream<Repository>;
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>): void;
|
||||
className(): string;
|
||||
title(): Mithril.Children;
|
||||
content(): Mithril.Children;
|
||||
submit(): void;
|
||||
}
|
2
extensions/package-manager/js/dist-typings/components/SettingsPage.d.ts
generated
vendored
2
extensions/package-manager/js/dist-typings/components/SettingsPage.d.ts
generated
vendored
@ -2,5 +2,7 @@ import type Mithril from 'mithril';
|
||||
import ExtensionPage, { ExtensionPageAttrs } from 'flarum/admin/components/ExtensionPage';
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
export default class SettingsPage extends ExtensionPage {
|
||||
content(): JSX.Element;
|
||||
sections(vnode: Mithril.VnodeDOM<ExtensionPageAttrs, this>): ItemList<unknown>;
|
||||
onsaved(): void;
|
||||
}
|
||||
|
2
extensions/package-manager/js/dist-typings/components/TaskOutputModal.d.ts
generated
vendored
2
extensions/package-manager/js/dist-typings/components/TaskOutputModal.d.ts
generated
vendored
@ -1,5 +1,5 @@
|
||||
/// <reference types="mithril" />
|
||||
/// <reference types="flarum/@types/translator-icu-rich" />
|
||||
/// <reference types="@flarum/core/dist-typings/@types/translator-icu-rich" />
|
||||
import Modal, { IInternalModalAttrs } from 'flarum/common/components/Modal';
|
||||
import Task from '../models/Task';
|
||||
interface TaskOutputModalAttrs extends IInternalModalAttrs {
|
||||
|
2
extensions/package-manager/js/dist-typings/components/WhyNotModal.d.ts
generated
vendored
2
extensions/package-manager/js/dist-typings/components/WhyNotModal.d.ts
generated
vendored
@ -1,4 +1,4 @@
|
||||
/// <reference types="flarum/@types/translator-icu-rich" />
|
||||
/// <reference types="@flarum/core/dist-typings/@types/translator-icu-rich" />
|
||||
import type Mithril from 'mithril';
|
||||
import Modal, { IInternalModalAttrs } from 'flarum/common/components/Modal';
|
||||
export interface WhyNotModalAttrs extends IInternalModalAttrs {
|
||||
|
1
extensions/package-manager/js/dist-typings/models/Task.d.ts
generated
vendored
1
extensions/package-manager/js/dist-typings/models/Task.d.ts
generated
vendored
@ -6,6 +6,7 @@ export default class Task extends Model {
|
||||
command(): string;
|
||||
package(): string;
|
||||
output(): string;
|
||||
guessedCause(): string;
|
||||
createdAt(): Date | null | undefined;
|
||||
startedAt(): Date;
|
||||
finishedAt(): Date;
|
||||
|
12
extensions/package-manager/js/dist-typings/states/ControlSectionState.d.ts
generated
vendored
12
extensions/package-manager/js/dist-typings/states/ControlSectionState.d.ts
generated
vendored
@ -9,6 +9,8 @@ export declare type UpdatedPackage = {
|
||||
'latest-minor': string | null;
|
||||
'latest-major': string | null;
|
||||
'latest-status': string;
|
||||
'required-as': string;
|
||||
'direct-dependency': boolean;
|
||||
description: string;
|
||||
};
|
||||
export declare type ComposerUpdates = {
|
||||
@ -31,7 +33,7 @@ export declare type LastUpdateRun = {
|
||||
} & {
|
||||
limitedPackages: () => string[];
|
||||
};
|
||||
export declare type LoadingTypes = UpdaterLoadingTypes | InstallerLoadingTypes | MajorUpdaterLoadingTypes;
|
||||
export declare type LoadingTypes = UpdaterLoadingTypes | InstallerLoadingTypes | MajorUpdaterLoadingTypes | 'queued-action';
|
||||
export declare type CoreUpdate = {
|
||||
package: UpdatedPackage;
|
||||
extension: Extension;
|
||||
@ -45,13 +47,17 @@ export default class ControlSectionState {
|
||||
get lastUpdateRun(): LastUpdateRun;
|
||||
constructor();
|
||||
isLoading(name?: LoadingTypes): boolean;
|
||||
isLoadingOtherThan(name: LoadingTypes): boolean;
|
||||
hasOperationRunning(): boolean;
|
||||
setLoading(name: LoadingTypes): void;
|
||||
requirePackage(data: any): void;
|
||||
checkForUpdates(): void;
|
||||
updateCoreMinor(): void;
|
||||
updateExtension(extension: Extension): void;
|
||||
updateExtension(extension: Extension, updateMode: 'soft' | 'hard'): void;
|
||||
updateGlobally(): void;
|
||||
formatExtensionUpdates(lastUpdateCheck: LastUpdateCheck): Extension[];
|
||||
formatCoreUpdate(lastUpdateCheck: LastUpdateCheck): CoreUpdate | null;
|
||||
majorUpdate({ dryRun }: {
|
||||
dryRun: boolean;
|
||||
}): void;
|
||||
}
|
||||
export {};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import QueueState from './QueueState';
|
||||
import ControlSectionState from './ControlSectionState';
|
||||
export default class PackageManagerState {
|
||||
export default class ExtensionManagerState {
|
||||
queue: QueueState;
|
||||
control: ControlSectionState;
|
||||
}
|
5
extensions/package-manager/js/dist-typings/states/QueueState.d.ts
generated
vendored
5
extensions/package-manager/js/dist-typings/states/QueueState.d.ts
generated
vendored
@ -1,11 +1,12 @@
|
||||
import Task from '../models/Task';
|
||||
import { ApiQueryParamsPlural } from 'flarum/common/Store';
|
||||
export default class QueueState {
|
||||
private polling;
|
||||
private tasks;
|
||||
private limit;
|
||||
private offset;
|
||||
private total;
|
||||
load(params?: ApiQueryParamsPlural): Promise<import("flarum/common/Store").ApiResponsePlural<Task>>;
|
||||
load(params?: ApiQueryParamsPlural, actionTaken?: boolean): Promise<Task[]>;
|
||||
getItems(): Task[] | null;
|
||||
getTotalPages(): number;
|
||||
pageNumber(): number;
|
||||
@ -13,4 +14,6 @@ export default class QueueState {
|
||||
hasNext(): boolean;
|
||||
prev(): void;
|
||||
next(): void;
|
||||
pollQueue(actionTaken?: boolean): void;
|
||||
hasPending(): boolean;
|
||||
}
|
||||
|
2
extensions/package-manager/js/dist/admin.js
generated
vendored
2
extensions/package-manager/js/dist/admin.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/package-manager/js/dist/admin.js.map
generated
vendored
2
extensions/package-manager/js/dist/admin.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@flarum/package-manager",
|
||||
"name": "@flarum/extension-manager",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"prettier": "@flarum/prettier-config",
|
||||
|
52
extensions/package-manager/js/src/admin/compat.ts
Normal file
52
extensions/package-manager/js/src/admin/compat.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import AuthMethodModal from './components/AuthMethodModal';
|
||||
import ConfigureAuth from './components/ConfigureAuth';
|
||||
import ConfigureComposer from './components/ConfigureComposer';
|
||||
import ConfigureJson from './components/ConfigureJson';
|
||||
import ControlSection from './components/ControlSection';
|
||||
import ExtensionItem from './components/ExtensionItem';
|
||||
import Installer from './components/Installer';
|
||||
import Label from './components/Label';
|
||||
import MajorUpdater from './components/MajorUpdater';
|
||||
import Pagination from './components/Pagination';
|
||||
import QueueSection from './components/QueueSection';
|
||||
import RepositoryModal from './components/RepositoryModal';
|
||||
import SettingsPage from './components/SettingsPage';
|
||||
import TaskOutputModal from './components/TaskOutputModal';
|
||||
import Updater from './components/Updater';
|
||||
import WhyNotModal from './components/WhyNotModal';
|
||||
|
||||
import Task from './models/Task';
|
||||
|
||||
import ControlSectionState from './states/ControlSectionState';
|
||||
import ExtensionManagerState from './states/ExtensionManagerState';
|
||||
import QueueState from './states/QueueState';
|
||||
|
||||
import errorHandler from './utils/errorHandler';
|
||||
import humanDuration from './utils/humanDuration';
|
||||
import jumpToQueue from './utils/jumpToQueue';
|
||||
|
||||
export default {
|
||||
'extension-manager/components/AuthMethodModal': AuthMethodModal,
|
||||
'extension-manager/components/ConfigureAuth': ConfigureAuth,
|
||||
'extension-manager/components/ConfigureComposer': ConfigureComposer,
|
||||
'extension-manager/components/ConfigureJson': ConfigureJson,
|
||||
'extension-manager/components/ControlSection': ControlSection,
|
||||
'extension-manager/components/ExtensionItem': ExtensionItem,
|
||||
'extension-manager/components/Installer': Installer,
|
||||
'extension-manager/components/Label': Label,
|
||||
'extension-manager/components/MajorUpdater': MajorUpdater,
|
||||
'extension-manager/components/Pagination': Pagination,
|
||||
'extension-manager/components/QueueSection': QueueSection,
|
||||
'extension-manager/components/RepositoryModal': RepositoryModal,
|
||||
'extension-manager/components/SettingsPage': SettingsPage,
|
||||
'extension-manager/components/TaskOutputModal': TaskOutputModal,
|
||||
'extension-manager/components/Updater': Updater,
|
||||
'extension-manager/components/WhyNotModal': WhyNotModal,
|
||||
'extension-manager/models/Task': Task,
|
||||
'extension-manager/states/ControlSectionState': ControlSectionState,
|
||||
'extension-manager/states/ExtensionManagerState': ExtensionManagerState,
|
||||
'extension-manager/states/QueueState': QueueState,
|
||||
'extension-manager/utils/errorHandler': errorHandler,
|
||||
'extension-manager/utils/humanDuration': humanDuration,
|
||||
'extension-manager/utils/jumpToQueue': jumpToQueue,
|
||||
};
|
@ -0,0 +1,88 @@
|
||||
import Modal, { IInternalModalAttrs } from 'flarum/common/components/Modal';
|
||||
import Mithril from 'mithril';
|
||||
import app from 'flarum/admin/app';
|
||||
import Select from 'flarum/common/components/Select';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
|
||||
export interface IAuthMethodModalAttrs extends IInternalModalAttrs {
|
||||
onsubmit: (type: string, host: string, token: string) => void;
|
||||
type?: string;
|
||||
host?: string;
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export default class AuthMethodModal<CustomAttrs extends IAuthMethodModalAttrs = IAuthMethodModalAttrs> extends Modal<CustomAttrs> {
|
||||
protected type!: Stream<string>;
|
||||
protected host!: Stream<string>;
|
||||
protected token!: Stream<string>;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.type = Stream(this.attrs.type || 'bearer');
|
||||
this.host = Stream(this.attrs.host || '');
|
||||
this.token = Stream(this.attrs.token || '');
|
||||
}
|
||||
|
||||
className(): string {
|
||||
return 'AuthMethodModal Modal--small';
|
||||
}
|
||||
|
||||
title(): Mithril.Children {
|
||||
const context = this.attrs.host ? 'edit' : 'add';
|
||||
return app.translator.trans(`flarum-extension-manager.admin.auth_config.${context}_label`);
|
||||
}
|
||||
|
||||
content(): Mithril.Children {
|
||||
const types = {
|
||||
'github-oauth': app.translator.trans('flarum-extension-manager.admin.auth_config.types.github-oauth'),
|
||||
'gitlab-oauth': app.translator.trans('flarum-extension-manager.admin.auth_config.types.gitlab-oauth'),
|
||||
'gitlab-token': app.translator.trans('flarum-extension-manager.admin.auth_config.types.gitlab-token'),
|
||||
bearer: app.translator.trans('flarum-extension-manager.admin.auth_config.types.bearer'),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.auth_config.add_modal.type_label')}</label>
|
||||
<Select options={types} value={this.type()} onchange={this.type} />
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.auth_config.add_modal.host_label')}</label>
|
||||
<input
|
||||
className="FormControl"
|
||||
bidi={this.host}
|
||||
placeholder={app.translator.trans('flarum-extension-manager.admin.auth_config.add_modal.host_placeholder')}
|
||||
/>
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.auth_config.add_modal.token_label')}</label>
|
||||
<textarea
|
||||
className="FormControl"
|
||||
oninput={(e: InputEvent) => this.token((e.target as HTMLTextAreaElement).value)}
|
||||
rows="6"
|
||||
placeholder={
|
||||
this.token().startsWith('unchanged:')
|
||||
? extractText(app.translator.trans('flarum-extension-manager.admin.auth_config.add_modal.unchanged_token_placeholder'))
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{this.token().startsWith('unchanged:') ? '' : this.token()}
|
||||
</textarea>
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
<Button className="Button Button--primary" onclick={this.submit.bind(this)}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.auth_config.add_modal.submit_button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
submit() {
|
||||
this.attrs.onsubmit(this.type(), this.host(), this.token());
|
||||
this.hide();
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
import app from 'flarum/admin/app';
|
||||
import type Mithril from 'mithril';
|
||||
import ConfigureJson, { IConfigureJson } from './ConfigureJson';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import AuthMethodModal from './AuthMethodModal';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
|
||||
export default class ConfigureAuth extends ConfigureJson<IConfigureJson> {
|
||||
protected type = 'auth';
|
||||
|
||||
title(): Mithril.Children {
|
||||
return app.translator.trans('flarum-extension-manager.admin.auth_config.title');
|
||||
}
|
||||
|
||||
className(): string {
|
||||
return 'ConfigureAuth';
|
||||
}
|
||||
|
||||
content(): Mithril.Children {
|
||||
const authSettings = Object.keys(this.settings);
|
||||
const hasAuthSettings =
|
||||
authSettings.length &&
|
||||
authSettings.every((type) => {
|
||||
const data = this.settings[type]();
|
||||
|
||||
return Array.isArray(data) ? data.length : Object.keys(data).length;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="ExtensionManager-SettingsGroups-content">
|
||||
{hasAuthSettings ? (
|
||||
authSettings.map((type) => {
|
||||
const hosts = this.settings[type]();
|
||||
|
||||
return (
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans(`flarum-extension-manager.admin.auth_config.types.${type}`)}</label>
|
||||
<div className="ConfigureAuth-hosts">
|
||||
{Object.keys(hosts).map((host) => {
|
||||
const data = hosts[host] as string | Record<string, string>;
|
||||
|
||||
return (
|
||||
<div className="ButtonGroup ButtonGroup--full">
|
||||
<Button
|
||||
className="Button"
|
||||
icon="fas fa-key"
|
||||
onclick={() =>
|
||||
app.modal.show(AuthMethodModal, {
|
||||
type,
|
||||
host,
|
||||
token: data,
|
||||
onsubmit: this.onchange.bind(this, host),
|
||||
})
|
||||
}
|
||||
>
|
||||
{host}
|
||||
</Button>
|
||||
<Button
|
||||
className="Button Button--icon"
|
||||
icon="fas fa-trash"
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.auth_config.delete_label')}
|
||||
onclick={() => {
|
||||
if (confirm(extractText(app.translator.trans('flarum-extension-manager.admin.auth_config.delete_confirmation')))) {
|
||||
const newType = { ...this.setting(type)() };
|
||||
delete newType[host];
|
||||
|
||||
if (Object.keys(newType).length) {
|
||||
this.setting(type)(newType);
|
||||
} else {
|
||||
delete this.settings[type];
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<span className="helpText">{app.translator.trans('flarum-extension-manager.admin.auth_config.no_auth_methods_configured')}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
submitButton(): Mithril.Children[] {
|
||||
const items = super.submitButton();
|
||||
|
||||
items.push(
|
||||
<Button
|
||||
className="Button"
|
||||
loading={this.loading}
|
||||
onclick={() =>
|
||||
app.modal.show(AuthMethodModal, {
|
||||
onsubmit: this.onchange.bind(this, null),
|
||||
})
|
||||
}
|
||||
>
|
||||
{app.translator.trans('flarum-extension-manager.admin.auth_config.add_label')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
onchange(oldHost: string | null, type: string, host: string, token: string) {
|
||||
const data = { ...this.setting(type)() };
|
||||
|
||||
if (oldHost) {
|
||||
delete data[oldHost];
|
||||
}
|
||||
|
||||
data[host] = token;
|
||||
|
||||
this.setting(type)(data);
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
import app from 'flarum/admin/app';
|
||||
import type Mithril from 'mithril';
|
||||
import ConfigureJson, { type IConfigureJson } from './ConfigureJson';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import RepositoryModal from './RepositoryModal';
|
||||
|
||||
export type Repository = {
|
||||
type: 'composer' | 'vcs' | 'path';
|
||||
url: string;
|
||||
};
|
||||
|
||||
export default class ConfigureComposer extends ConfigureJson<IConfigureJson> {
|
||||
protected type = 'composer';
|
||||
|
||||
title(): Mithril.Children {
|
||||
return app.translator.trans('flarum-extension-manager.admin.composer.title');
|
||||
}
|
||||
|
||||
className(): string {
|
||||
return 'ConfigureComposer';
|
||||
}
|
||||
|
||||
content(): Mithril.Children {
|
||||
return (
|
||||
<div className="ExtensionManager-SettingsGroups-content">
|
||||
{this.attrs.buildSettingComponent.call(this, {
|
||||
setting: 'minimum-stability',
|
||||
label: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.label'),
|
||||
help: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.help'),
|
||||
type: 'select',
|
||||
options: {
|
||||
stable: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.options.stable'),
|
||||
RC: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.options.rc'),
|
||||
beta: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.options.beta'),
|
||||
alpha: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.options.alpha'),
|
||||
dev: app.translator.trans('flarum-extension-manager.admin.composer.minimum_stability.options.dev'),
|
||||
},
|
||||
})}
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.composer.repositories.label')}</label>
|
||||
<div className="helpText">{app.translator.trans('flarum-extension-manager.admin.composer.repositories.help')}</div>
|
||||
<div className="ConfigureComposer-repositories">
|
||||
{Object.keys(this.setting('repositories')() || {}).map((name) => {
|
||||
const repository = this.setting('repositories')()[name] as Repository;
|
||||
|
||||
return (
|
||||
<div className="ButtonGroup ButtonGroup--full">
|
||||
<Button
|
||||
className="Button"
|
||||
icon={
|
||||
{
|
||||
composer: 'fas fa-cubes',
|
||||
vcs: 'fas fa-code-branch',
|
||||
path: 'fas fa-folder',
|
||||
}[repository.type]
|
||||
}
|
||||
onclick={() =>
|
||||
app.modal.show(RepositoryModal, {
|
||||
name,
|
||||
repository,
|
||||
onsubmit: (repository: Repository, newName: string) => {
|
||||
const repositories = this.setting('repositories')();
|
||||
delete repositories[name];
|
||||
|
||||
this.setting('repositories')(repositories);
|
||||
|
||||
this.onchange(repository, newName);
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
{name} ({repository.type})
|
||||
</Button>
|
||||
<Button
|
||||
className="Button Button--icon"
|
||||
icon="fas fa-trash"
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.composer.delete_repository_label')}
|
||||
onclick={() => {
|
||||
if (confirm(extractText(app.translator.trans('flarum-extension-manager.admin.composer.delete_repository_confirmation')))) {
|
||||
const repositories = { ...this.setting('repositories')() };
|
||||
delete repositories[name];
|
||||
|
||||
this.setting('repositories')(repositories);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
submitButton(): Mithril.Children[] {
|
||||
const items = super.submitButton();
|
||||
|
||||
items.push(
|
||||
<Button className="Button" onclick={() => app.modal.show(RepositoryModal, { onsubmit: this.onchange.bind(this) })}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.composer.add_repository_label')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
onchange(repository: Repository, name: string) {
|
||||
this.setting('repositories')({
|
||||
...this.setting('repositories')(),
|
||||
[name]: repository,
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
import app from 'flarum/admin/app';
|
||||
import type Mithril from 'mithril';
|
||||
import Component, { type ComponentAttrs } from 'flarum/common/Component';
|
||||
import { CommonSettingsItemOptions, type SettingsComponentOptions } from '@flarum/core/src/admin/components/AdminPage';
|
||||
import AdminPage from 'flarum/admin/components/AdminPage';
|
||||
import type ItemList from 'flarum/common/utils/ItemList';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
|
||||
export interface IConfigureJson extends ComponentAttrs {
|
||||
buildSettingComponent: (entry: ((this: this) => Mithril.Children) | SettingsComponentOptions) => Mithril.Children;
|
||||
}
|
||||
|
||||
export default abstract class ConfigureJson<CustomAttrs extends IConfigureJson = IConfigureJson> extends Component<CustomAttrs> {
|
||||
protected settings: Record<string, Stream<any>> = {};
|
||||
protected initialSettings: Record<string, any> | null = null;
|
||||
protected loading: boolean = false;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.submit(true);
|
||||
}
|
||||
|
||||
protected abstract type: string;
|
||||
abstract title(): Mithril.Children;
|
||||
abstract content(): Mithril.Children;
|
||||
|
||||
className(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
view(): Mithril.Children {
|
||||
return (
|
||||
<div className={classList('Form', this.className())}>
|
||||
<label>{this.title()}</label>
|
||||
{this.content()}
|
||||
<div className="Form-group Form--controls">{this.submitButton()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
submitButton(): Mithril.Children[] {
|
||||
return [
|
||||
<Button className="Button Button--primary" loading={this.loading} onclick={() => this.submit(false)} disabled={!this.isDirty()}>
|
||||
{app.translator.trans('core.admin.settings.submit_button')}
|
||||
</Button>,
|
||||
];
|
||||
}
|
||||
|
||||
customSettingComponents(): ItemList<(attributes: CommonSettingsItemOptions) => Mithril.Children> {
|
||||
return AdminPage.prototype.customSettingComponents();
|
||||
}
|
||||
|
||||
setting(key: string) {
|
||||
return this.settings[key] ?? (this.settings[key] = Stream());
|
||||
}
|
||||
|
||||
submit(readOnly: boolean) {
|
||||
this.loading = true;
|
||||
|
||||
const configuration: any = {};
|
||||
|
||||
Object.keys(this.settings).forEach((key) => {
|
||||
configuration[key] = this.settings[key]();
|
||||
});
|
||||
|
||||
app
|
||||
.request({
|
||||
method: 'POST',
|
||||
url: app.forum.attribute('apiUrl') + '/extension-manager/composer',
|
||||
body: {
|
||||
type: this.type,
|
||||
data: readOnly ? null : configuration,
|
||||
},
|
||||
})
|
||||
.then(({ data }: any) => {
|
||||
Object.keys(data).forEach((key) => {
|
||||
this.settings[key] = Stream(data[key]);
|
||||
});
|
||||
|
||||
this.initialSettings = Array.isArray(data) ? {} : data;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
isDirty() {
|
||||
return JSON.stringify(this.initialSettings) !== JSON.stringify(this.settings);
|
||||
}
|
||||
}
|
@ -14,14 +14,14 @@ export default class ControlSection extends Component<ComponentAttrs> {
|
||||
|
||||
view() {
|
||||
return (
|
||||
<div className="ExtensionPage-permissions PackageManager-controlSection">
|
||||
<div className="ExtensionPage-permissions ExtensionManager-controlSection">
|
||||
<div className="ExtensionPage-permissions-header">
|
||||
<div className="container">
|
||||
<h2 className="ExtensionTitle">{app.translator.trans('flarum-package-manager.admin.sections.control.title')}</h2>
|
||||
<h2 className="ExtensionTitle">{app.translator.trans('flarum-extension-manager.admin.sections.control.title')}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container">
|
||||
{app.data['flarum-package-manager.writable_dirs'] ? (
|
||||
{app.data['flarum-extension-manager.writable_dirs'] ? (
|
||||
<>
|
||||
<Installer />
|
||||
<Updater />
|
||||
@ -29,7 +29,7 @@ export default class ControlSection extends Component<ComponentAttrs> {
|
||||
) : (
|
||||
<div className="Form-group">
|
||||
<Alert type="warning" dismissible={false}>
|
||||
{app.translator.trans('flarum-package-manager.admin.file_permissions')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.file_permissions')}
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
@ -10,11 +10,17 @@ import { Extension } from 'flarum/admin/AdminApplication';
|
||||
import { UpdatedPackage } from '../states/ControlSectionState';
|
||||
import WhyNotModal from './WhyNotModal';
|
||||
import Label from './Label';
|
||||
import Dropdown from 'flarum/common/components/Dropdown';
|
||||
|
||||
export interface ExtensionItemAttrs extends ComponentAttrs {
|
||||
extension: Extension;
|
||||
updates: UpdatedPackage;
|
||||
onClickUpdate: CallableFunction;
|
||||
onClickUpdate:
|
||||
| CallableFunction
|
||||
| {
|
||||
soft: CallableFunction;
|
||||
hard: CallableFunction;
|
||||
};
|
||||
whyNotWarning?: boolean;
|
||||
isCore?: boolean;
|
||||
updatable?: boolean;
|
||||
@ -29,43 +35,56 @@ export default class ExtensionItem<Attrs extends ExtensionItemAttrs = ExtensionI
|
||||
return (
|
||||
<div
|
||||
className={classList({
|
||||
'PackageManager-extension': true,
|
||||
'PackageManager-extension--core': isCore,
|
||||
'PackageManager-extension--danger': isDanger,
|
||||
'ExtensionManager-extension': true,
|
||||
'ExtensionManager-extension--core': isCore,
|
||||
'ExtensionManager-extension--danger': isDanger,
|
||||
})}
|
||||
>
|
||||
<div className="PackageManager-extension-icon ExtensionIcon" style={extension.icon}>
|
||||
<div className="ExtensionManager-extension-icon ExtensionIcon" style={extension.icon}>
|
||||
{extension.icon ? icon(extension.icon.name) : ''}
|
||||
</div>
|
||||
<div className="PackageManager-extension-info">
|
||||
<div className="PackageManager-extension-name">{extension.extra['flarum-extension'].title}</div>
|
||||
<div className="PackageManager-extension-version">
|
||||
<span className="PackageManager-extension-version-current">{this.version(updates['version'])}</span>
|
||||
<div className="ExtensionManager-extension-info">
|
||||
<div className="ExtensionManager-extension-name">{extension.extra['flarum-extension'].title}</div>
|
||||
<div className="ExtensionManager-extension-version">
|
||||
<span className="ExtensionManager-extension-version-current">{this.version(updates['version'])}</span>
|
||||
{latestVersion ? (
|
||||
<Label className="PackageManager-extension-version-latest" type={updates['latest-minor'] ? 'success' : 'warning'}>
|
||||
<Label className="ExtensionManager-extension-version-latest" type={updates['latest-minor'] ? 'success' : 'warning'}>
|
||||
{this.version(latestVersion)}
|
||||
</Label>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="PackageManager-extension-controls">
|
||||
{onClickUpdate ? (
|
||||
<Tooltip text={app.translator.trans('flarum-package-manager.admin.extensions.update')}>
|
||||
<div className="ExtensionManager-extension-controls">
|
||||
{onClickUpdate && typeof onClickUpdate === 'function' ? (
|
||||
<Tooltip text={app.translator.trans('flarum-extension-manager.admin.extensions.update')}>
|
||||
<Button
|
||||
icon="fas fa-arrow-alt-circle-up"
|
||||
className="Button Button--icon Button--flat"
|
||||
onclick={onClickUpdate}
|
||||
aria-label={app.translator.trans('flarum-package-manager.admin.extensions.update')}
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.extensions.update')}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : onClickUpdate ? (
|
||||
<Dropdown
|
||||
buttonClassName="Button Button--icon Button--flat"
|
||||
icon="fas fa-arrow-alt-circle-up"
|
||||
label={app.translator.trans('flarum-extension-manager.admin.extensions.update')}
|
||||
>
|
||||
<Button icon="fas fa-arrow-alt-circle-up" className="Button" onclick={onClickUpdate.soft}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.update_soft_label')}
|
||||
</Button>
|
||||
<Button icon="fas fa-arrow-alt-circle-up" className="Button" onclick={onClickUpdate.hard} disabled={!updates['direct-dependency']}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.update_hard_label')}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
) : null}
|
||||
{whyNotWarning ? (
|
||||
<Tooltip text={app.translator.trans('flarum-package-manager.admin.extensions.check_why_it_failed_updating')}>
|
||||
<Tooltip text={app.translator.trans('flarum-extension-manager.admin.extensions.check_why_it_failed_updating')}>
|
||||
<Button
|
||||
icon="fas fa-exclamation-circle"
|
||||
className="Button Button--icon Button--flat Button--danger"
|
||||
onclick={() => app.modal.show(WhyNotModal, { package: extension.name })}
|
||||
aria-label={app.translator.trans('flarum-package-manager.admin.extensions.check_why_it_failed_updating')}
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.extensions.check_why_it_failed_updating')}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
@ -75,6 +94,6 @@ export default class ExtensionItem<Attrs extends ExtensionItemAttrs = ExtensionI
|
||||
}
|
||||
|
||||
version(v: string): string {
|
||||
return 'v' + v.replace('v', '');
|
||||
return v.charAt(0) === 'v' ? v.substring(1) : v;
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,6 @@ import app from 'flarum/admin/app';
|
||||
import Component, { ComponentAttrs } from 'flarum/common/Component';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
import LoadingModal from 'flarum/admin/components/LoadingModal';
|
||||
|
||||
import errorHandler from '../utils/errorHandler';
|
||||
import jumpToQueue from '../utils/jumpToQueue';
|
||||
import { AsyncBackendResponse } from '../shims';
|
||||
|
||||
export interface InstallerAttrs extends ComponentAttrs {}
|
||||
|
||||
@ -24,11 +19,13 @@ export default class Installer extends Component<InstallerAttrs> {
|
||||
|
||||
view(): Mithril.Children {
|
||||
return (
|
||||
<div className="Form-group PackageManager-installer">
|
||||
<label htmlFor="install-extension">{app.translator.trans('flarum-package-manager.admin.extensions.install')}</label>
|
||||
<div className="Form-group ExtensionManager-installer">
|
||||
<label htmlFor="install-extension">{app.translator.trans('flarum-extension-manager.admin.extensions.install')}</label>
|
||||
<p className="helpText">
|
||||
{app.translator.trans('flarum-package-manager.admin.extensions.install_help', {
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.install_help', {
|
||||
extiverse: <a href="https://extiverse.com">extiverse.com</a>,
|
||||
semantic_link: <a href="https://devhints.io/semver" />,
|
||||
code: <code />,
|
||||
})}
|
||||
</p>
|
||||
<div className="FormControl-container">
|
||||
@ -37,10 +34,10 @@ export default class Installer extends Component<InstallerAttrs> {
|
||||
className="Button"
|
||||
icon="fas fa-download"
|
||||
onclick={this.onsubmit.bind(this)}
|
||||
loading={app.packageManager.control.isLoading('extension-install')}
|
||||
disabled={app.packageManager.control.isLoadingOtherThan('extension-install')}
|
||||
loading={app.extensionManager.control.isLoading('extension-install')}
|
||||
disabled={app.extensionManager.control.hasOperationRunning()}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.extensions.proceed')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.proceed')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -54,35 +51,6 @@ export default class Installer extends Component<InstallerAttrs> {
|
||||
}
|
||||
|
||||
onsubmit(): void {
|
||||
app.packageManager.control.setLoading('extension-install');
|
||||
app.modal.show(LoadingModal);
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse & { id: number }>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/extensions`,
|
||||
body: {
|
||||
data: this.data(),
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
const extensionId = response.id;
|
||||
app.alerts.show(
|
||||
{ type: 'success' },
|
||||
app.translator.trans('flarum-package-manager.admin.extensions.successful_install', { extension: extensionId })
|
||||
);
|
||||
window.location.href = `${app.forum.attribute('adminUrl')}#/extension/${extensionId}`;
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.finally(() => {
|
||||
app.packageManager.control.setLoading(null);
|
||||
app.modal.close();
|
||||
m.redraw();
|
||||
});
|
||||
app.extensionManager.control.requirePackage(this.data());
|
||||
}
|
||||
}
|
||||
|
@ -3,16 +3,12 @@ import app from 'flarum/admin/app';
|
||||
import Component, { ComponentAttrs } from 'flarum/common/Component';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import Tooltip from 'flarum/common/components/Tooltip';
|
||||
import LoadingModal from 'flarum/admin/components/LoadingModal';
|
||||
import Alert from 'flarum/common/components/Alert';
|
||||
import RequestError from 'flarum/common/utils/RequestError';
|
||||
|
||||
import { UpdatedPackage, UpdateState } from '../states/ControlSectionState';
|
||||
import errorHandler from '../utils/errorHandler';
|
||||
import WhyNotModal from './WhyNotModal';
|
||||
import ExtensionItem from './ExtensionItem';
|
||||
import { AsyncBackendResponse } from '../shims';
|
||||
import jumpToQueue from '../utils/jumpToQueue';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
|
||||
export interface MajorUpdaterAttrs extends ComponentAttrs {
|
||||
coreUpdate: UpdatedPackage;
|
||||
@ -33,32 +29,39 @@ export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttr
|
||||
view(): Mithril.Children {
|
||||
// @todo move Form-group--danger class to core for reuse
|
||||
return (
|
||||
<div className="Form-group Form-group--danger PackageManager-majorUpdate">
|
||||
<img alt="flarum logo" src={app.forum.attribute('baseUrl') + '/assets/extensions/flarum-package-manager/flarum.svg'} />
|
||||
<label>{app.translator.trans('flarum-package-manager.admin.major_updater.title', { version: this.attrs.coreUpdate['latest-major'] })}</label>
|
||||
<p className="helpText">{app.translator.trans('flarum-package-manager.admin.major_updater.description')}</p>
|
||||
<div className="PackageManager-updaterControls">
|
||||
<Tooltip text={app.translator.trans('flarum-package-manager.admin.major_updater.dry_run_help')}>
|
||||
<div
|
||||
className={classList('Form-group Form-group--danger ExtensionManager-majorUpdate', {
|
||||
'ExtensionManager-majorUpdate--failed': this.updateState.status === 'failure',
|
||||
'ExtensionManager-majorUpdate--incompatibleExtensions': this.updateState.incompatibleExtensions.length,
|
||||
})}
|
||||
>
|
||||
<img alt="flarum logo" src={app.forum.attribute('baseUrl') + '/assets/extensions/flarum-extension-manager/flarum.svg'} />
|
||||
<label>
|
||||
{app.translator.trans('flarum-extension-manager.admin.major_updater.title', { version: this.attrs.coreUpdate['latest-major'] })}
|
||||
</label>
|
||||
<p className="helpText">{app.translator.trans('flarum-extension-manager.admin.major_updater.description')}</p>
|
||||
<div className="ExtensionManager-updaterControls">
|
||||
<Tooltip text={app.translator.trans('flarum-extension-manager.admin.major_updater.dry_run_help')}>
|
||||
<Button
|
||||
className="Button"
|
||||
icon="fas fa-vial"
|
||||
onclick={this.update.bind(this, true)}
|
||||
disabled={app.packageManager.control.isLoadingOtherThan('major-update-dry-run')}
|
||||
disabled={app.extensionManager.control.hasOperationRunning()}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.major_updater.dry_run')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.major_updater.dry_run')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
className="Button Button--danger"
|
||||
icon="fas fa-play"
|
||||
onclick={this.update.bind(this, false)}
|
||||
disabled={app.packageManager.control.isLoadingOtherThan('major-update')}
|
||||
disabled={app.extensionManager.control.hasOperationRunning()}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.major_updater.update')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.major_updater.update')}
|
||||
</Button>
|
||||
</div>
|
||||
{this.updateState.incompatibleExtensions.length ? (
|
||||
<div className="PackageManager-majorUpdate-incompatibleExtensions PackageManager-extensions-grid">
|
||||
<div className="ExtensionManager-majorUpdate-incompatibleExtensions ExtensionManager-extensions-grid">
|
||||
{this.updateState.incompatibleExtensions.map((extension: string) => (
|
||||
<ExtensionItem
|
||||
extension={app.data.extensions[extension.replace('flarum-', '').replace('flarum-ext-', '').replace('/', '-')]}
|
||||
@ -72,20 +75,20 @@ export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttr
|
||||
{this.updateState.status === 'failure' ? (
|
||||
<Alert
|
||||
type="error"
|
||||
className="PackageManager-majorUpdate-failure"
|
||||
className="ExtensionManager-majorUpdate-failure"
|
||||
dismissible={false}
|
||||
controls={[
|
||||
<Button
|
||||
className="Button Button--text PackageManager-majorUpdate-failure-details"
|
||||
className="Button Button--text ExtensionManager-majorUpdate-failure-details"
|
||||
icon="fas fa-question-circle"
|
||||
onclick={() => app.modal.show(WhyNotModal, { package: 'flarum/core' })}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.major_updater.failure.why')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.major_updater.failure.why')}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<p className="PackageManager-majorUpdate-failure-desc">
|
||||
{app.translator.trans('flarum-package-manager.admin.major_updater.failure.desc')}
|
||||
<p className="ExtensionManager-majorUpdate-failure-desc">
|
||||
{app.translator.trans('flarum-extension-manager.admin.major_updater.failure.desc')}
|
||||
</p>
|
||||
</Alert>
|
||||
) : null}
|
||||
@ -94,34 +97,6 @@ export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttr
|
||||
}
|
||||
|
||||
update(dryRun: boolean) {
|
||||
app.packageManager.control.setLoading(dryRun ? 'major-update-dry-run' : 'major-update');
|
||||
app.modal.show(LoadingModal);
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/major-update`,
|
||||
body: {
|
||||
data: { dryRun },
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.update_successful'));
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.catch((e: RequestError) => {
|
||||
app.modal.close();
|
||||
this.updateState.status = 'failure';
|
||||
this.updateState.incompatibleExtensions = e.response?.errors?.pop()?.incompatible_extensions as string[];
|
||||
})
|
||||
.finally(() => {
|
||||
app.packageManager.control.setLoading(null);
|
||||
m.redraw();
|
||||
});
|
||||
app.extensionManager.control.majorUpdate({ dryRun });
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ export default class Pagination extends Component<PaginationAttrs> {
|
||||
return (
|
||||
<nav className="Pagination UserListPage-gridPagination">
|
||||
<Button
|
||||
disabled={!this.attrs.list.hasPrev()}
|
||||
disabled={!this.attrs.list.hasPrev() || app.extensionManager.control.isLoading()}
|
||||
title={app.translator.trans('core.admin.users.pagination.back_button')}
|
||||
onclick={() => this.attrs.list.prev()}
|
||||
icon="fas fa-chevron-left"
|
||||
@ -28,7 +28,7 @@ export default class Pagination extends Component<PaginationAttrs> {
|
||||
})}
|
||||
</span>
|
||||
<Button
|
||||
disabled={!this.attrs.list.hasNext()}
|
||||
disabled={!this.attrs.list.hasNext() || app.extensionManager.control.isLoading()}
|
||||
title={app.translator.trans('core.admin.users.pagination.next_button')}
|
||||
onclick={() => this.attrs.list.next()}
|
||||
icon="fas fa-chevron-right"
|
||||
|
@ -8,6 +8,7 @@ import { Extension } from 'flarum/admin/AdminApplication';
|
||||
import icon from 'flarum/common/helpers/icon';
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import Link from 'flarum/common/components/Link';
|
||||
|
||||
import Label from './Label';
|
||||
import TaskOutputModal from './TaskOutputModal';
|
||||
@ -24,20 +25,21 @@ export default class QueueSection extends Component<{}> {
|
||||
oninit(vnode: Mithril.Vnode<{}, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
app.packageManager.queue.load();
|
||||
app.extensionManager.queue.load();
|
||||
}
|
||||
|
||||
view() {
|
||||
return (
|
||||
<section id="PackageManager-queueSection" className="ExtensionPage-permissions PackageManager-queueSection">
|
||||
<div className="ExtensionPage-permissions-header PackageManager-queueSection-header">
|
||||
<section id="ExtensionManager-queueSection" className="ExtensionPage-permissions ExtensionManager-queueSection">
|
||||
<div className="ExtensionPage-permissions-header ExtensionManager-queueSection-header">
|
||||
<div className="container">
|
||||
<h2 className="ExtensionTitle">{app.translator.trans('flarum-package-manager.admin.sections.queue.title')}</h2>
|
||||
<h2 className="ExtensionTitle">{app.translator.trans('flarum-extension-manager.admin.sections.queue.title')}</h2>
|
||||
<Button
|
||||
className="Button Button--icon"
|
||||
icon="fas fa-sync-alt"
|
||||
onclick={() => app.packageManager.queue.load()}
|
||||
aria-label={app.translator.trans('flarum-package-manager.admin.sections.queue.refresh')}
|
||||
onclick={() => app.extensionManager.queue.load()}
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.sections.queue.refresh')}
|
||||
disabled={app.extensionManager.control.isLoading()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -52,12 +54,12 @@ export default class QueueSection extends Component<{}> {
|
||||
items.add(
|
||||
'operation',
|
||||
{
|
||||
label: extractText(app.translator.trans('flarum-package-manager.admin.sections.queue.columns.operation')),
|
||||
label: extractText(app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.operation')),
|
||||
content: (task) => (
|
||||
<div className="PackageManager-queueTable-operation">
|
||||
<span className="PackageManager-queueTable-operation-icon">{this.operationIcon(task.operation())}</span>
|
||||
<span className="PackageManager-queueTable-operation-name">
|
||||
{app.translator.trans(`flarum-package-manager.admin.sections.queue.operations.${task.operation()}`)}
|
||||
<div className="ExtensionManager-queueTable-operation">
|
||||
<span className="ExtensionManager-queueTable-operation-icon">{this.operationIcon(task.operation())}</span>
|
||||
<span className="ExtensionManager-queueTable-operation-name">
|
||||
{app.translator.trans(`flarum-extension-manager.admin.sections.queue.operations.${task.operation()}`)}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
@ -68,20 +70,20 @@ export default class QueueSection extends Component<{}> {
|
||||
items.add(
|
||||
'package',
|
||||
{
|
||||
label: extractText(app.translator.trans('flarum-package-manager.admin.sections.queue.columns.package')),
|
||||
label: extractText(app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.package')),
|
||||
content: (task) => {
|
||||
const extension: Extension | null = app.data.extensions[task.package()?.replace(/(\/flarum-|\/flarum-ext-|\/)/g, '-')];
|
||||
|
||||
return extension ? (
|
||||
<div className="PackageManager-queueTable-package">
|
||||
<div className="PackageManager-queueTable-package-icon ExtensionIcon" style={extension.icon}>
|
||||
<Link className="ExtensionManager-queueTable-package" href={app.route('extension', { id: extension.id })}>
|
||||
<div className="ExtensionManager-queueTable-package-icon ExtensionIcon" style={extension.icon}>
|
||||
{!!extension.icon && icon(extension.icon.name)}
|
||||
</div>
|
||||
<div className="PackageManager-queueTable-package-details">
|
||||
<span className="PackageManager-queueTable-package-title">{extension.extra['flarum-extension'].title}</span>
|
||||
<span className="PackageManager-queueTable-package-name">{task.package()}</span>
|
||||
<div className="ExtensionManager-queueTable-package-details">
|
||||
<span className="ExtensionManager-queueTable-package-title">{extension.extra['flarum-extension'].title}</span>
|
||||
<span className="ExtensionManager-queueTable-package-name">{task.package()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
task.package()
|
||||
);
|
||||
@ -93,14 +95,17 @@ export default class QueueSection extends Component<{}> {
|
||||
items.add(
|
||||
'status',
|
||||
{
|
||||
label: extractText(app.translator.trans('flarum-package-manager.admin.sections.queue.columns.status')),
|
||||
label: extractText(app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.status')),
|
||||
content: (task) => (
|
||||
<Label
|
||||
className="PackageManager-queueTable-status"
|
||||
type={{ running: 'neutral', failure: 'error', pending: 'warning', success: 'success' }[task.status()]}
|
||||
>
|
||||
{app.translator.trans(`flarum-package-manager.admin.sections.queue.statuses.${task.status()}`)}
|
||||
</Label>
|
||||
<>
|
||||
<Label
|
||||
className="ExtensionManager-queueTable-status"
|
||||
type={{ running: 'neutral', failure: 'error', pending: 'warning', success: 'success' }[task.status()]}
|
||||
>
|
||||
{app.translator.trans(`flarum-extension-manager.admin.sections.queue.statuses.${task.status()}`)}
|
||||
</Label>
|
||||
{['pending', 'running'].includes(task.status()) && <LoadingIndicator size="small" display="inline" />}
|
||||
</>
|
||||
),
|
||||
},
|
||||
70
|
||||
@ -109,10 +114,10 @@ export default class QueueSection extends Component<{}> {
|
||||
items.add(
|
||||
'elapsedTime',
|
||||
{
|
||||
label: extractText(app.translator.trans('flarum-package-manager.admin.sections.queue.columns.elapsed_time')),
|
||||
label: extractText(app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.elapsed_time')),
|
||||
content: (task) =>
|
||||
!task.startedAt() ? (
|
||||
app.translator.trans('flarum-package-manager.admin.sections.queue.task_just_started')
|
||||
!task.startedAt() || !task.finishedAt() ? (
|
||||
app.translator.trans('flarum-extension-manager.admin.sections.queue.task_just_started')
|
||||
) : (
|
||||
<Tooltip text={`${dayjs(task.startedAt()).format('LL LTS')} ${dayjs(task.finishedAt()).format('LL LTS')}`}>
|
||||
<span>{humanDuration(task.startedAt(), task.finishedAt())}</span>
|
||||
@ -125,7 +130,7 @@ export default class QueueSection extends Component<{}> {
|
||||
items.add(
|
||||
'memoryUsed',
|
||||
{
|
||||
label: extractText(app.translator.trans('flarum-package-manager.admin.sections.queue.columns.peak_memory_used')),
|
||||
label: extractText(app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.peak_memory_used')),
|
||||
content: (task) => <span>{task.peakMemoryUsed()}</span>,
|
||||
},
|
||||
60
|
||||
@ -134,15 +139,16 @@ export default class QueueSection extends Component<{}> {
|
||||
items.add(
|
||||
'details',
|
||||
{
|
||||
label: extractText(app.translator.trans('flarum-package-manager.admin.sections.queue.columns.details')),
|
||||
label: extractText(app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.details')),
|
||||
content: (task) => (
|
||||
<Button
|
||||
className="Button Button--icon Table-controls-item"
|
||||
icon="fas fa-file-alt"
|
||||
aria-label={app.translator.trans('flarum-package-manager.admin.sections.queue.columns.details')}
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.sections.queue.columns.details')}
|
||||
// @todo fix in core
|
||||
// @ts-ignore
|
||||
onclick={() => app.modal.show(TaskOutputModal, { task })}
|
||||
disabled={['pending', 'running'].includes(task.status())}
|
||||
/>
|
||||
),
|
||||
className: 'Table-controls',
|
||||
@ -154,21 +160,21 @@ export default class QueueSection extends Component<{}> {
|
||||
}
|
||||
|
||||
queueTable() {
|
||||
const tasks = app.packageManager.queue.getItems();
|
||||
const tasks = app.extensionManager.queue.getItems();
|
||||
|
||||
if (!tasks) {
|
||||
return <LoadingIndicator />;
|
||||
}
|
||||
|
||||
if (tasks && !tasks.length) {
|
||||
return <h3 className="ExtensionPage-subHeader">{app.translator.trans('flarum-package-manager.admin.sections.queue.none')}</h3>;
|
||||
return <h3 className="ExtensionPage-subHeader">{app.translator.trans('flarum-extension-manager.admin.sections.queue.none')}</h3>;
|
||||
}
|
||||
|
||||
const columns = this.columns();
|
||||
|
||||
return (
|
||||
<>
|
||||
<table className="Table PackageManager-queueTable">
|
||||
<table className="Table ExtensionManager-queueTable">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.toArray().map((item, index) => (
|
||||
@ -193,7 +199,7 @@ export default class QueueSection extends Component<{}> {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Pagination list={app.packageManager.queue} />
|
||||
<Pagination list={app.extensionManager.queue} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,77 @@
|
||||
import Modal, { IInternalModalAttrs } from 'flarum/common/components/Modal';
|
||||
import Mithril from 'mithril';
|
||||
import app from 'flarum/admin/app';
|
||||
import Select from 'flarum/common/components/Select';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import { type Repository } from './ConfigureComposer';
|
||||
|
||||
export interface IRepositoryModalAttrs extends IInternalModalAttrs {
|
||||
onsubmit: (repository: Repository, key: string) => void;
|
||||
name?: string;
|
||||
repository?: Repository;
|
||||
}
|
||||
|
||||
export default class RepositoryModal<CustomAttrs extends IRepositoryModalAttrs = IRepositoryModalAttrs> extends Modal<CustomAttrs> {
|
||||
protected name!: Stream<string>;
|
||||
protected repository!: Stream<Repository>;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.name = Stream(this.attrs.name || '');
|
||||
this.repository = Stream(this.attrs.repository || { type: 'composer', url: '' });
|
||||
}
|
||||
|
||||
className(): string {
|
||||
return 'RepositoryModal Modal--small';
|
||||
}
|
||||
|
||||
title(): Mithril.Children {
|
||||
const context = this.attrs.repository ? 'edit' : 'add';
|
||||
return app.translator.trans(`flarum-extension-manager.admin.composer.${context}_repository_label`);
|
||||
}
|
||||
|
||||
content(): Mithril.Children {
|
||||
const types = {
|
||||
composer: app.translator.trans('flarum-extension-manager.admin.composer.repositories.types.composer'),
|
||||
vcs: app.translator.trans('flarum-extension-manager.admin.composer.repositories.types.vcs'),
|
||||
path: app.translator.trans('flarum-extension-manager.admin.composer.repositories.types.path'),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.composer.repositories.add_modal.name_label')}</label>
|
||||
<input className="FormControl" bidi={this.name} />
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.composer.repositories.add_modal.type_label')}</label>
|
||||
<Select
|
||||
options={types}
|
||||
value={this.repository().type}
|
||||
onchange={(value: 'composer' | 'vcs' | 'path') => this.repository({ ...this.repository(), type: value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.composer.repositories.add_modal.url')}</label>
|
||||
<input
|
||||
className="FormControl"
|
||||
onchange={(e: Event) => this.repository({ ...this.repository(), url: (e.target as HTMLInputElement).value })}
|
||||
value={this.repository().url}
|
||||
/>
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
<Button className="Button Button--primary" onclick={this.submit.bind(this)}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.composer.repositories.add_modal.submit_button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
submit() {
|
||||
this.attrs.onsubmit(this.repository(), this.name());
|
||||
this.hide();
|
||||
}
|
||||
}
|
@ -5,8 +5,45 @@ import ItemList from 'flarum/common/utils/ItemList';
|
||||
|
||||
import QueueSection from './QueueSection';
|
||||
import ControlSection from './ControlSection';
|
||||
import ConfigureComposer from './ConfigureComposer';
|
||||
import Alert from 'flarum/common/components/Alert';
|
||||
import listItems from 'flarum/common/helpers/listItems';
|
||||
import ConfigureAuth from './ConfigureAuth';
|
||||
|
||||
export default class SettingsPage extends ExtensionPage {
|
||||
content() {
|
||||
const settings = app.extensionData.getSettings(this.extension.id);
|
||||
|
||||
const warnings = [app.translator.trans('flarum-extension-manager.admin.settings.access_warning')];
|
||||
|
||||
if (app.data.debugEnabled) warnings.push(app.translator.trans('flarum-extension-manager.admin.settings.debug_mode_warning'));
|
||||
|
||||
return (
|
||||
<div className="ExtensionPage-settings">
|
||||
<div className="container">
|
||||
<div className="Form-group">
|
||||
<Alert className="ExtensionManager-primaryWarning" type="warning" dismissible={false}>
|
||||
<ul>{listItems(warnings)}</ul>
|
||||
</Alert>
|
||||
</div>
|
||||
{settings ? (
|
||||
<div className="ExtensionManager-SettingsGroups">
|
||||
<div className="Form">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.settings.title')}</label>
|
||||
<div className="SettingsGroups-content">{settings.map(this.buildSettingComponent.bind(this))}</div>
|
||||
<div className="Form-group Form--controls">{this.submitButton()}</div>
|
||||
</div>
|
||||
<ConfigureComposer buildSettingComponent={this.buildSettingComponent} />
|
||||
<ConfigureAuth buildSettingComponent={this.buildSettingComponent} />
|
||||
</div>
|
||||
) : (
|
||||
<h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_settings')}</h3>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
sections(vnode: Mithril.VnodeDOM<ExtensionPageAttrs, this>): ItemList<unknown> {
|
||||
const items = super.sections(vnode);
|
||||
|
||||
@ -14,12 +51,17 @@ export default class SettingsPage extends ExtensionPage {
|
||||
|
||||
items.add('control', <ControlSection />, 8);
|
||||
|
||||
if (parseInt(app.data.settings['flarum-package-manager.queue_jobs'])) {
|
||||
if (app.data.settings['flarum-extension-manager.queue_jobs'] !== '0' && app.data.settings['flarum-extension-manager.queue_jobs']) {
|
||||
items.add('queue', <QueueSection />, 5);
|
||||
}
|
||||
|
||||
items.setPriority('permissions', 0);
|
||||
items.remove('permissions');
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
onsaved() {
|
||||
super.onsaved();
|
||||
m.redraw();
|
||||
}
|
||||
}
|
||||
|
@ -12,21 +12,33 @@ export default class TaskOutputModal<CustomAttrs extends TaskOutputModalAttrs =
|
||||
}
|
||||
|
||||
title() {
|
||||
return app.translator.trans(`flarum-package-manager.admin.sections.queue.operations.${this.attrs.task.operation()}`);
|
||||
return app.translator.trans(`flarum-extension-manager.admin.sections.queue.operations.${this.attrs.task.operation()}`);
|
||||
}
|
||||
|
||||
content() {
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="TaskOutputModal-data">
|
||||
{this.attrs.task.status() === 'failure' && (
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.sections.queue.output_modal.guessed_cause')}</label>
|
||||
<div className="FormControl TaskOutputModal-data-guessed-cause">
|
||||
{(this.attrs.task.guessedCause() &&
|
||||
app.translator.trans('flarum-extension-manager.admin.exceptions.guessed_cause.' + this.attrs.task.guessedCause())) ||
|
||||
app.translator.trans('flarum-extension-manager.admin.sections.queue.output_modal.cause_unknown')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-package-manager.admin.sections.queue.output_modal.command')}</label>
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.sections.queue.output_modal.command')}</label>
|
||||
<div className="FormControl TaskOutputModal-data-command">
|
||||
<code>$ composer {this.attrs.task.command()}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-package-manager.admin.sections.queue.output_modal.output')}</label>
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.sections.queue.output_modal.output')}</label>
|
||||
<div className="FormControl TaskOutputModal-data-output">
|
||||
<code>
|
||||
<pre>{this.attrs.task.output()}</pre>
|
||||
|
@ -6,7 +6,6 @@ import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
||||
import MajorUpdater from './MajorUpdater';
|
||||
import ExtensionItem from './ExtensionItem';
|
||||
import { Extension } from 'flarum/admin/AdminApplication';
|
||||
import Alert from 'flarum/common/components/Alert';
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
|
||||
export interface IUpdaterAttrs extends ComponentAttrs {}
|
||||
@ -15,30 +14,30 @@ export type UpdaterLoadingTypes = 'check' | 'minor-update' | 'global-update' | '
|
||||
|
||||
export default class Updater extends Component<IUpdaterAttrs> {
|
||||
view() {
|
||||
const core = app.packageManager.control.coreUpdate;
|
||||
const core = app.extensionManager.control.coreUpdate;
|
||||
|
||||
return [
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-package-manager.admin.updater.updater_title')}</label>
|
||||
<p className="helpText">{app.translator.trans('flarum-package-manager.admin.updater.updater_help')}</p>
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.updater.updater_title')}</label>
|
||||
<p className="helpText">{app.translator.trans('flarum-extension-manager.admin.updater.updater_help')}</p>
|
||||
{this.lastUpdateCheckView()}
|
||||
<div className="PackageManager-updaterControls">{this.controlItems().toArray()}</div>
|
||||
<div className="ExtensionManager-updaterControls">{this.controlItems().toArray()}</div>
|
||||
{this.availableUpdatesView()}
|
||||
</div>,
|
||||
core && core.package['latest-major'] ? (
|
||||
<MajorUpdater coreUpdate={core.package} updateState={app.packageManager.control.lastUpdateRun.major} />
|
||||
<MajorUpdater coreUpdate={core.package} updateState={app.extensionManager.control.lastUpdateRun.major} />
|
||||
) : null,
|
||||
];
|
||||
}
|
||||
|
||||
lastUpdateCheckView() {
|
||||
return (
|
||||
(app.packageManager.control.lastUpdateCheck?.checkedAt && (
|
||||
<p className="PackageManager-lastUpdatedAt">
|
||||
<span className="PackageManager-lastUpdatedAt-label">
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.last_update_checked_at')}
|
||||
(app.extensionManager.control.lastUpdateCheck?.checkedAt && (
|
||||
<p className="ExtensionManager-lastUpdatedAt">
|
||||
<span className="ExtensionManager-lastUpdatedAt-label">
|
||||
{app.translator.trans('flarum-extension-manager.admin.updater.last_update_checked_at')}
|
||||
</span>
|
||||
<span className="PackageManager-lastUpdatedAt-value">{humanTime(app.packageManager.control.lastUpdateCheck.checkedAt)}</span>
|
||||
<span className="ExtensionManager-lastUpdatedAt-value">{humanTime(app.extensionManager.control.lastUpdateCheck.checkedAt)}</span>
|
||||
</p>
|
||||
)) ||
|
||||
null
|
||||
@ -46,33 +45,33 @@ export default class Updater extends Component<IUpdaterAttrs> {
|
||||
}
|
||||
|
||||
availableUpdatesView() {
|
||||
const state = app.packageManager.control;
|
||||
const state = app.extensionManager.control;
|
||||
|
||||
if (app.packageManager.control.isLoading()) {
|
||||
if (app.extensionManager.control.isLoading('check') || app.extensionManager.control.isLoading('global-update')) {
|
||||
return (
|
||||
<div className="PackageManager-extensions">
|
||||
<div className="ExtensionManager-extensions">
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!(state.extensionUpdates.length || state.coreUpdate)) {
|
||||
const hasMinorCoreUpdate = state.coreUpdate && state.coreUpdate.package['latest-minor'];
|
||||
|
||||
if (!(state.extensionUpdates.length || hasMinorCoreUpdate)) {
|
||||
return (
|
||||
<div className="PackageManager-extensions">
|
||||
<Alert type="success" dismissible={false}>
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.up_to_date')}
|
||||
</Alert>
|
||||
<div className="ExtensionManager-extensions">
|
||||
<span className="helpText">{app.translator.trans('flarum-extension-manager.admin.updater.up_to_date')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="PackageManager-extensions">
|
||||
<div className="PackageManager-extensions-grid">
|
||||
{state.coreUpdate ? (
|
||||
<div className="ExtensionManager-extensions">
|
||||
<div className="ExtensionManager-extensions-grid">
|
||||
{hasMinorCoreUpdate ? (
|
||||
<ExtensionItem
|
||||
extension={state.coreUpdate.extension}
|
||||
updates={state.coreUpdate.package}
|
||||
extension={state.coreUpdate!.extension}
|
||||
updates={state.coreUpdate!.package}
|
||||
isCore={true}
|
||||
onClickUpdate={() => state.updateCoreMinor()}
|
||||
whyNotWarning={state.lastUpdateRun.limitedPackages().includes('flarum/core')}
|
||||
@ -82,7 +81,10 @@ export default class Updater extends Component<IUpdaterAttrs> {
|
||||
<ExtensionItem
|
||||
extension={extension}
|
||||
updates={state.packageUpdates[extension.id]}
|
||||
onClickUpdate={() => state.updateExtension(extension)}
|
||||
onClickUpdate={{
|
||||
soft: () => state.updateExtension(extension, 'soft'),
|
||||
hard: () => state.updateExtension(extension, 'hard'),
|
||||
}}
|
||||
whyNotWarning={state.lastUpdateRun.limitedPackages().includes(extension.name)}
|
||||
/>
|
||||
))}
|
||||
@ -99,11 +101,11 @@ export default class Updater extends Component<IUpdaterAttrs> {
|
||||
<Button
|
||||
className="Button"
|
||||
icon="fas fa-sync-alt"
|
||||
onclick={() => app.packageManager.control.checkForUpdates()}
|
||||
loading={app.packageManager.control.isLoading('check')}
|
||||
disabled={app.packageManager.control.isLoadingOtherThan('check')}
|
||||
onclick={() => app.extensionManager.control.checkForUpdates()}
|
||||
loading={app.extensionManager.control.isLoading('check')}
|
||||
disabled={app.extensionManager.control.hasOperationRunning()}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.check_for_updates')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.updater.check_for_updates')}
|
||||
</Button>,
|
||||
100
|
||||
);
|
||||
@ -113,11 +115,11 @@ export default class Updater extends Component<IUpdaterAttrs> {
|
||||
<Button
|
||||
className="Button"
|
||||
icon="fas fa-play"
|
||||
onclick={() => app.packageManager.control.updateGlobally()}
|
||||
loading={app.packageManager.control.isLoading('global-update')}
|
||||
disabled={app.packageManager.control.isLoadingOtherThan('global-update')}
|
||||
onclick={() => app.extensionManager.control.updateGlobally()}
|
||||
loading={app.extensionManager.control.isLoading('global-update')}
|
||||
disabled={app.extensionManager.control.hasOperationRunning()}
|
||||
>
|
||||
{app.translator.trans('flarum-package-manager.admin.updater.run_global_update')}
|
||||
{app.translator.trans('flarum-extension-manager.admin.updater.run_global_update')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
|
@ -24,7 +24,7 @@ export default class WhyNotModal<CustomAttrs extends WhyNotModalAttrs = WhyNotMo
|
||||
}
|
||||
|
||||
title() {
|
||||
return app.translator.trans('flarum-package-manager.admin.why_not_modal.title');
|
||||
return app.translator.trans('flarum-extension-manager.admin.why_not_modal.title');
|
||||
}
|
||||
|
||||
oncreate(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
|
||||
@ -41,7 +41,7 @@ export default class WhyNotModal<CustomAttrs extends WhyNotModalAttrs = WhyNotMo
|
||||
app
|
||||
.request<WhyNotResponse>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/why-not`,
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/why-not`,
|
||||
body: {
|
||||
data: {
|
||||
package: this.attrs.package,
|
||||
|
@ -4,35 +4,30 @@ import ExtensionPage from 'flarum/admin/components/ExtensionPage';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import LoadingModal from 'flarum/admin/components/LoadingModal';
|
||||
import isExtensionEnabled from 'flarum/admin/utils/isExtensionEnabled';
|
||||
import Alert from 'flarum/common/components/Alert';
|
||||
|
||||
import SettingsPage from './components/SettingsPage';
|
||||
import Task from './models/Task';
|
||||
import jumpToQueue from './utils/jumpToQueue';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import { AsyncBackendResponse } from './shims';
|
||||
import PackageManagerState from './states/PackageManagerState';
|
||||
import ExtensionManagerState from './states/ExtensionManagerState';
|
||||
|
||||
app.initializers.add('flarum-package-manager', (app) => {
|
||||
app.store.models['package-manager-tasks'] = Task;
|
||||
app.initializers.add('flarum-extension-manager', (app) => {
|
||||
app.store.models['extension-manager-tasks'] = Task;
|
||||
|
||||
app.packageManager = new PackageManagerState();
|
||||
app.extensionManager = new ExtensionManagerState();
|
||||
|
||||
if (app.data['flarum-extension-manager.using_sync_queue']) {
|
||||
app.data.settings['flarum-extension-manager.queue_jobs'] = '0';
|
||||
}
|
||||
|
||||
app.extensionData
|
||||
.for('flarum-package-manager')
|
||||
.registerSetting(() => (
|
||||
<div className="Form-group">
|
||||
<Alert type="warning" dismissible={false}>
|
||||
{app.translator.trans('flarum-package-manager.admin.settings.access_warning')}
|
||||
</Alert>
|
||||
</div>
|
||||
))
|
||||
.for('flarum-extension-manager')
|
||||
.registerSetting({
|
||||
setting: 'flarum-package-manager.queue_jobs',
|
||||
label: app.translator.trans('flarum-package-manager.admin.settings.queue_jobs'),
|
||||
setting: 'flarum-extension-manager.queue_jobs',
|
||||
label: app.translator.trans('flarum-extension-manager.admin.settings.queue_jobs'),
|
||||
help: m.trust(
|
||||
extractText(
|
||||
app.translator.trans('flarum-package-manager.admin.settings.queue_jobs_help', {
|
||||
app.translator.trans('flarum-extension-manager.admin.settings.queue_jobs_help', {
|
||||
basic_impl_link: 'https://discuss.flarum.org/d/28151-database-queue-the-simplest-queue-even-for-shared-hosting',
|
||||
adv_impl_link: 'https://discuss.flarum.org/d/21873-redis-sessions-cache-queues',
|
||||
php_version: `<strong>${app.data.phpVersion}</strong>`,
|
||||
@ -40,14 +35,19 @@ app.initializers.add('flarum-package-manager', (app) => {
|
||||
})
|
||||
)
|
||||
),
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
disabled: app.data['flarum-package-manager.using_sync_queue'],
|
||||
disabled: app.data['flarum-extension-manager.using_sync_queue'],
|
||||
})
|
||||
.registerSetting({
|
||||
setting: 'flarum-extension-manager.task_retention_days',
|
||||
label: app.translator.trans('flarum-extension-manager.admin.settings.task_retention_days'),
|
||||
help: app.translator.trans('flarum-extension-manager.admin.settings.task_retention_days_help'),
|
||||
type: 'number',
|
||||
})
|
||||
.registerPage(SettingsPage);
|
||||
|
||||
extend(ExtensionPage.prototype, 'topItems', function (items) {
|
||||
if (this.extension.id === 'flarum-package-manager' || isExtensionEnabled(this.extension.id)) {
|
||||
if (this.extension.id === 'flarum-extension-manager' || isExtensionEnabled(this.extension.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -61,14 +61,14 @@ app.initializers.add('flarum-package-manager', (app) => {
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/extensions/${this.extension.id}`,
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/extensions/${this.extension.id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.extensions.successful_remove'));
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-extension-manager.admin.extensions.successful_remove'));
|
||||
window.location = app.forum.attribute('adminUrl');
|
||||
}
|
||||
})
|
||||
@ -77,8 +77,14 @@ app.initializers.add('flarum-package-manager', (app) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.remove')}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Expose compat API
|
||||
import packageManagerCompat from './compat';
|
||||
import { compat } from '@flarum/core/admin';
|
||||
|
||||
Object.assign(compat, packageManagerCompat);
|
||||
|
@ -32,6 +32,10 @@ export default class Task extends Model {
|
||||
return Model.attribute<string>('output').call(this);
|
||||
}
|
||||
|
||||
guessedCause() {
|
||||
return Model.attribute<string>('guessedCause').call(this);
|
||||
}
|
||||
|
||||
createdAt() {
|
||||
return Model.attribute('createdAt', Model.transformDate).call(this);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import PackageManagerState from './states/PackageManagerState';
|
||||
import 'dayjs/plugin/relativeTime';
|
||||
import ExtensionManagerState from './states/ExtensionManagerState';
|
||||
|
||||
export interface AsyncBackendResponse {
|
||||
processing: boolean;
|
||||
@ -6,6 +7,6 @@ export interface AsyncBackendResponse {
|
||||
|
||||
declare module 'flarum/admin/AdminApplication' {
|
||||
export default interface AdminApplication {
|
||||
packageManager: PackageManagerState;
|
||||
extensionManager: ExtensionManagerState;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import errorHandler from '../utils/errorHandler';
|
||||
import jumpToQueue from '../utils/jumpToQueue';
|
||||
import { Extension } from 'flarum/admin/AdminApplication';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import RequestError from 'flarum/common/utils/RequestError';
|
||||
|
||||
export type UpdatedPackage = {
|
||||
name: string;
|
||||
@ -16,6 +17,8 @@ export type UpdatedPackage = {
|
||||
'latest-minor': string | null;
|
||||
'latest-major': string | null;
|
||||
'latest-status': string;
|
||||
'required-as': string;
|
||||
'direct-dependency': boolean;
|
||||
description: string;
|
||||
};
|
||||
|
||||
@ -43,7 +46,7 @@ export type LastUpdateRun = {
|
||||
limitedPackages: () => string[];
|
||||
};
|
||||
|
||||
export type LoadingTypes = UpdaterLoadingTypes | InstallerLoadingTypes | MajorUpdaterLoadingTypes;
|
||||
export type LoadingTypes = UpdaterLoadingTypes | InstallerLoadingTypes | MajorUpdaterLoadingTypes | 'queued-action';
|
||||
|
||||
export type CoreUpdate = {
|
||||
package: UpdatedPackage;
|
||||
@ -58,7 +61,7 @@ export default class ControlSectionState {
|
||||
public extensionUpdates!: Extension[];
|
||||
public coreUpdate: CoreUpdate | null = null;
|
||||
get lastUpdateRun(): LastUpdateRun {
|
||||
const lastUpdateRun = JSON.parse(app.data.settings['flarum-package-manager.last_update_run']) as LastUpdateRun;
|
||||
const lastUpdateRun = JSON.parse(app.data.settings['flarum-extension-manager.last_update_run']) as LastUpdateRun;
|
||||
|
||||
lastUpdateRun.limitedPackages = () => [
|
||||
...lastUpdateRun.major.limitedPackages,
|
||||
@ -70,7 +73,7 @@ export default class ControlSectionState {
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.lastUpdateCheck = JSON.parse(app.data.settings['flarum-package-manager.last_update_check']) as LastUpdateCheck;
|
||||
this.lastUpdateCheck = JSON.parse(app.data.settings['flarum-extension-manager.last_update_check']) as LastUpdateCheck;
|
||||
this.extensionUpdates = this.formatExtensionUpdates(this.lastUpdateCheck);
|
||||
this.coreUpdate = this.formatCoreUpdate(this.lastUpdateCheck);
|
||||
}
|
||||
@ -79,21 +82,53 @@ export default class ControlSectionState {
|
||||
return (name && this.loading === name) || (!name && this.loading !== null);
|
||||
}
|
||||
|
||||
isLoadingOtherThan(name: LoadingTypes): boolean {
|
||||
return this.loading !== null && this.loading !== name;
|
||||
hasOperationRunning(): boolean {
|
||||
return this.isLoading() || app.extensionManager.queue.hasPending();
|
||||
}
|
||||
|
||||
setLoading(name: LoadingTypes): void {
|
||||
this.loading = name;
|
||||
}
|
||||
|
||||
requirePackage(data: any) {
|
||||
app.extensionManager.control.setLoading('extension-install');
|
||||
app.modal.show(LoadingModal);
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse & { id: number }>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/extensions`,
|
||||
body: {
|
||||
data,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
const extensionId = response.id;
|
||||
app.alerts.show(
|
||||
{ type: 'success' },
|
||||
app.translator.trans('flarum-extension-manager.admin.extensions.successful_install', { extension: extensionId })
|
||||
);
|
||||
window.location.href = `${app.forum.attribute('adminUrl')}#/extension/${extensionId}`;
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.finally(() => {
|
||||
app.modal.close();
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
checkForUpdates() {
|
||||
this.setLoading('check');
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | LastUpdateCheck>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/check-for-updates`,
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/check-for-updates`,
|
||||
})
|
||||
.then((response) => {
|
||||
if ((response as AsyncBackendResponse).processing) {
|
||||
@ -102,51 +137,55 @@ export default class ControlSectionState {
|
||||
this.lastUpdateCheck = response as LastUpdateCheck;
|
||||
this.extensionUpdates = this.formatExtensionUpdates(response as LastUpdateCheck);
|
||||
this.coreUpdate = this.formatCoreUpdate(response as LastUpdateCheck);
|
||||
this.setLoading(null);
|
||||
m.redraw();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.finally(() => {
|
||||
this.setLoading(null);
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
updateCoreMinor() {
|
||||
if (confirm(extractText(app.translator.trans('flarum-package-manager.admin.minor_update_confirmation.content')))) {
|
||||
if (confirm(extractText(app.translator.trans('flarum-extension-manager.admin.minor_update_confirmation.content')))) {
|
||||
app.modal.show(LoadingModal);
|
||||
this.setLoading('minor-update');
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/minor-update`,
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/minor-update`,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.update_successful'));
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-extension-manager.admin.update_successful'));
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.finally(() => {
|
||||
this.setLoading(null);
|
||||
app.modal.close();
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateExtension(extension: Extension) {
|
||||
updateExtension(extension: Extension, updateMode: 'soft' | 'hard') {
|
||||
app.modal.show(LoadingModal);
|
||||
this.setLoading('extension-update');
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'PATCH',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/extensions/${extension.id}`,
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/extensions/${extension.id}`,
|
||||
body: {
|
||||
data: {
|
||||
updateMode,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
@ -154,7 +193,7 @@ export default class ControlSectionState {
|
||||
} else {
|
||||
app.alerts.show(
|
||||
{ type: 'success' },
|
||||
app.translator.trans('flarum-package-manager.admin.extensions.successful_update', {
|
||||
app.translator.trans('flarum-extension-manager.admin.extensions.successful_update', {
|
||||
extension: extension.extra['flarum-extension'].title,
|
||||
})
|
||||
);
|
||||
@ -163,7 +202,6 @@ export default class ControlSectionState {
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.finally(() => {
|
||||
this.setLoading(null);
|
||||
app.modal.close();
|
||||
m.redraw();
|
||||
});
|
||||
@ -176,19 +214,18 @@ export default class ControlSectionState {
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/package-manager/global-update`,
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/global-update`,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.updater.global_update_successful'));
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-extension-manager.admin.updater.global_update_successful'));
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.finally(() => {
|
||||
this.setLoading(null);
|
||||
app.modal.close();
|
||||
m.redraw();
|
||||
});
|
||||
@ -226,14 +263,46 @@ export default class ControlSectionState {
|
||||
version: app.data.settings.version,
|
||||
icon: {
|
||||
// @ts-ignore
|
||||
backgroundImage: `url(${app.data.resources[0]['attributes']['baseUrl']}/assets/extensions/flarum-package-manager/flarum.svg`,
|
||||
backgroundImage: `url(${app.data.resources[0]['attributes']['baseUrl']}/assets/extensions/flarum-extension-manager/flarum.svg`,
|
||||
},
|
||||
extra: {
|
||||
'flarum-extension': {
|
||||
title: extractText(app.translator.trans('flarum-package-manager.admin.updater.flarum')),
|
||||
title: extractText(app.translator.trans('flarum-extension-manager.admin.updater.flarum')),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
majorUpdate({ dryRun }: { dryRun: boolean }) {
|
||||
app.extensionManager.control.setLoading(dryRun ? 'major-update-dry-run' : 'major-update');
|
||||
app.modal.show(LoadingModal);
|
||||
const updateState = this.lastUpdateRun.major;
|
||||
|
||||
app
|
||||
.request<AsyncBackendResponse | null>({
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/extension-manager/major-update`,
|
||||
body: {
|
||||
data: { dryRun },
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.processing) {
|
||||
jumpToQueue();
|
||||
} else {
|
||||
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-extension-manager.admin.update_successful'));
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(errorHandler)
|
||||
.catch((e: RequestError) => {
|
||||
app.modal.close();
|
||||
updateState.status = 'failure';
|
||||
updateState.incompatibleExtensions = e.response?.errors?.pop()?.incompatible_extensions as string[];
|
||||
})
|
||||
.finally(() => {
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import QueueState from './QueueState';
|
||||
import ControlSectionState from './ControlSectionState';
|
||||
|
||||
export default class PackageManagerState {
|
||||
export default class ExtensionManagerState {
|
||||
public queue: QueueState = new QueueState();
|
||||
public control: ControlSectionState = new ControlSectionState();
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user