mirror of
https://github.com/flarum/core.git
synced 2025-08-17 22:01:44 +02:00
Compare commits
28 Commits
sm/package
...
sqlite-dri
Author | SHA1 | Date | |
---|---|---|---|
|
2b917372a7 | ||
|
270188b5b0 | ||
|
9149ecc7aa | ||
|
5fc2bb5eb6 | ||
|
24f3a6829f | ||
|
bf523b2325 | ||
|
e771b908d5 | ||
|
721e2eae3d | ||
|
3fbe05fd18 | ||
|
8f29b7af82 | ||
|
734f4a150c | ||
|
0186ca909e | ||
|
1aa7806244 | ||
|
e3350543af | ||
|
d400dcbc2f | ||
|
430709bf5b | ||
|
f784f48906 | ||
|
3a34136e36 | ||
|
fb1703cd9b | ||
|
b58fec7ead | ||
|
537f97a07a | ||
|
c1be00e79a | ||
|
91b89bc698 | ||
|
278617a10d | ||
|
f793e5b8f8 | ||
|
01598555a9 | ||
|
5399c86a1b | ||
|
74ce4cf1a7 |
7
.github/workflows/REUSABLE_backend.yml
vendored
7
.github/workflows/REUSABLE_backend.yml
vendored
@@ -31,6 +31,7 @@ on:
|
||||
description: Versions of PHP to test with. Should be array of strings encoded as JSON array
|
||||
type: string
|
||||
required: false
|
||||
# Keep PHP versions synced with build-install-packages.yml
|
||||
default: '["8.1", "8.2", "8.3"]'
|
||||
|
||||
php_extensions:
|
||||
@@ -51,10 +52,16 @@ on:
|
||||
required: false
|
||||
default: error_reporting=E_ALL
|
||||
|
||||
secrets:
|
||||
composer_auth:
|
||||
description: The Composer auth tokens to use for private packages.
|
||||
required: false
|
||||
|
||||
env:
|
||||
COMPOSER_ROOT_VERSION: dev-main
|
||||
# `inputs.composer_directory` defaults to `inputs.backend_directory`
|
||||
FLARUM_TEST_TMP_DIR_LOCAL: tests/integration/tmp
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
4
.github/workflows/REUSABLE_frontend.yml
vendored
4
.github/workflows/REUSABLE_frontend.yml
vendored
@@ -90,11 +90,15 @@ on:
|
||||
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:
|
||||
|
29
.github/workflows/build-install-packages.yml
vendored
Normal file
29
.github/workflows/build-install-packages.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Build Install Packages
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
env:
|
||||
VERSION: ${{ github.event.release.tag_name }}
|
||||
PHP_VERSIONS: '8.1 8.2 8.3'
|
||||
INSTALL_PACKAGES_INPUTS: '{ "flarum_version": "{0}", "php_versions": "{1}" }'
|
||||
|
||||
jobs:
|
||||
delay:
|
||||
name: Wait for packagist to publish new packages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: sleep 30m
|
||||
|
||||
build:
|
||||
name: Build Installation Packages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger build in flarum/installation-packages
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
workflow: Build Flarum Install Packages
|
||||
repo: flarum/installation-packages
|
||||
token: ${{ secrets.PACKAGES_BUILD_TOKEN }}
|
||||
inputs: ${{ format(env.INSTALL_PACKAGES_INPUTS, env.VERSION, env.PHP_VERSIONS) }}
|
@@ -38,3 +38,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",
|
||||
@@ -70,7 +70,7 @@
|
||||
"Flarum\\Lock\\Tests\\": "extensions/lock/tests",
|
||||
"Flarum\\Mentions\\Tests\\": "extensions/mentions/tests",
|
||||
"Flarum\\Nicknames\\Tests\\": "extensions/nicknames/tests",
|
||||
"Flarum\\PackageManager\\Tests\\": "extensions/package-manager/tests",
|
||||
"Flarum\\ExtensionManager\\Tests\\": "extensions/package-manager/tests",
|
||||
"Flarum\\Pusher\\Tests\\": "extensions/pusher/tests",
|
||||
"Flarum\\Statistics\\Tests\\": "extensions/statistics/tests",
|
||||
"Flarum\\Sticky\\Tests\\": "extensions/sticky/tests",
|
||||
@@ -94,7 +94,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",
|
||||
@@ -112,6 +112,7 @@
|
||||
"dflydev/fig-cookies": "^3.0",
|
||||
"doctrine/dbal": "^3.6.2",
|
||||
"dragonmantank/cron-expression": "^3.3",
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"franzl/whoops-middleware": "2.0",
|
||||
"guzzlehttp/guzzle": "*",
|
||||
"illuminate/bus": "^10.0",
|
||||
@@ -130,7 +131,7 @@
|
||||
"illuminate/support": "^10.0",
|
||||
"illuminate/validation": "^10.0",
|
||||
"illuminate/view": "^10.0",
|
||||
"intervention/image": "^2.7.2",
|
||||
"intervention/image": "^3.2",
|
||||
"jenssegers/agent": "^2.6",
|
||||
"laminas/laminas-diactoros": "^3.0",
|
||||
"laminas/laminas-httphandlerrunner": "^2.6",
|
||||
@@ -168,7 +169,7 @@
|
||||
"mockery/mockery": "^1.5",
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"phpstan/phpstan": "^1.10.0",
|
||||
"nunomaduro/larastan": "^2.6",
|
||||
"larastan/larastan": "^2.7",
|
||||
"symfony/var-dumper": "^6.3"
|
||||
},
|
||||
"config": {
|
||||
|
@@ -47,7 +47,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,
|
||||
|
@@ -10,19 +10,23 @@
|
||||
namespace Flarum\Approval\Tests\integration;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\User\User;
|
||||
|
||||
trait InteractsWithUnapprovedContent
|
||||
{
|
||||
protected function prepareUnapprovedDatabaseContent()
|
||||
{
|
||||
$this->prepareDatabase([
|
||||
'users' => [
|
||||
User::class => [
|
||||
['id' => 1, 'username' => 'Muralf', 'email' => 'muralf@machine.local', 'is_email_confirmed' => 1],
|
||||
$this->normalUser(),
|
||||
['id' => 3, 'username' => 'acme', 'email' => 'acme@machine.local', 'is_email_confirmed' => 1],
|
||||
['id' => 4, 'username' => 'luceos', 'email' => 'luceos@machine.local', 'is_email_confirmed' => 1],
|
||||
],
|
||||
'discussions' => [
|
||||
Discussion::class => [
|
||||
['id' => 1, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 4, 'first_post_id' => 1, 'comment_count' => 1, 'is_approved' => 1, 'is_private' => 0],
|
||||
['id' => 2, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 4, 'first_post_id' => 2, 'comment_count' => 1, 'is_approved' => 0, 'is_private' => 1],
|
||||
['id' => 3, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 4, 'first_post_id' => 3, 'comment_count' => 1, 'is_approved' => 0, 'is_private' => 1],
|
||||
@@ -31,7 +35,7 @@ trait InteractsWithUnapprovedContent
|
||||
['id' => 6, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 4, 'first_post_id' => 6, 'comment_count' => 1, 'is_approved' => 0, 'is_private' => 1],
|
||||
['id' => 7, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 4, 'first_post_id' => 7, 'comment_count' => 1, 'is_approved' => 1, 'is_private' => 0],
|
||||
],
|
||||
'posts' => [
|
||||
Post::class => [
|
||||
['id' => 1, 'discussion_id' => 1, 'user_id' => 4, 'type' => 'comment', 'content' => '<t><p>Text</p></t>', 'is_private' => 0, 'is_approved' => 1, 'number' => 1],
|
||||
['id' => 2, 'discussion_id' => 2, 'user_id' => 4, 'type' => 'comment', 'content' => '<t><p>Text</p></t>', 'is_private' => 0, 'is_approved' => 1, 'number' => 1],
|
||||
['id' => 3, 'discussion_id' => 3, 'user_id' => 4, 'type' => 'comment', 'content' => '<t><p>Text</p></t>', 'is_private' => 0, 'is_approved' => 1, 'number' => 1],
|
||||
@@ -45,7 +49,7 @@ trait InteractsWithUnapprovedContent
|
||||
['id' => 10, 'discussion_id' => 7, 'user_id' => 4, 'type' => 'comment', 'content' => '<t><p>Text</p></t>', 'is_private' => 0, 'is_approved' => 1, 'number' => 4],
|
||||
['id' => 11, 'discussion_id' => 7, 'user_id' => 4, 'type' => 'comment', 'content' => '<t><p>Text</p></t>', 'is_private' => 1, 'is_approved' => 0, 'number' => 5],
|
||||
],
|
||||
'groups' => [
|
||||
Group::class => [
|
||||
['id' => 4, 'name_singular' => 'Acme', 'name_plural' => 'Acme', 'is_hidden' => 0]
|
||||
],
|
||||
'group_user' => [
|
||||
|
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
@@ -2,6 +2,7 @@ import { extend } from 'flarum/common/extend';
|
||||
import TextEditorButton from 'flarum/common/components/TextEditorButton';
|
||||
import KeyboardNavigatable from 'flarum/common/utils/KeyboardNavigatable';
|
||||
import Tooltip from 'flarum/common/components/Tooltip';
|
||||
import AutocompleteReader from 'flarum/common/utils/AutocompleteReader';
|
||||
|
||||
import AutocompleteDropdown from './fragments/AutocompleteDropdown';
|
||||
import getEmojiIconCode from './helpers/getEmojiIconCode';
|
||||
@@ -40,15 +41,7 @@ export default function addComposerAutocomplete() {
|
||||
extend('flarum/common/components/TextEditor', 'buildEditorParams', function (params) {
|
||||
const emojiKeys = Object.keys(emojiMap);
|
||||
|
||||
let relEmojiStart;
|
||||
let absEmojiStart;
|
||||
let typed;
|
||||
|
||||
const applySuggestion = (replacement) => {
|
||||
this.attrs.composer.editor.replaceBeforeCursor(absEmojiStart - 1, replacement + ' ');
|
||||
|
||||
this.emojiDropdown.hide();
|
||||
};
|
||||
const autocompleteReader = new AutocompleteReader(':');
|
||||
|
||||
params.inputListeners.push(() => {
|
||||
const selection = this.attrs.composer.editor.getSelectionRange();
|
||||
@@ -57,29 +50,20 @@ export default function addComposerAutocomplete() {
|
||||
|
||||
if (selection[1] - cursor > 0) return;
|
||||
|
||||
// Search backwards from the cursor for an ':' symbol. If we find
|
||||
// one and followed by a whitespace, we will want to show the
|
||||
// autocomplete dropdown!
|
||||
const lastChunk = this.attrs.composer.editor.getLastNChars(15);
|
||||
absEmojiStart = 0;
|
||||
for (let i = lastChunk.length - 1; i >= 0; i--) {
|
||||
const character = lastChunk.substr(i, 1);
|
||||
// check what user typed, emoji names only contains alphanumeric,
|
||||
// underline, '+' and '-'
|
||||
if (!/[a-z0-9]|\+|\-|_|\:/.test(character)) break;
|
||||
// make sure ':' preceded by a whitespace or newline
|
||||
if (character === ':' && (i == 0 || /\s/.test(lastChunk.substr(i - 1, 1)))) {
|
||||
relEmojiStart = i + 1;
|
||||
absEmojiStart = cursor - lastChunk.length + i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const autocompleting = autocompleteReader.check(lastChunk, cursor, /[a-z0-9]|\+|\-|_|\:/);
|
||||
|
||||
this.emojiDropdown.hide();
|
||||
this.emojiDropdown.active = false;
|
||||
|
||||
if (absEmojiStart) {
|
||||
typed = lastChunk.substring(relEmojiStart).toLowerCase();
|
||||
if (autocompleting) {
|
||||
const typed = autocompleting.typed;
|
||||
const emojiDropdown = this.emojiDropdown;
|
||||
|
||||
const applySuggestion = (replacement) => {
|
||||
this.attrs.composer.editor.replaceBeforeCursor(autocompleting.absoluteStart - 1, replacement + ' ');
|
||||
this.emojiDropdown.hide();
|
||||
};
|
||||
|
||||
const makeSuggestion = function ({ emoji, name, code }) {
|
||||
return (
|
||||
@@ -88,7 +72,7 @@ export default function addComposerAutocomplete() {
|
||||
key={emoji}
|
||||
onclick={() => applySuggestion(emoji)}
|
||||
onmouseenter={function () {
|
||||
this.emojiDropdown.setIndex($(this).parent().index() - 1);
|
||||
emojiDropdown.setIndex($(this).parent().index() - 1);
|
||||
}}
|
||||
>
|
||||
<img alt={emoji} className="emoji" draggable="false" loading="lazy" src={`${cdn}72x72/${code}.png`} title={name} />
|
||||
@@ -152,7 +136,7 @@ export default function addComposerAutocomplete() {
|
||||
m.render(this.$('.ComposerBody-emojiDropdownContainer')[0], this.emojiDropdown.render());
|
||||
|
||||
this.emojiDropdown.show();
|
||||
const coordinates = this.attrs.composer.editor.getCaretCoordinates(absEmojiStart);
|
||||
const coordinates = this.attrs.composer.editor.getCaretCoordinates(autocompleting.absoluteStart);
|
||||
const width = this.emojiDropdown.$().outerWidth();
|
||||
const height = this.emojiDropdown.$().outerHeight();
|
||||
const parent = this.emojiDropdown.$().offsetParent();
|
||||
|
@@ -32,6 +32,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;
|
||||
}
|
||||
|
@@ -29,10 +29,10 @@ class FlagSerializer extends AbstractSerializer
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => $model->type,
|
||||
'reason' => $model->reason,
|
||||
'type' => $model->type,
|
||||
'reason' => $model->reason,
|
||||
'reasonDetail' => $model->reason_detail,
|
||||
'createdAt' => $this->formatDate($model->created_at),
|
||||
'createdAt' => $this->formatDate($model->created_at),
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -9,9 +9,12 @@
|
||||
|
||||
namespace Flarum\Flags\Tests\integration\api\flags;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ListTest extends TestCase
|
||||
@@ -28,7 +31,7 @@ class ListTest extends TestCase
|
||||
$this->extension('flarum-flags');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'users' => [
|
||||
User::class => [
|
||||
$this->normalUser(),
|
||||
[
|
||||
'id' => 3,
|
||||
@@ -44,10 +47,10 @@ class ListTest extends TestCase
|
||||
'group_permission' => [
|
||||
['group_id' => Group::MODERATOR_ID, 'permission' => 'discussion.viewFlags'],
|
||||
],
|
||||
'discussions' => [
|
||||
Discussion::class => [
|
||||
['id' => 1, 'title' => '', 'user_id' => 1, 'comment_count' => 1],
|
||||
],
|
||||
'posts' => [
|
||||
Post::class => [
|
||||
['id' => 1, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
['id' => 2, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
['id' => 3, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
|
@@ -9,9 +9,13 @@
|
||||
|
||||
namespace Flarum\Flags\Tests\integration\api\flags;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Tags\Tag;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ListWithTagsTest extends TestCase
|
||||
@@ -29,13 +33,13 @@ class ListWithTagsTest extends TestCase
|
||||
$this->extension('flarum-tags');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'tags' => [
|
||||
Tag::class => [
|
||||
['id' => 1, 'name' => 'Unrestricted', 'slug' => '1', 'position' => 0, 'parent_id' => null],
|
||||
['id' => 2, 'name' => 'Mods can view discussions', 'slug' => '2', 'position' => 0, 'parent_id' => null, 'is_restricted' => true],
|
||||
['id' => 3, 'name' => 'Mods can view flags', 'slug' => '3', 'position' => 0, 'parent_id' => null, 'is_restricted' => true],
|
||||
['id' => 4, 'name' => 'Mods can view discussions and flags', 'slug' => '4', 'position' => 0, 'parent_id' => null, 'is_restricted' => true],
|
||||
],
|
||||
'users' => [
|
||||
User::class => [
|
||||
$this->normalUser(),
|
||||
[
|
||||
'id' => 3,
|
||||
@@ -55,7 +59,7 @@ class ListWithTagsTest extends TestCase
|
||||
['group_id' => Group::MODERATOR_ID, 'permission' => 'tag4.viewDiscussions'],
|
||||
['group_id' => Group::MODERATOR_ID, 'permission' => 'tag4.discussion.viewFlags'],
|
||||
],
|
||||
'discussions' => [
|
||||
Discussion::class => [
|
||||
['id' => 1, 'title' => 'no tags', 'user_id' => 1, 'comment_count' => 1],
|
||||
['id' => 2, 'title' => 'has tags where mods can view discussions but not flags', 'user_id' => 1, 'comment_count' => 1],
|
||||
['id' => 3, 'title' => 'has tags where mods can view flags but not discussions', 'user_id' => 1, 'comment_count' => 1],
|
||||
@@ -68,7 +72,7 @@ class ListWithTagsTest extends TestCase
|
||||
['discussion_id' => 4, 'tag_id' => 4],
|
||||
['discussion_id' => 5, 'tag_id' => 1],
|
||||
],
|
||||
'posts' => [
|
||||
Post::class => [
|
||||
// From regular ListTest
|
||||
['id' => 1, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
['id' => 2, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
|
||||
|
@@ -10,9 +10,13 @@
|
||||
namespace Flarum\Likes\Tests\integration\api;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Post\CommentPost;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Flarum\User\User;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class LikePostTest extends TestCase
|
||||
@@ -26,21 +30,21 @@ class LikePostTest extends TestCase
|
||||
$this->extension('flarum-likes');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'users' => [
|
||||
User::class => [
|
||||
['id' => 1, 'username' => 'Muralf', 'email' => 'muralf@machine.local', 'is_email_confirmed' => 1],
|
||||
$this->normalUser(),
|
||||
['id' => 3, 'username' => 'Acme', 'email' => 'acme@machine.local', 'is_email_confirmed' => 1],
|
||||
],
|
||||
'discussions' => [
|
||||
Discussion::class => [
|
||||
['id' => 1, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 2],
|
||||
],
|
||||
'posts' => [
|
||||
Post::class => [
|
||||
['id' => 1, 'number' => 1, 'discussion_id' => 1, 'created_at' => Carbon::now(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>something</p></t>'],
|
||||
['id' => 3, 'number' => 2, 'discussion_id' => 1, 'created_at' => Carbon::now(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>something</p></t>'],
|
||||
['id' => 5, 'number' => 3, 'discussion_id' => 1, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'discussionRenamed', 'content' => '<t><p>something</p></t>'],
|
||||
['id' => 6, 'number' => 4, 'discussion_id' => 1, 'created_at' => Carbon::now(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>something</p></t>'],
|
||||
],
|
||||
'groups' => [
|
||||
Group::class => [
|
||||
['id' => 5, 'name_singular' => 'Acme', 'name_plural' => 'Acme', 'is_hidden' => 0],
|
||||
['id' => 6, 'name_singular' => 'Acme1', 'name_plural' => 'Acme1', 'is_hidden' => 0]
|
||||
],
|
||||
|
@@ -10,10 +10,13 @@
|
||||
namespace Flarum\Likes\Tests\integration\api\discussions;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Likes\Api\LoadLikesRelationship;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ListPostsTest extends TestCase
|
||||
@@ -30,13 +33,13 @@ class ListPostsTest extends TestCase
|
||||
$this->extension('flarum-likes');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'discussions' => [
|
||||
Discussion::class => [
|
||||
['id' => 100, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'user_id' => 1, 'first_post_id' => 101, 'comment_count' => 1],
|
||||
],
|
||||
'posts' => [
|
||||
Post::class => [
|
||||
['id' => 101, 'discussion_id' => 100, 'created_at' => Carbon::now(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>text</p></t>'],
|
||||
],
|
||||
'users' => [
|
||||
User::class => [
|
||||
$this->normalUser(),
|
||||
['id' => 102, 'username' => 'user102', 'email' => '102@machine.local', 'is_email_confirmed' => 1],
|
||||
['id' => 103, 'username' => 'user103', 'email' => '103@machine.local', 'is_email_confirmed' => 1],
|
||||
|
2
extensions/lock/js/dist/admin.js
generated
vendored
2
extensions/lock/js/dist/admin.js
generated
vendored
@@ -1,2 +1,2 @@
|
||||
(()=>{var e={n:r=>{var o=r&&r.__esModule?()=>r.default:()=>r;return e.d(o,{a:o}),o},d:(r,o)=>{for(var t in o)e.o(o,t)&&!e.o(r,t)&&Object.defineProperty(r,t,{enumerable:!0,get:o[t]})},o:(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},r={};(()=>{"use strict";e.r(r),e.d(r,{extend:()=>l});const o=flarum.reg.get("core","admin/app");var t=e.n(o);const n=flarum.reg.get("core","common/extenders");var a=e.n(n);class s{pattern(){return"is:locked"}toFilter(e,r){return{[(r?"-":"")+"locked"]:!0}}filterKey(){return"locked"}fromFilter(e,r){return"".concat(r?"-":"","is:locked")}}flarum.reg.add("flarum-lock","common/query/discussions/LockedGambit",s);const l=[(new(a().Search)).gambit("discussions",s)];t().initializers.add("lock",(()=>{t().extensionData.for("flarum-lock").registerPermission({icon:"fas fa-lock",label:t().translator.trans("flarum-lock.admin.permissions.lock_discussions_label"),permission:"discussion.lock"},"moderate",95)}))})(),module.exports=r})();
|
||||
(()=>{var e={n:r=>{var o=r&&r.__esModule?()=>r.default:()=>r;return e.d(o,{a:o}),o},d:(r,o)=>{for(var a in o)e.o(o,a)&&!e.o(r,a)&&Object.defineProperty(r,a,{enumerable:!0,get:o[a]})},o:(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},r={};(()=>{"use strict";e.r(r),e.d(r,{extend:()=>m});const o=flarum.reg.get("core","admin/app");var a=e.n(o);const t=flarum.reg.get("core","common/extenders");var s=e.n(t);const n=flarum.reg.get("core","common/query/IGambit"),l=flarum.reg.get("core","common/app");var i=e.n(l);class c extends n.BooleanGambit{key(){return i().translator.trans("flarum-lock.lib.gambits.discussions.locked.key",{},!0)}filterKey(){return"locked"}}flarum.reg.add("flarum-lock","common/query/discussions/LockedGambit",c);const m=[(new(s().Search)).gambit("discussions",c)];a().initializers.add("lock",(()=>{a().extensionData.for("flarum-lock").registerPermission({icon:"fas fa-lock",label:a().translator.trans("flarum-lock.admin.permissions.lock_discussions_label"),permission:"discussion.lock"},"moderate",95)}))})(),module.exports=r})();
|
||||
//# sourceMappingURL=admin.js.map
|
2
extensions/lock/js/dist/admin.js.map
generated
vendored
2
extensions/lock/js/dist/admin.js.map
generated
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"admin.js","mappings":"MACA,IAAIA,EAAsB,CCA1BA,EAAyBC,IACxB,IAAIC,EAASD,GAAUA,EAAOE,WAC7B,IAAOF,EAAiB,QACxB,IAAM,EAEP,OADAD,EAAoBI,EAAEF,EAAQ,CAAEG,EAAGH,IAC5BA,CAAM,ECLdF,EAAwB,CAACM,EAASC,KACjC,IAAI,IAAIC,KAAOD,EACXP,EAAoBS,EAAEF,EAAYC,KAASR,EAAoBS,EAAEH,EAASE,IAC5EE,OAAOC,eAAeL,EAASE,EAAK,CAAEI,YAAY,EAAMC,IAAKN,EAAWC,IAE1E,ECNDR,EAAwB,CAACc,EAAKC,IAAUL,OAAOM,UAAUC,eAAeC,KAAKJ,EAAKC,GCClFf,EAAyBM,IACH,oBAAXa,QAA0BA,OAAOC,aAC1CV,OAAOC,eAAeL,EAASa,OAAOC,YAAa,CAAEC,MAAO,WAE7DX,OAAOC,eAAeL,EAAS,aAAc,CAAEe,OAAO,GAAO,G,qDCL9D,MAAM,EAA+BC,OAAOC,IAAIV,IAAI,OAAQ,a,aCA5D,MAAM,EAA+BS,OAAOC,IAAIV,IAAI,OAAQ,oB,aCA7C,MAAMW,EACnBC,UACE,MAAO,WACT,CACAC,SAASC,EAAUC,GAEjB,MAAO,CACL,EAFWA,EAAS,IAAM,IAAM,WAEzB,EAEX,CACAC,YACE,MAAO,QACT,CACAC,WAAWT,EAAOO,GAChB,MAAO,GAAGG,OAAOH,EAAS,IAAM,GAAI,YACtC,EAEFN,OAAOC,IAAIS,IAAI,cAAe,wCAAyCR,GCfvE,UAAgB,IAAI,aACnBS,OAAO,cAAeT,ICDvB,qBAAqB,QAAQ,KAC3B,sBAAsB,eAAeU,mBAAmB,CACtDC,KAAM,cACNC,MAAO,qBAAqB,wDAC5BC,WAAY,mBACX,WAAY,GAAG,G","sources":["webpack://@flarum/lock/webpack/bootstrap","webpack://@flarum/lock/webpack/runtime/compat get default export","webpack://@flarum/lock/webpack/runtime/define property getters","webpack://@flarum/lock/webpack/runtime/hasOwnProperty shorthand","webpack://@flarum/lock/webpack/runtime/make namespace object","webpack://@flarum/lock/external root \"flarum.reg.get('core', 'admin/app')\"","webpack://@flarum/lock/external root \"flarum.reg.get('core', 'common/extenders')\"","webpack://@flarum/lock/./src/common/query/discussions/LockedGambit.ts","webpack://@flarum/lock/./src/common/extend.ts","webpack://@flarum/lock/./src/admin/index.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'admin/app');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/extenders');","export default class LockedGambit {\n pattern() {\n return 'is:locked';\n }\n toFilter(_matches, negate) {\n const key = (negate ? '-' : '') + 'locked';\n return {\n [key]: true\n };\n }\n filterKey() {\n return 'locked';\n }\n fromFilter(value, negate) {\n return \"\".concat(negate ? '-' : '', \"is:locked\");\n }\n}\nflarum.reg.add('flarum-lock', 'common/query/discussions/LockedGambit', LockedGambit);","import Extend from 'flarum/common/extenders';\nimport LockedGambit from './query/discussions/LockedGambit';\nexport default [new Extend.Search() //\n.gambit('discussions', LockedGambit)];","import app from 'flarum/admin/app';\nexport { default as extend } from './extend';\napp.initializers.add('lock', () => {\n app.extensionData.for('flarum-lock').registerPermission({\n icon: 'fas fa-lock',\n label: app.translator.trans('flarum-lock.admin.permissions.lock_discussions_label'),\n permission: 'discussion.lock'\n }, 'moderate', 95);\n});"],"names":["__webpack_require__","module","getter","__esModule","d","a","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Symbol","toStringTag","value","flarum","reg","LockedGambit","pattern","toFilter","_matches","negate","filterKey","fromFilter","concat","add","gambit","registerPermission","icon","label","permission"],"sourceRoot":""}
|
||||
{"version":3,"file":"admin.js","mappings":"MACA,IAAIA,EAAsB,CCA1BA,EAAyBC,IACxB,IAAIC,EAASD,GAAUA,EAAOE,WAC7B,IAAOF,EAAiB,QACxB,IAAM,EAEP,OADAD,EAAoBI,EAAEF,EAAQ,CAAEG,EAAGH,IAC5BA,CAAM,ECLdF,EAAwB,CAACM,EAASC,KACjC,IAAI,IAAIC,KAAOD,EACXP,EAAoBS,EAAEF,EAAYC,KAASR,EAAoBS,EAAEH,EAASE,IAC5EE,OAAOC,eAAeL,EAASE,EAAK,CAAEI,YAAY,EAAMC,IAAKN,EAAWC,IAE1E,ECNDR,EAAwB,CAACc,EAAKC,IAAUL,OAAOM,UAAUC,eAAeC,KAAKJ,EAAKC,GCClFf,EAAyBM,IACH,oBAAXa,QAA0BA,OAAOC,aAC1CV,OAAOC,eAAeL,EAASa,OAAOC,YAAa,CAAEC,MAAO,WAE7DX,OAAOC,eAAeL,EAAS,aAAc,CAAEe,OAAO,GAAO,G,qDCL9D,MAAM,EAA+BC,OAAOC,IAAIV,IAAI,OAAQ,a,aCA5D,MAAM,EAA+BS,OAAOC,IAAIV,IAAI,OAAQ,oB,aCA5D,MAAM,EAA+BS,OAAOC,IAAIV,IAAI,OAAQ,wBCAtD,EAA+BS,OAAOC,IAAIV,IAAI,OAAQ,c,aCE7C,MAAMW,UAAqB,EAAAC,cACxCjB,MACE,OAAO,qBAAqB,iDAAkD,CAAC,GAAG,EACpF,CACAkB,YACE,MAAO,QACT,EAEFJ,OAAOC,IAAII,IAAI,cAAe,wCAAyCH,GCRvE,UAAgB,IAAI,aACnBI,OAAO,cAAeJ,ICDvB,qBAAqB,QAAQ,KAC3B,sBAAsB,eAAeK,mBAAmB,CACtDC,KAAM,cACNC,MAAO,qBAAqB,wDAC5BC,WAAY,mBACX,WAAY,GAAG,G","sources":["webpack://@flarum/lock/webpack/bootstrap","webpack://@flarum/lock/webpack/runtime/compat get default export","webpack://@flarum/lock/webpack/runtime/define property getters","webpack://@flarum/lock/webpack/runtime/hasOwnProperty shorthand","webpack://@flarum/lock/webpack/runtime/make namespace object","webpack://@flarum/lock/external root \"flarum.reg.get('core', 'admin/app')\"","webpack://@flarum/lock/external root \"flarum.reg.get('core', 'common/extenders')\"","webpack://@flarum/lock/external root \"flarum.reg.get('core', 'common/query/IGambit')\"","webpack://@flarum/lock/external root \"flarum.reg.get('core', 'common/app')\"","webpack://@flarum/lock/./src/common/query/discussions/LockedGambit.ts","webpack://@flarum/lock/./src/common/extend.ts","webpack://@flarum/lock/./src/admin/index.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'admin/app');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/extenders');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/query/IGambit');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/app');","import { BooleanGambit } from 'flarum/common/query/IGambit';\nimport app from 'flarum/common/app';\nexport default class LockedGambit extends BooleanGambit {\n key() {\n return app.translator.trans('flarum-lock.lib.gambits.discussions.locked.key', {}, true);\n }\n filterKey() {\n return 'locked';\n }\n}\nflarum.reg.add('flarum-lock', 'common/query/discussions/LockedGambit', LockedGambit);","import Extend from 'flarum/common/extenders';\nimport LockedGambit from './query/discussions/LockedGambit';\nexport default [new Extend.Search() //\n.gambit('discussions', LockedGambit)];","import app from 'flarum/admin/app';\nexport { default as extend } from './extend';\napp.initializers.add('lock', () => {\n app.extensionData.for('flarum-lock').registerPermission({\n icon: 'fas fa-lock',\n label: app.translator.trans('flarum-lock.admin.permissions.lock_discussions_label'),\n permission: 'discussion.lock'\n }, 'moderate', 95);\n});"],"names":["__webpack_require__","module","getter","__esModule","d","a","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Symbol","toStringTag","value","flarum","reg","LockedGambit","BooleanGambit","filterKey","add","gambit","registerPermission","icon","label","permission"],"sourceRoot":""}
|
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 e=t&&t.__esModule?()=>t.default:()=>t;return o.d(e,{a:e}),e},d:(t,e)=>{for(var n in e)o.o(e,n)&&!o.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},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:()=>S});const e=flarum.reg.get("core","common/extend"),n=flarum.reg.get("core","forum/app");var r=o.n(n);const s=flarum.reg.get("core","forum/components/Notification");var c=o.n(s);class a extends(c()){icon(){return"fas fa-lock"}href(){const o=this.attrs.notification;return r().route.discussion(o.subject(),o.content().postNumber)}content(){return r().translator.trans("flarum-lock.forum.notifications.discussion_locked_text",{user:this.attrs.notification.fromUser()})}excerpt(){return null}}flarum.reg.add("flarum-lock","forum/components/DiscussionLockedNotification",a);const i=flarum.reg.get("core","common/models/Discussion");var l=o.n(i);const u=flarum.reg.get("core","common/components/Badge");var d=o.n(u);const f=flarum.reg.get("core","forum/utils/DiscussionControls");var k=o.n(f);const g=flarum.reg.get("core","forum/components/DiscussionPage");var p=o.n(g);const b=flarum.reg.get("core","common/components/Button");var _=o.n(b);const y=flarum.reg.get("core","common/extenders");var v=o.n(y);const L=flarum.reg.get("core","forum/components/EventPost");var h=o.n(L);class x extends(h()){icon(){return this.attrs.post.content().locked?"fas fa-lock":"fas fa-unlock"}descriptionKey(){return this.attrs.post.content().locked?"flarum-lock.forum.post_stream.discussion_locked_text":"flarum-lock.forum.post_stream.discussion_unlocked_text"}}flarum.reg.add("flarum-lock","forum/components/DiscussionLockedPost",x);class P{pattern(){return"is:locked"}toFilter(o,t){return{[(t?"-":"")+"locked"]:!0}}filterKey(){return"locked"}fromFilter(o,t){return"".concat(t?"-":"","is:locked")}}flarum.reg.add("flarum-lock","common/query/discussions/LockedGambit",P);const S=[(new(v().Search)).gambit("discussions",P),(new(v().PostTypes)).add("discussionLocked",x),new(v().Model)(l()).attribute("isLocked").attribute("canLock")];r().initializers.add("flarum-lock",(()=>{r().notificationComponents.discussionLocked=a,(0,e.extend)(l().prototype,"badges",(function(o){this.isLocked()&&o.add("locked",m(d(),{type:"locked",label:r().translator.trans("flarum-lock.forum.badge.locked_tooltip"),icon:"fas fa-lock"}))})),(0,e.extend)(k(),"moderationControls",(function(o,t){t.canLock()&&o.add("lock",m(_(),{icon:"fas fa-lock",onclick:this.lockAction.bind(t)},r().translator.trans("flarum-lock.forum.discussion_controls.".concat(t.isLocked()?"unlock":"lock","_button"))))})),k().lockAction=function(){this.save({isLocked:!this.isLocked()}).then((()=>{r().current.matches(p())&&r().current.get("stream").update(),m.redraw()}))},(0,e.extend)("flarum/forum/components/NotificationGrid","notificationTypes",(function(o){o.add("discussionLocked",{name:"discussionLocked",icon:"fas fa-lock",label:r().translator.trans("flarum-lock.forum.settings.notify_discussion_locked_label")})}))}))})(),module.exports=t})();
|
||||
(()=>{var o={n:e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},d:(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o:(o,e)=>Object.prototype.hasOwnProperty.call(o,e),r:o=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(o,"__esModule",{value:!0})}},e={};(()=>{"use strict";o.r(e),o.d(e,{extend:()=>D});const t=flarum.reg.get("core","common/extend"),n=flarum.reg.get("core","forum/app");var r=o.n(n);const s=flarum.reg.get("core","forum/components/Notification");var c=o.n(s);class a extends(c()){icon(){return"fas fa-lock"}href(){const o=this.attrs.notification;return r().route.discussion(o.subject(),o.content().postNumber)}content(){return r().translator.trans("flarum-lock.forum.notifications.discussion_locked_text",{user:this.attrs.notification.fromUser()})}excerpt(){return null}}flarum.reg.add("flarum-lock","forum/components/DiscussionLockedNotification",a);const i=flarum.reg.get("core","common/models/Discussion");var l=o.n(i);const u=flarum.reg.get("core","common/components/Badge");var d=o.n(u);const f=flarum.reg.get("core","forum/utils/DiscussionControls");var k=o.n(f);const g=flarum.reg.get("core","forum/components/DiscussionPage");var p=o.n(g);const b=flarum.reg.get("core","common/components/Button");var y=o.n(b);const _=flarum.reg.get("core","common/extenders");var v=o.n(_);const x=flarum.reg.get("core","forum/components/EventPost");var L=o.n(x);class h extends(L()){icon(){return this.attrs.post.content().locked?"fas fa-lock":"fas fa-unlock"}descriptionKey(){return this.attrs.post.content().locked?"flarum-lock.forum.post_stream.discussion_locked_text":"flarum-lock.forum.post_stream.discussion_unlocked_text"}}flarum.reg.add("flarum-lock","forum/components/DiscussionLockedPost",h);const P=flarum.reg.get("core","common/query/IGambit"),S=flarum.reg.get("core","common/app");var j=o.n(S);class w extends P.BooleanGambit{key(){return j().translator.trans("flarum-lock.lib.gambits.discussions.locked.key",{},!0)}filterKey(){return"locked"}}flarum.reg.add("flarum-lock","common/query/discussions/LockedGambit",w);const D=[(new(v().Search)).gambit("discussions",w),(new(v().PostTypes)).add("discussionLocked",h),new(v().Model)(l()).attribute("isLocked").attribute("canLock")];r().initializers.add("flarum-lock",(()=>{r().notificationComponents.discussionLocked=a,(0,t.extend)(l().prototype,"badges",(function(o){this.isLocked()&&o.add("locked",m(d(),{type:"locked",label:r().translator.trans("flarum-lock.forum.badge.locked_tooltip"),icon:"fas fa-lock"}))})),(0,t.extend)(k(),"moderationControls",(function(o,e){e.canLock()&&o.add("lock",m(y(),{icon:"fas fa-lock",onclick:this.lockAction.bind(e)},r().translator.trans("flarum-lock.forum.discussion_controls.".concat(e.isLocked()?"unlock":"lock","_button"))))})),k().lockAction=function(){this.save({isLocked:!this.isLocked()}).then((()=>{r().current.matches(p())&&r().current.get("stream").update(),m.redraw()}))},(0,t.extend)("flarum/forum/components/NotificationGrid","notificationTypes",(function(o){o.add("discussionLocked",{name:"discussionLocked",icon:"fas fa-lock",label:r().translator.trans("flarum-lock.forum.settings.notify_discussion_locked_label")})}))}))})(),module.exports=e})();
|
||||
//# 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
@@ -1,23 +1,12 @@
|
||||
import IGambit from 'flarum/common/query/IGambit';
|
||||
import { BooleanGambit } from 'flarum/common/query/IGambit';
|
||||
import app from 'flarum/common/app';
|
||||
|
||||
export default class LockedGambit implements IGambit {
|
||||
pattern(): string {
|
||||
return 'is:locked';
|
||||
}
|
||||
|
||||
toFilter(_matches: string[], negate: boolean): Record<string, any> {
|
||||
const key = (negate ? '-' : '') + 'locked';
|
||||
|
||||
return {
|
||||
[key]: true,
|
||||
};
|
||||
export default class LockedGambit extends BooleanGambit {
|
||||
key(): string {
|
||||
return app.translator.trans('flarum-lock.lib.gambits.discussions.locked.key', {}, true);
|
||||
}
|
||||
|
||||
filterKey(): string {
|
||||
return 'locked';
|
||||
}
|
||||
|
||||
fromFilter(value: string, negate: boolean): string {
|
||||
return `${negate ? '-' : ''}is:locked`;
|
||||
}
|
||||
}
|
||||
|
@@ -35,3 +35,12 @@ flarum-lock:
|
||||
# These translations are used in the Settings page.
|
||||
settings:
|
||||
notify_discussion_locked_label: Someone locks a discussion I started
|
||||
|
||||
# Translations in this namespace are used by the forum and admin interfaces.
|
||||
lib:
|
||||
|
||||
# These translations are used by gambits. Gambit keys must be in snake_case, no spaces.
|
||||
gambits:
|
||||
discussions:
|
||||
locked:
|
||||
key: locked
|
||||
|
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
@@ -2,6 +2,8 @@ import app from 'flarum/forum/app';
|
||||
import { extend } from 'flarum/common/extend';
|
||||
import TextEditorButton from 'flarum/common/components/TextEditorButton';
|
||||
import KeyboardNavigatable from 'flarum/common/utils/KeyboardNavigatable';
|
||||
import AutocompleteReader from 'flarum/common/utils/AutocompleteReader';
|
||||
import { throttle } from 'flarum/common/utils/throttleDebounce';
|
||||
|
||||
import AutocompleteDropdown from './fragments/AutocompleteDropdown';
|
||||
import MentionableModels from './mentionables/MentionableModels';
|
||||
@@ -9,6 +11,7 @@ import MentionableModels from './mentionables/MentionableModels';
|
||||
export default function addComposerAutocomplete() {
|
||||
extend('flarum/common/components/TextEditor', 'onbuild', function () {
|
||||
this.mentionsDropdown = new AutocompleteDropdown();
|
||||
this.searchMentions = throttle(250, (mentionables, buildSuggestions) => mentionables.search().then(buildSuggestions));
|
||||
const $editor = this.$('.TextEditor-editor').wrap('<div class="ComposerBody-mentionsWrapper"></div>');
|
||||
|
||||
this.navigator = new KeyboardNavigatable();
|
||||
@@ -24,21 +27,8 @@ export default function addComposerAutocomplete() {
|
||||
});
|
||||
|
||||
extend('flarum/common/components/TextEditor', 'buildEditorParams', function (params) {
|
||||
let relMentionStart;
|
||||
let absMentionStart;
|
||||
let matchTyped;
|
||||
|
||||
let mentionables = new MentionableModels({
|
||||
onmouseenter: function () {
|
||||
this.mentionsDropdown.setIndex($(this).parent().index());
|
||||
},
|
||||
onclick: (replacement) => {
|
||||
this.attrs.composer.editor.replaceBeforeCursor(absMentionStart - 1, replacement + ' ');
|
||||
|
||||
this.mentionsDropdown.hide();
|
||||
},
|
||||
});
|
||||
|
||||
const suggestionsInputListener = () => {
|
||||
const selection = this.attrs.composer.editor.getSelectionRange();
|
||||
|
||||
@@ -46,30 +36,27 @@ export default function addComposerAutocomplete() {
|
||||
|
||||
if (selection[1] - cursor > 0) return;
|
||||
|
||||
// Search backwards from the cursor for a mention triggering symbol. If we find one,
|
||||
// we will want to show the correct autocomplete dropdown!
|
||||
// Check classes implementing the IMentionableModel interface to see triggering symbols.
|
||||
const lastChunk = this.attrs.composer.editor.getLastNChars(30);
|
||||
absMentionStart = 0;
|
||||
let activeFormat = null;
|
||||
for (let i = lastChunk.length - 1; i >= 0; i--) {
|
||||
const character = lastChunk.substr(i, 1);
|
||||
activeFormat = app.mentionFormats.get(character);
|
||||
const autocompleteReader = new AutocompleteReader((character) => !!(activeFormat = app.mentionFormats.get(character)));
|
||||
const autocompleting = autocompleteReader.check(this.attrs.composer.editor.getLastNChars(30), cursor, /\S+/);
|
||||
|
||||
if (activeFormat && (i === 0 || /\s/.test(lastChunk.substr(i - 1, 1)))) {
|
||||
relMentionStart = i + 1;
|
||||
absMentionStart = cursor - lastChunk.length + i + 1;
|
||||
mentionables.init(activeFormat.makeMentionables());
|
||||
break;
|
||||
}
|
||||
}
|
||||
const mentionsDropdown = this.mentionsDropdown;
|
||||
let mentionables = new MentionableModels({
|
||||
onmouseenter: function () {
|
||||
mentionsDropdown.setIndex($(this).parent().index());
|
||||
},
|
||||
onclick: (replacement) => {
|
||||
this.attrs.composer.editor.replaceBeforeCursor(autocompleting.absoluteStart - 1, replacement + ' ');
|
||||
this.mentionsDropdown.hide();
|
||||
},
|
||||
});
|
||||
|
||||
this.mentionsDropdown.hide();
|
||||
this.mentionsDropdown.active = false;
|
||||
|
||||
if (absMentionStart) {
|
||||
const typed = lastChunk.substring(relMentionStart).toLowerCase();
|
||||
matchTyped = activeFormat.queryFromTyped(typed);
|
||||
if (autocompleting) {
|
||||
mentionables.init(activeFormat.makeMentionables());
|
||||
matchTyped = activeFormat.queryFromTyped(autocompleting.typed);
|
||||
|
||||
if (!matchTyped) return;
|
||||
|
||||
@@ -85,7 +72,7 @@ export default function addComposerAutocomplete() {
|
||||
m.render(this.$('.ComposerBody-mentionsDropdownContainer')[0], this.mentionsDropdown.render());
|
||||
|
||||
this.mentionsDropdown.show();
|
||||
const coordinates = this.attrs.composer.editor.getCaretCoordinates(absMentionStart);
|
||||
const coordinates = this.attrs.composer.editor.getCaretCoordinates(autocompleting.absoluteStart);
|
||||
const width = this.mentionsDropdown.$().outerWidth();
|
||||
const height = this.mentionsDropdown.$().outerHeight();
|
||||
const parent = this.mentionsDropdown.$().offsetParent();
|
||||
@@ -118,7 +105,7 @@ export default function addComposerAutocomplete() {
|
||||
this.mentionsDropdown.setIndex(0);
|
||||
this.mentionsDropdown.$().scrollTop(0);
|
||||
|
||||
mentionables.search()?.then(buildSuggestions);
|
||||
this.searchMentions(mentionables, buildSuggestions);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -2,7 +2,6 @@ import type MentionableModel from './MentionableModel';
|
||||
import type Model from 'flarum/common/Model';
|
||||
import type Mithril from 'mithril';
|
||||
import MentionsDropdownItem from '../components/MentionsDropdownItem';
|
||||
import { throttle } from 'flarum/common/utils/throttleDebounce';
|
||||
|
||||
export default class MentionableModels {
|
||||
protected mentionables?: MentionableModel[];
|
||||
@@ -33,7 +32,7 @@ export default class MentionableModels {
|
||||
* Don't send API calls searching for models until at least 2 characters have been typed.
|
||||
* This focuses the mention results on models already loaded.
|
||||
*/
|
||||
public readonly search = throttle(250, async (): Promise<void> => {
|
||||
public readonly search = async (): Promise<void> => {
|
||||
if (!this.typed || this.typed.length <= 1) return;
|
||||
|
||||
const typedLower = this.typed.toLowerCase();
|
||||
@@ -51,7 +50,7 @@ export default class MentionableModels {
|
||||
this.searched.push(typedLower);
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
public matches(mentionable: MentionableModel, model: Model): boolean {
|
||||
return mentionable.matches(model, this.typed?.toLowerCase() || '');
|
||||
|
@@ -12,6 +12,7 @@ namespace Flarum\Mentions\Formatter;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Http\SlugManager;
|
||||
use Flarum\Locale\TranslatorInterface;
|
||||
use Flarum\Post\Post;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use s9e\TextFormatter\Renderer;
|
||||
use s9e\TextFormatter\Utils;
|
||||
@@ -24,12 +25,22 @@ class FormatPostMentions
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(Renderer $renderer, mixed $context, ?string $xml, Request $request = null): string
|
||||
/**
|
||||
* Configure rendering for post mentions.
|
||||
*
|
||||
* @param \s9e\TextFormatter\Renderer $renderer
|
||||
* @param mixed $context
|
||||
* @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;
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Mentions\Formatter;
|
||||
|
||||
use Flarum\Locale\TranslatorInterface;
|
||||
use Flarum\Post\Post;
|
||||
use s9e\TextFormatter\Utils;
|
||||
|
||||
class UnparsePostMentions
|
||||
@@ -31,10 +32,11 @@ class UnparsePostMentions
|
||||
*/
|
||||
protected function updatePostMentionTags(mixed $context, string $xml): string
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
@@ -10,8 +10,10 @@
|
||||
namespace Flarum\Mentions\Tests\integration\api;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Post\CommentPost;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Flarum\User\User;
|
||||
@@ -30,14 +32,14 @@ class GroupMentionsTest extends TestCase
|
||||
$this->extension('flarum-mentions');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'users' => [
|
||||
User::class => [
|
||||
['id' => 3, 'username' => 'potato', 'email' => 'potato@machine.local', 'is_email_confirmed' => 1],
|
||||
['id' => 4, 'username' => 'toby', 'email' => 'toby@machine.local', 'is_email_confirmed' => 1],
|
||||
],
|
||||
'discussions' => [
|
||||
Discussion::class => [
|
||||
['id' => 2, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 3, 'first_post_id' => 4, 'comment_count' => 2],
|
||||
],
|
||||
'posts' => [
|
||||
Post::class => [
|
||||
['id' => 4, 'number' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r><p>One of the <GROUPMENTION groupname="Mods" id="4">@"Mods"#g4</GROUPMENTION> will look at this</p></r>'],
|
||||
['id' => 6, 'number' => 3, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r><p><GROUPMENTION groupname="OldGroupName" id="100">@"OldGroupName"#g100</GROUPMENTION></p></r>'],
|
||||
['id' => 7, 'number' => 4, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r><p><GROUPMENTION groupname="OldGroupName" id="11">@"OldGroupName"#g11</GROUPMENTION></p></r>'],
|
||||
@@ -53,7 +55,7 @@ class GroupMentionsTest extends TestCase
|
||||
['group_id' => Group::MEMBER_ID, 'permission' => 'postWithoutThrottle'],
|
||||
['group_id' => 9, 'permission' => 'mentionGroups'],
|
||||
],
|
||||
'groups' => [
|
||||
Group::class => [
|
||||
['id' => 9, 'name_singular' => 'HasPermissionToMentionGroups', 'name_plural' => 'test'],
|
||||
['id' => 10, 'name_singular' => 'Hidden', 'name_plural' => 'Ninjas', 'icon' => 'fas fa-wrench', 'color' => '#000', 'is_hidden' => 1],
|
||||
['id' => 11, 'name_singular' => 'Fresh Name', 'name_plural' => 'Fresh Name', 'color' => '#ccc', 'icon' => 'fas fa-users', 'is_hidden' => 0]
|
||||
|
@@ -10,9 +10,12 @@
|
||||
namespace Flarum\Mentions\Tests\integration\api\discussions;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Mentions\Api\LoadMentionedByRelationship;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ListPostsTest extends TestCase
|
||||
@@ -29,10 +32,10 @@ class ListPostsTest extends TestCase
|
||||
$this->extension('flarum-mentions');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'discussions' => [
|
||||
Discussion::class => [
|
||||
['id' => 1, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1],
|
||||
],
|
||||
'posts' => [
|
||||
Post::class => [
|
||||
['id' => 1, 'discussion_id' => 1, 'created_at' => Carbon::now(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>text</p></t>'],
|
||||
['id' => 2, 'discussion_id' => 1, 'created_at' => Carbon::now(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>text</p></t>'],
|
||||
['id' => 3, 'discussion_id' => 1, 'created_at' => Carbon::now(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>text</p></t>'],
|
||||
@@ -43,7 +46,7 @@ class ListPostsTest extends TestCase
|
||||
['post_id' => 3, 'mentions_user_id' => 1],
|
||||
['post_id' => 4, 'mentions_user_id' => 2]
|
||||
],
|
||||
'users' => [
|
||||
User::class => [
|
||||
$this->normalUser(),
|
||||
]
|
||||
]);
|
||||
@@ -112,10 +115,10 @@ class ListPostsTest extends TestCase
|
||||
protected function prepareMentionedByData(): void
|
||||
{
|
||||
$this->prepareDatabase([
|
||||
'discussions' => [
|
||||
Discussion::class => [
|
||||
['id' => 100, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'user_id' => 1, 'first_post_id' => 101, 'comment_count' => 12],
|
||||
],
|
||||
'posts' => [
|
||||
Post::class => [
|
||||
['id' => 101, 'discussion_id' => 100, 'created_at' => Carbon::now(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>text</p></t>'],
|
||||
['id' => 102, 'discussion_id' => 100, 'created_at' => Carbon::now(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>text</p></t>'],
|
||||
['id' => 103, 'discussion_id' => 100, 'created_at' => Carbon::now(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>text</p></t>', 'is_private' => 1],
|
||||
|
@@ -10,8 +10,11 @@
|
||||
namespace Flarum\Mentions\Tests\integration\api;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Discussion;
|
||||
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;
|
||||
@@ -31,16 +34,16 @@ class PostMentionsTest extends TestCase
|
||||
$this->extension('flarum-mentions');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'users' => [
|
||||
User::class => [
|
||||
['id' => 3, 'username' => 'potato', 'email' => 'potato@machine.local', 'is_email_confirmed' => 1],
|
||||
['id' => 4, 'username' => 'toby', 'email' => 'toby@machine.local', 'is_email_confirmed' => 1],
|
||||
['id' => 5, 'username' => 'bad_user', 'email' => 'bad_user@machine.local', 'is_email_confirmed' => 1],
|
||||
],
|
||||
'discussions' => [
|
||||
Discussion::class => [
|
||||
['id' => 2, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 3, 'first_post_id' => 4, 'comment_count' => 2],
|
||||
['id' => 50, 'title' => __CLASS__, 'is_private' => true, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 3, 'first_post_id' => 4, 'comment_count' => 1],
|
||||
],
|
||||
'posts' => [
|
||||
Post::class => [
|
||||
['id' => 4, 'number' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r><POSTMENTION displayname="TobyFlarum___" id="5" number="2" discussionid="2" username="toby">@tobyuuu#5</POSTMENTION></r>'],
|
||||
['id' => 5, 'number' => 3, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 4, 'type' => 'comment', 'content' => '<r><POSTMENTION displayname="potato" id="4" number="3" discussionid="2" username="potato">@potato#4</POSTMENTION></r>'],
|
||||
['id' => 6, 'number' => 4, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r><POSTMENTION displayname="i_am_a_deleted_user" id="7" number="5" discussionid="2" username="i_am_a_deleted_user">@"i_am_a_deleted_user"#p7</POSTMENTION></r>'],
|
||||
@@ -538,6 +541,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
|
||||
|
@@ -10,10 +10,14 @@
|
||||
namespace Flarum\Mentions\Tests\integration\api;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Post\CommentPost;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Tags\Tag;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Flarum\User\User;
|
||||
|
||||
class TagMentionsTest extends TestCase
|
||||
{
|
||||
@@ -26,20 +30,20 @@ class TagMentionsTest extends TestCase
|
||||
$this->extension('flarum-tags', 'flarum-mentions');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'users' => [
|
||||
User::class => [
|
||||
['id' => 3, 'username' => 'potato', 'email' => 'potato@machine.local', 'is_email_confirmed' => 1],
|
||||
['id' => 4, 'username' => 'toby', 'email' => 'toby@machine.local', 'is_email_confirmed' => 1],
|
||||
],
|
||||
'discussions' => [
|
||||
Discussion::class => [
|
||||
['id' => 2, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 3, 'first_post_id' => 4, 'comment_count' => 2],
|
||||
],
|
||||
'posts' => [
|
||||
Post::class => [
|
||||
['id' => 4, 'number' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r><TAGMENTION id="1" slug="test_old_slug" tagname="TestOldName">#test_old_slug</TAGMENTION></r>'],
|
||||
['id' => 7, 'number' => 5, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 2021, 'type' => 'comment', 'content' => '<r><TAGMENTION id="3" slug="support" tagname="Support">#deleted_relation</TAGMENTION></r>'],
|
||||
['id' => 8, 'number' => 6, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 4, 'type' => 'comment', 'content' => '<r><TAGMENTION id="2020" slug="i_am_a_deleted_tag" tagname="i_am_a_deleted_tag">#i_am_a_deleted_tag</TAGMENTION></r>'],
|
||||
['id' => 10, 'number' => 11, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 4, 'type' => 'comment', 'content' => '<r><TAGMENTION id="5" slug="laravel">#laravel</TAGMENTION></r>'],
|
||||
],
|
||||
'tags' => [
|
||||
Tag::class => [
|
||||
['id' => 1, 'name' => 'Test', 'slug' => 'test', 'is_restricted' => 0],
|
||||
['id' => 2, 'name' => 'Flarum', 'slug' => 'flarum', 'is_restricted' => 0],
|
||||
['id' => 3, 'name' => 'Support', 'slug' => 'support', 'is_restricted' => 0],
|
||||
|
@@ -10,8 +10,10 @@
|
||||
namespace Flarum\Mentions\Tests\integration\api;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Extend;
|
||||
use Flarum\Post\CommentPost;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Flarum\User\DisplayName\DriverInterface;
|
||||
@@ -31,16 +33,16 @@ class UserMentionsTest extends TestCase
|
||||
$this->extension('flarum-mentions');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'users' => [
|
||||
User::class => [
|
||||
$this->normalUser(),
|
||||
['id' => 3, 'username' => 'potato', 'email' => 'potato@machine.local', 'is_email_confirmed' => 1],
|
||||
['id' => 4, 'username' => 'toby', 'email' => 'toby@machine.local', 'is_email_confirmed' => 1],
|
||||
['id' => 5, 'username' => 'bad_user', 'email' => 'bad_user@machine.local', 'is_email_confirmed' => 1],
|
||||
],
|
||||
'discussions' => [
|
||||
Discussion::class => [
|
||||
['id' => 2, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 3, 'first_post_id' => 4, 'comment_count' => 2],
|
||||
],
|
||||
'posts' => [
|
||||
Post::class => [
|
||||
['id' => 4, 'number' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r><USERMENTION displayname="TobyFlarum___" id="4" username="toby">@tobyuuu</USERMENTION></r>'],
|
||||
['id' => 6, 'number' => 3, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 4, 'type' => 'comment', 'content' => '<r><USERMENTION displayname="i_am_a_deleted_user" id="2021" username="i_am_a_deleted_user">@"i_am_a_deleted_user"#2021</USERMENTION></r>'],
|
||||
['id' => 10, 'number' => 11, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 5, 'type' => 'comment', 'content' => '<r><USERMENTION displayname="Bad "#p6 User" id="5">@"Bad "#p6 User"#5</USERMENTION></r>'],
|
||||
|
@@ -27,7 +27,7 @@ class UpdateTest extends TestCase
|
||||
|
||||
$this->extension('flarum-nicknames');
|
||||
$this->prepareDatabase([
|
||||
'users' => [
|
||||
User::class => [
|
||||
$this->normalUser(),
|
||||
],
|
||||
]);
|
||||
|
@@ -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": "^2.0",
|
||||
"composer/composer": "^2.3"
|
||||
"composer/composer": "^2.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"flarum/testing": "^2.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),
|
||||
];
|
||||
|
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 {};
|
||||
|
6
extensions/package-manager/js/dist-typings/states/ExtensionManagerState.d.ts
generated
vendored
Normal file
6
extensions/package-manager/js/dist-typings/states/ExtensionManagerState.d.ts
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import QueueState from './QueueState';
|
||||
import ControlSectionState from './ControlSectionState';
|
||||
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",
|
||||
|
@@ -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="Form 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('FormSection', 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);
|
||||
}
|
||||
}
|
@@ -15,14 +15,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'] ? (
|
||||
<Form>
|
||||
<Installer />
|
||||
<Updater />
|
||||
@@ -30,7 +30,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 name={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,23 +19,25 @@ 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>
|
||||
<p className="helpText">
|
||||
{app.translator.trans('flarum-package-manager.admin.extensions.install_help', {
|
||||
<div className="Form-group ExtensionManager-installer">
|
||||
<label htmlFor="install-extension">{app.translator.trans('flarum-extension-manager.admin.extensions.install')}</label>
|
||||
<div className="helpText">
|
||||
{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>
|
||||
<div className="FormControl-container">
|
||||
<input className="FormControl" id="install-extension" placeholder="vendor/package-name" bidi={this.packageName} />
|
||||
<Button
|
||||
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,11 @@ 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 {
|
||||
@@ -35,36 +30,38 @@ export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttr
|
||||
// @todo move Form-group--danger class to core for reuse
|
||||
return (
|
||||
<div
|
||||
className={classList('Form-group Form-group--danger PackageManager-majorUpdate', {
|
||||
'PackageManager-majorUpdate--failed': this.updateState.status === 'failure',
|
||||
'PackageManager-majorUpdate--incompatibleExtensions': this.updateState.incompatibleExtensions.length,
|
||||
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-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')}>
|
||||
<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('/', '-')]}
|
||||
@@ -78,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}
|
||||
@@ -100,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/components/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 name={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,23 +199,27 @@ export default class QueueSection extends Component<{}> {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Pagination list={app.packageManager.queue} />
|
||||
<Pagination list={app.extensionManager.queue} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
operationIcon(operation: TaskOperations): Mithril.Children {
|
||||
const iconName = {
|
||||
update_check: 'fas fa-sync-alt',
|
||||
update_major: 'fas fa-play',
|
||||
update_minor: 'fas fa-play',
|
||||
update_global: 'fas fa-play',
|
||||
extension_install: 'fas fa-download',
|
||||
extension_remove: 'fas fa-times',
|
||||
extension_update: 'fas fa-arrow-alt-circle-up',
|
||||
why_not: 'fas fa-exclamation-circle',
|
||||
}[operation];
|
||||
|
||||
return <Icon name={iconName} />;
|
||||
return (
|
||||
<Icon
|
||||
name={
|
||||
{
|
||||
update_check: 'fas fa-sync-alt',
|
||||
update_major: 'fas fa-play',
|
||||
update_minor: 'fas fa-play',
|
||||
update_global: 'fas fa-play',
|
||||
extension_install: 'fas fa-download',
|
||||
extension_remove: 'fas fa-times',
|
||||
extension_update: 'fas fa-arrow-alt-circle-up',
|
||||
why_not: 'fas fa-exclamation-circle',
|
||||
}[operation]
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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="ExtensionManager-warnings Form-group">
|
||||
<Alert className="ExtensionManager-primaryWarning" type="warning" dismissible={false}>
|
||||
<ul>{listItems(warnings)}</ul>
|
||||
</Alert>
|
||||
</div>
|
||||
{settings ? (
|
||||
<div className="FormSectionGroup ExtensionManager-SettingsGroups">
|
||||
<div className="FormSection">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.settings.title')}</label>
|
||||
<div className="Form">{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>
|
||||
<div className="helpText">{app.translator.trans('flarum-extension-manager.admin.updater.updater_help')}</div>
|
||||
{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,11 +45,11 @@ 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>
|
||||
);
|
||||
@@ -60,15 +59,15 @@ export default class Updater extends Component<IUpdaterAttrs> {
|
||||
|
||||
if (!(state.extensionUpdates.length || hasMinorCoreUpdate)) {
|
||||
return (
|
||||
<div className="PackageManager-extensions">
|
||||
<span className="helpText">{app.translator.trans('flarum-package-manager.admin.updater.up_to_date')}</span>
|
||||
<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">
|
||||
<div className="ExtensionManager-extensions">
|
||||
<div className="ExtensionManager-extensions-grid">
|
||||
{hasMinorCoreUpdate ? (
|
||||
<ExtensionItem
|
||||
extension={state.coreUpdate!.extension}
|
||||
@@ -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,7 +77,7 @@ app.initializers.add('flarum-package-manager', (app) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.remove')}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,7 @@
|
||||
import QueueState from './QueueState';
|
||||
import ControlSectionState from './ControlSectionState';
|
||||
|
||||
export default class ExtensionManagerState {
|
||||
public queue: QueueState = new QueueState();
|
||||
public control: ControlSectionState = new ControlSectionState();
|
||||
}
|
@@ -3,12 +3,13 @@ import Task from '../models/Task';
|
||||
import { ApiQueryParamsPlural } from 'flarum/common/Store';
|
||||
|
||||
export default class QueueState {
|
||||
private polling: any = null;
|
||||
private tasks: Task[] | null = null;
|
||||
private limit = 20;
|
||||
private offset = 0;
|
||||
private total = 0;
|
||||
|
||||
load(params?: ApiQueryParamsPlural) {
|
||||
load(params?: ApiQueryParamsPlural, actionTaken = false): Promise<Task[]> {
|
||||
this.tasks = null;
|
||||
params = {
|
||||
page: {
|
||||
@@ -19,12 +20,26 @@ export default class QueueState {
|
||||
...params,
|
||||
};
|
||||
|
||||
return app.store.find<Task[]>('package-manager-tasks', params || {}).then((data) => {
|
||||
return app.store.find<Task[]>('extension-manager-tasks', params || {}).then((data) => {
|
||||
this.tasks = data;
|
||||
this.total = data.payload.meta?.total!;
|
||||
this.total = data.payload.meta?.total;
|
||||
|
||||
m.redraw();
|
||||
|
||||
// Check if there is a pending or running task
|
||||
const pendingTask = data?.find((task) => task.status() === 'pending' || task.status() === 'running');
|
||||
|
||||
if (pendingTask) {
|
||||
this.pollQueue(actionTaken);
|
||||
} else if (actionTaken) {
|
||||
app.extensionManager.control.setLoading(null);
|
||||
|
||||
// Refresh the page
|
||||
window.location.reload();
|
||||
} else if (app.extensionManager.control.isLoading()) {
|
||||
app.extensionManager.control.setLoading(null);
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
@@ -62,4 +77,18 @@ export default class QueueState {
|
||||
this.load();
|
||||
}
|
||||
}
|
||||
|
||||
pollQueue(actionTaken = false): void {
|
||||
if (this.polling) {
|
||||
clearTimeout(this.polling);
|
||||
}
|
||||
|
||||
this.polling = setTimeout(() => {
|
||||
this.load({}, actionTaken);
|
||||
}, 6000);
|
||||
}
|
||||
|
||||
hasPending() {
|
||||
return !!this.tasks?.find((task) => task.status() === 'pending' || task.status() === 'running');
|
||||
}
|
||||
}
|
||||
|
@@ -1,29 +1,33 @@
|
||||
import app from 'flarum/admin/app';
|
||||
|
||||
export default function (e: any) {
|
||||
app.extensionManager.control.setLoading(null);
|
||||
|
||||
const error = e.response.errors[0];
|
||||
|
||||
if (!['composer_command_failure', 'extension_already_installed', 'extension_not_installed'].includes(error.code)) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
app.alerts.clear();
|
||||
|
||||
switch (error.code) {
|
||||
case 'composer_command_failure':
|
||||
if (error.guessed_cause) {
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans(`flarum-package-manager.admin.exceptions.guessed_cause.${error.guessed_cause}`));
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans(`flarum-extension-manager.admin.exceptions.guessed_cause.${error.guessed_cause}`));
|
||||
app.modal.close();
|
||||
} else {
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-package-manager.admin.exceptions.composer_command_failure'));
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-extension-manager.admin.exceptions.composer_command_failure'));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'extension_already_installed':
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-package-manager.admin.exceptions.extension_already_installed'));
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-extension-manager.admin.exceptions.extension_already_installed'));
|
||||
app.modal.close();
|
||||
break;
|
||||
|
||||
case 'extension_not_installed':
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-package-manager.admin.exceptions.extension_not_installed'));
|
||||
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-extension-manager.admin.exceptions.extension_not_installed'));
|
||||
app.modal.close();
|
||||
}
|
||||
}
|
||||
|
@@ -5,9 +5,12 @@ window.jumpToQueue = jumpToQueue;
|
||||
|
||||
export default function jumpToQueue(): void {
|
||||
app.modal.close();
|
||||
m.route.set(app.route('extension', { id: 'flarum-package-manager' }));
|
||||
app.packageManager.queue.load();
|
||||
|
||||
m.route.set(app.route('extension', { id: 'flarum-extension-manager' }));
|
||||
|
||||
app.extensionManager.queue.load({}, true);
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('PackageManager-queueSection')?.scrollIntoView({ block: 'nearest' });
|
||||
document.getElementById('ExtensionManager-queueSection')?.scrollIntoView({ block: 'nearest' });
|
||||
}, 200);
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
@import "admin/QueueSection";
|
||||
@import "admin/ControlSection";
|
||||
|
||||
.PackageManager-controlSection, .PackageManager-queueSection {
|
||||
.ExtensionManager-controlSection {
|
||||
> .container {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
@@ -27,3 +27,53 @@
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.ButtonGroup--full {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
> .Button:first-child {
|
||||
flex-grow: 1;
|
||||
text-align: start;
|
||||
}
|
||||
}
|
||||
|
||||
.ConfigureAuth-hosts, .ConfigureComposer-repositories {
|
||||
> .ButtonGroup {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.flarum-extension-manager-Page .SettingsGroups .Form {
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
.ExtensionManager-SettingsGroups {
|
||||
display: flex;
|
||||
column-count: 3;
|
||||
column-gap: 30px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.FormSection {
|
||||
min-width: 300px;
|
||||
max-height: 500px;
|
||||
min-height: 20vh;
|
||||
max-width: 400px;
|
||||
|
||||
@media (max-width: 1209px) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.Form-controls {
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ExtensionManager-warnings {
|
||||
margin-bottom: 24px;
|
||||
|
||||
> .Alert {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.PackageManager-lastUpdatedAt {
|
||||
.ExtensionManager-lastUpdatedAt {
|
||||
color: var(--control-color);
|
||||
|
||||
&-label {
|
||||
@@ -6,7 +6,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.PackageManager-updaterControls {
|
||||
.ExtensionManager-updaterControls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
@@ -14,7 +14,7 @@
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.PackageManager-extensions {
|
||||
.ExtensionManager-extensions {
|
||||
width: 100%;
|
||||
|
||||
&-grid {
|
||||
@@ -25,7 +25,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.PackageManager-extension {
|
||||
.ExtensionManager-extension {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -81,7 +81,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.PackageManager-majorUpdate {
|
||||
.ExtensionManager-majorUpdate {
|
||||
--space: 16px;
|
||||
padding: var(--space);
|
||||
display: grid;
|
||||
@@ -142,7 +142,7 @@
|
||||
border-top: 1px solid var(--control-bg);
|
||||
}
|
||||
|
||||
.PackageManager-updaterControls {
|
||||
.ExtensionManager-updaterControls {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -153,14 +153,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.PackageManager-installer .FormControl-container {
|
||||
max-width: 400px;
|
||||
.ExtensionManager-installer .FormControl-container {
|
||||
max-width: 450px;
|
||||
|
||||
.FormControl {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.PackageManager-controlSection .container {
|
||||
max-width: 900px;
|
||||
.ExtensionManager-controlSection .container {
|
||||
max-width: 1030px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.ExtensionManager-primaryWarning ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.PackageManager-queueSection {
|
||||
.ExtensionManager-queueSection {
|
||||
&-header > .container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
.Label {
|
||||
text-transform: uppercase;
|
||||
margin-inline-end: 8px;
|
||||
}
|
||||
|
||||
.Table {
|
||||
@@ -25,7 +26,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.PackageManager-queueTable {
|
||||
.ExtensionManager-queueTable {
|
||||
&-package {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@@ -1,12 +1,66 @@
|
||||
flarum-package-manager:
|
||||
flarum-extension-manager:
|
||||
admin:
|
||||
auth_config:
|
||||
add_label: New authentication method
|
||||
add_modal:
|
||||
host_label: Host
|
||||
host_placeholder: "example: extiverse.com"
|
||||
submit_button: Submit
|
||||
token_label: Token
|
||||
type_label: Type
|
||||
unchanged_token_placeholder: "(unchanged)"
|
||||
delete_confirmation: Are you sure you want to delete this authentication method?
|
||||
delete_label: Delete authentication method
|
||||
edit_label: Edit authentication method
|
||||
fields:
|
||||
host: Host
|
||||
token: Token
|
||||
no_auth_methods_configured: No authentication methods configured. This is an optional advanced feature to allow installing from private repositories.
|
||||
remove_button_label: Remove authentication method
|
||||
title: Authentication Methods
|
||||
types:
|
||||
github-oauth: GitHub OAuth
|
||||
gitlab-oauth: GitLab OAuth
|
||||
gitlab-token: GitLab Token
|
||||
bearer: HTTP Bearer
|
||||
composer:
|
||||
add_repository_label: Add Repository
|
||||
delete_repository_confirmation: Are you sure you want to delete this repository? All extensions installed from this repository will be removed.
|
||||
delete_repository_label: Delete repository
|
||||
edit_repository_label: Edit repository
|
||||
title: Composer
|
||||
minimum_stability:
|
||||
label: Minimum Stability
|
||||
help: The type of packages allowed to be installed. Do not change this unless you know what you are doing.
|
||||
options:
|
||||
stable: Stable (Recommended)
|
||||
rc: Release Candidate
|
||||
beta: Beta
|
||||
alpha: Alpha
|
||||
dev: Dev
|
||||
repositories:
|
||||
label: Repositories
|
||||
help: >
|
||||
Add additional repositories to install packages from. This is an advanced feature, do not add repositories that are not trusted, as they can be used to execute malicious code on your server.
|
||||
types:
|
||||
composer: composer
|
||||
vcs: vcs
|
||||
path: path
|
||||
add_modal:
|
||||
name_label: Name
|
||||
type_label: Type
|
||||
url: URL
|
||||
submit_button: Submit
|
||||
|
||||
exceptions:
|
||||
composer_command_failure: Failed to execute. Check the composer logs in storage/logs/composer.
|
||||
extension_already_installed: Extension is already installed.
|
||||
extension_not_directly_dependency: Extension is installed as a dependency of another extension, it cannot be directly removed.
|
||||
extension_not_installed: Extension not found.
|
||||
|
||||
guessed_cause:
|
||||
extension_incompatible_with_instance: The extension is most likely incompatible with your current Flarum instance.
|
||||
extension_not_found: The extension was not found or does not exist.
|
||||
extensions_incompatible_with_new_major: >
|
||||
Some installed extensions are not compatible with the newest major release.
|
||||
Please wait until the extensions are updated to be compatible by the authors, or remove them before proceeding.
|
||||
@@ -14,15 +68,20 @@ flarum-package-manager:
|
||||
extensions:
|
||||
check_why_it_failed_updating: Show why it did not update to the latest.
|
||||
install: Install a new extension
|
||||
install_help: Fill in the extension package name to proceed. Visit {extiverse} to browse extensions.
|
||||
install_help: >
|
||||
Fill in the extension package name to proceed. You can specify a <semantic_link>semantic version</semantic_link> using the format <code>vendor/package-name:version</code>.
|
||||
Visit {extiverse} to browse extensions.
|
||||
proceed: Proceed
|
||||
remove: Uninstall
|
||||
successful_install: "{extension} was installed successfully, redirecting.."
|
||||
successful_remove: Extension removed successfully.
|
||||
successful_update: "{extension} was updated successfully, redirecting.."
|
||||
update: Update
|
||||
update_soft_label: Soft update
|
||||
update_hard_label: Hard update
|
||||
|
||||
file_permissions: >
|
||||
The package manager requires read and write permissions on the following files and directories: composer.json, composer.lock, vendor, storage, storage/.composer
|
||||
The extension manager requires read and write permissions on the following files and directories: composer.json, composer.lock, vendor, storage, storage/.composer
|
||||
|
||||
major_updater:
|
||||
description: >
|
||||
@@ -47,7 +106,7 @@ flarum-package-manager:
|
||||
columns:
|
||||
details: Details
|
||||
elapsed_time: Completed in
|
||||
peak_memory_used: Maximum Memory Used
|
||||
peak_memory_used: Peak Memory Usage
|
||||
operation: Operation
|
||||
package: Package
|
||||
status: Status
|
||||
@@ -62,7 +121,9 @@ flarum-package-manager:
|
||||
update_minor: Minor update
|
||||
why_not: Analyze why a package cannot be updated
|
||||
output_modal:
|
||||
cause_unknown: Unknown
|
||||
command: Composer Command
|
||||
guessed_cause: Cause
|
||||
output: Output
|
||||
refresh: Refresh tasks list
|
||||
statuses:
|
||||
@@ -74,11 +135,17 @@ flarum-package-manager:
|
||||
title: Queue
|
||||
|
||||
settings:
|
||||
access_warning: Please be careful to who you give access to the admin area, the package manager could be misused by bad actors to install packages that can lead to security breaches.
|
||||
title: => core.ref.settings
|
||||
access_warning: Please be careful to who you give access to the admin area, the extension manager could be misused by bad actors to install packages that can lead to security breaches.
|
||||
debug_mode_warning: You are running in debug mode, the extension manager cannot properly install and update local development packages. Please use the command line interface instead for such purposes.
|
||||
queue_jobs: Run operations in the background queue
|
||||
queue_jobs_help: >
|
||||
You can read about a <a href='{basic_impl_link}'>basic queue</a> implementation or a <a href='{adv_impl_link}'>more advanced</a> one.
|
||||
Make sure the PHP version used for the queue is {php_version}. Make sure <a href='{folder_perms_link}'>folder permissions</a> are correctly configured.
|
||||
task_retention_days: Task retention days
|
||||
task_retention_days_help: >
|
||||
The number of days to keep completed tasks in the database. Tasks older than this will be deleted.
|
||||
Set to 0 to keep all tasks.
|
||||
|
||||
updater:
|
||||
up_to_date: Everything is up to date!
|
||||
|
@@ -10,7 +10,7 @@
|
||||
use Flarum\Database\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
return Migration::createTable(
|
||||
return Migration::createTableIfNotExists(
|
||||
'package_manager_tasks',
|
||||
function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
|
@@ -0,0 +1,28 @@
|
||||
<?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.
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Schema\Builder;
|
||||
|
||||
return [
|
||||
'up' => function (Builder $schema) {
|
||||
$schema->table('package_manager_tasks', function (Blueprint $table) use ($schema) {
|
||||
if (! $schema->hasColumn('package_manager_tasks', 'guessed_cause')) {
|
||||
$table->string('guessed_cause', 255)->nullable()->after('output');
|
||||
}
|
||||
});
|
||||
},
|
||||
'down' => function (Builder $schema) {
|
||||
$schema->table('package_manager_tasks', function (Blueprint $table) use ($schema) {
|
||||
if ($schema->hasColumn('package_manager_tasks', 'guessed_cause')) {
|
||||
$table->dropColumn('guessed_cause');
|
||||
}
|
||||
});
|
||||
}
|
||||
];
|
@@ -0,0 +1,23 @@
|
||||
<?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.
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Schema\Builder;
|
||||
|
||||
return [
|
||||
'up' => function (Builder $schema) {
|
||||
$schema->rename('package_manager_tasks', 'extension_manager_tasks');
|
||||
$schema->getConnection()->table('migrations')->where('extension', 'flarum-package-manager')->delete();
|
||||
},
|
||||
'down' => function (Builder $schema) {
|
||||
$schema->rename('extension_manager_tasks', 'package_manager_tasks');
|
||||
$schema->getConnection()->table('migrations')->where('extension', 'flarum-extension-manager')->update([
|
||||
'extension' => 'flarum-package-manager',
|
||||
]);
|
||||
}
|
||||
];
|
31
extensions/package-manager/src/AllValidatorRules.php
Normal file
31
extensions/package-manager/src/AllValidatorRules.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?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\ExtensionManager;
|
||||
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
/**
|
||||
* @todo: fix in 2.0
|
||||
*/
|
||||
trait AllValidatorRules
|
||||
{
|
||||
protected function makeValidator(array $attributes): Validator
|
||||
{
|
||||
$rules = $this->getRules();
|
||||
|
||||
$validator = $this->validator->make($attributes, $rules, $this->getMessages());
|
||||
|
||||
foreach ($this->configuration as $callable) {
|
||||
$callable($this, $validator);
|
||||
}
|
||||
|
||||
return $validator;
|
||||
}
|
||||
}
|
@@ -7,11 +7,11 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
namespace Flarum\ExtensionManager\Api\Controller;
|
||||
|
||||
use Flarum\ExtensionManager\Command\CheckForUpdates;
|
||||
use Flarum\ExtensionManager\Job\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\CheckForUpdates;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -24,16 +24,15 @@ class CheckForUpdatesController implements RequestHandlerInterface
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
$actor->assertAdmin();
|
||||
|
||||
/**
|
||||
* @TODO somewhere, if we're queuing, check that a similar composer command isn't already running,
|
||||
* to avoid duplicate jobs.
|
||||
*/
|
||||
$response = $this->bus->dispatch(
|
||||
new CheckForUpdates($actor)
|
||||
);
|
||||
|
159
extensions/package-manager/src/Api/Controller/ConfigureComposerController.php
Executable file
159
extensions/package-manager/src/Api/Controller/ConfigureComposerController.php
Executable file
@@ -0,0 +1,159 @@
|
||||
<?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\ExtensionManager\Api\Controller;
|
||||
|
||||
use Flarum\ExtensionManager\Composer\ComposerJson;
|
||||
use Flarum\ExtensionManager\ConfigureAuthValidator;
|
||||
use Flarum\ExtensionManager\ConfigureComposerValidator;
|
||||
use Flarum\Foundation\Paths;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
/**
|
||||
* Used to both set and read the composer.json configuration.
|
||||
* And other composer local configuration such as auth.json.
|
||||
*/
|
||||
class ConfigureComposerController implements RequestHandlerInterface
|
||||
{
|
||||
protected array $configurable = [
|
||||
'minimum-stability',
|
||||
'repositories',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
protected ConfigureComposerValidator $composerValidator,
|
||||
protected ConfigureAuthValidator $authValidator,
|
||||
protected Paths $paths,
|
||||
protected ComposerJson $composerJson,
|
||||
protected Filesystem $filesystem
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$type = Arr::get($request->getParsedBody(), 'type');
|
||||
|
||||
$actor->assertAdmin();
|
||||
|
||||
if (! in_array($type, ['composer', 'auth'])) {
|
||||
return new JsonResponse([
|
||||
'data' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
if ($type === 'composer') {
|
||||
$data = $this->composerConfig($request);
|
||||
} else {
|
||||
$data = $this->authConfig($request);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function composerConfig(ServerRequestInterface $request): array
|
||||
{
|
||||
$data = Arr::only(Arr::get($request->getParsedBody(), 'data') ?? [], $this->configurable);
|
||||
|
||||
$this->composerValidator->assertValid($data);
|
||||
$composerJson = $this->composerJson->get();
|
||||
|
||||
if (! empty($data)) {
|
||||
foreach ($data as $key => $value) {
|
||||
Arr::set($composerJson, $key, $value);
|
||||
}
|
||||
|
||||
// Always prefer stable releases.
|
||||
$composerJson['prefer-stable'] = true;
|
||||
|
||||
$this->composerJson->set($composerJson);
|
||||
}
|
||||
|
||||
$default = [
|
||||
'minimum-stability' => 'stable',
|
||||
'repositories' => [],
|
||||
];
|
||||
|
||||
foreach ($this->configurable as $key) {
|
||||
$composerJson[$key] = Arr::get($composerJson, $key, Arr::get($default, $key));
|
||||
|
||||
if (is_null($composerJson[$key])) {
|
||||
$composerJson[$key] = $default[$key];
|
||||
}
|
||||
}
|
||||
|
||||
$composerJson = Arr::sortRecursive($composerJson);
|
||||
|
||||
return Arr::only($composerJson, $this->configurable);
|
||||
}
|
||||
|
||||
protected function authConfig(ServerRequestInterface $request): array
|
||||
{
|
||||
$data = Arr::get($request->getParsedBody(), 'data');
|
||||
|
||||
$this->authValidator->assertValid($data ?? []);
|
||||
|
||||
try {
|
||||
$authJson = json_decode($this->filesystem->get($this->paths->base.'/auth.json'), true);
|
||||
} catch (FileNotFoundException $e) {
|
||||
$authJson = [];
|
||||
}
|
||||
|
||||
if (! is_null($data)) {
|
||||
foreach ($data as $type => $hosts) {
|
||||
foreach ($hosts as $host => $token) {
|
||||
if (empty($token)) {
|
||||
unset($authJson[$type][$host]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_starts_with($token, 'unchanged:')) {
|
||||
$old = Str::of($token)->explode(':')->skip(1)->values()->all();
|
||||
|
||||
if (count($old) !== 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$oldType, $oldHost] = $old;
|
||||
|
||||
if (! isset($authJson[$oldType][$oldHost])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[$type][$host] = $authJson[$oldType][$oldHost];
|
||||
} else {
|
||||
$data[$type][$host] = $token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->filesystem->put($this->paths->base.'/auth.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
$authJson = $data;
|
||||
}
|
||||
|
||||
// Remove tokens from response.
|
||||
foreach ($authJson as $type => $hosts) {
|
||||
foreach ($hosts as $host => $token) {
|
||||
$authJson[$type][$host] = "unchanged:$type:$host";
|
||||
}
|
||||
}
|
||||
|
||||
return $authJson;
|
||||
}
|
||||
}
|
@@ -7,11 +7,11 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
namespace Flarum\ExtensionManager\Api\Controller;
|
||||
|
||||
use Flarum\ExtensionManager\Command\GlobalUpdate;
|
||||
use Flarum\ExtensionManager\Job\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\GlobalUpdate;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
@@ -25,6 +25,9 @@ class GlobalUpdateController implements RequestHandlerInterface
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
@@ -7,13 +7,13 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
namespace Flarum\ExtensionManager\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractListController;
|
||||
use Flarum\ExtensionManager\Api\Serializer\TaskSerializer;
|
||||
use Flarum\ExtensionManager\Task\Task;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\PackageManager\Api\Serializer\TaskSerializer;
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
@@ -22,7 +22,7 @@ class ListTasksController extends AbstractListController
|
||||
public ?string $serializer = TaskSerializer::class;
|
||||
|
||||
public function __construct(
|
||||
protected UrlGenerator $url,
|
||||
protected UrlGenerator $url
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class ListTasksController extends AbstractListController
|
||||
$offset = $this->extractOffset($request);
|
||||
|
||||
$results = Task::query()
|
||||
->latest()
|
||||
->latest('id')
|
||||
->offset($offset)
|
||||
->limit($limit)
|
||||
->get();
|
||||
@@ -46,7 +46,7 @@ class ListTasksController extends AbstractListController
|
||||
$document->addMeta('total', (string) $total);
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->to('api')->route('package-manager.tasks.index'),
|
||||
$this->url->to('api')->route('extension-manager.tasks.index'),
|
||||
$request->getQueryParams(),
|
||||
$offset,
|
||||
$limit,
|
||||
|
@@ -7,11 +7,11 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
namespace Flarum\ExtensionManager\Api\Controller;
|
||||
|
||||
use Flarum\ExtensionManager\Command\MajorUpdate;
|
||||
use Flarum\ExtensionManager\Job\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\MajorUpdate;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
|
@@ -7,11 +7,11 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
namespace Flarum\ExtensionManager\Api\Controller;
|
||||
|
||||
use Flarum\ExtensionManager\Command\MinorUpdate;
|
||||
use Flarum\ExtensionManager\Job\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\MinorUpdate;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
@@ -25,6 +25,9 @@ class MinorUpdateController implements RequestHandlerInterface
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
@@ -7,11 +7,11 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
namespace Flarum\ExtensionManager\Api\Controller;
|
||||
|
||||
use Flarum\ExtensionManager\Command\RemoveExtension;
|
||||
use Flarum\ExtensionManager\Job\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\RemoveExtension;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
|
@@ -7,11 +7,11 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
namespace Flarum\ExtensionManager\Api\Controller;
|
||||
|
||||
use Flarum\ExtensionManager\Command\RequireExtension;
|
||||
use Flarum\ExtensionManager\Job\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\RequireExtension;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
@@ -7,11 +7,11 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
namespace Flarum\ExtensionManager\Api\Controller;
|
||||
|
||||
use Flarum\ExtensionManager\Command\UpdateExtension;
|
||||
use Flarum\ExtensionManager\Job\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\UpdateExtension;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
@@ -30,9 +30,10 @@ class UpdateExtensionController implements RequestHandlerInterface
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$extensionId = Arr::get($request->getQueryParams(), 'id');
|
||||
$updateMode = Arr::get($request->getParsedBody(), 'data.updateMode');
|
||||
|
||||
$response = $this->bus->dispatch(
|
||||
new UpdateExtension($actor, $extensionId)
|
||||
new UpdateExtension($actor, $extensionId, $updateMode)
|
||||
);
|
||||
|
||||
return $response->queueJobs
|
||||
|
@@ -7,11 +7,11 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
namespace Flarum\ExtensionManager\Api\Controller;
|
||||
|
||||
use Flarum\ExtensionManager\Command\WhyNot;
|
||||
use Flarum\ExtensionManager\Job\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\WhyNot;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
@@ -7,24 +7,30 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Api\Serializer;
|
||||
namespace Flarum\ExtensionManager\Api\Serializer;
|
||||
|
||||
use Flarum\Api\Serializer\AbstractSerializer;
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use Flarum\ExtensionManager\Task\Task;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class TaskSerializer extends AbstractSerializer
|
||||
{
|
||||
protected $type = 'package-manager-tasks';
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $type = 'extension-manager-tasks';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param Task $model
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function getDefaultAttributes(object|array $model): array
|
||||
protected function getDefaultAttributes($model): array
|
||||
{
|
||||
if (! ($model instanceof Task)) {
|
||||
throw new InvalidArgumentException(
|
||||
$this::class.' can only serialize instances of '.Task::class
|
||||
get_class($this).' can only serialize instances of '.Task::class
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,6 +40,7 @@ class TaskSerializer extends AbstractSerializer
|
||||
'command' => $model->command,
|
||||
'package' => $model->package,
|
||||
'output' => $model->output,
|
||||
'guessedCause' => $model->guessed_cause,
|
||||
'createdAt' => $model->created_at,
|
||||
'startedAt' => $model->started_at,
|
||||
'finishedAt' => $model->finished_at,
|
||||
|
@@ -7,15 +7,15 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Command;
|
||||
namespace Flarum\ExtensionManager\Command;
|
||||
|
||||
use Flarum\PackageManager\Task\Operation;
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use Flarum\ExtensionManager\Task\Task;
|
||||
|
||||
abstract class AbstractActionCommand
|
||||
{
|
||||
public ?Task $task = null;
|
||||
public ?string $package = null;
|
||||
public ?string $extensionId = null;
|
||||
|
||||
abstract public function getOperationName(): Operation;
|
||||
abstract public function getOperationName(): string;
|
||||
}
|
||||
|
@@ -7,9 +7,9 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Command;
|
||||
namespace Flarum\ExtensionManager\Command;
|
||||
|
||||
use Flarum\PackageManager\Task\Operation;
|
||||
use Flarum\ExtensionManager\Task\Task;
|
||||
use Flarum\User\User;
|
||||
|
||||
class CheckForUpdates extends AbstractActionCommand
|
||||
@@ -19,8 +19,8 @@ class CheckForUpdates extends AbstractActionCommand
|
||||
) {
|
||||
}
|
||||
|
||||
public function getOperationName(): Operation
|
||||
public function getOperationName(): string
|
||||
{
|
||||
return Operation::UPDATE_CHECK;
|
||||
return Task::UPDATE_CHECK;
|
||||
}
|
||||
}
|
||||
|
@@ -7,18 +7,24 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Command;
|
||||
namespace Flarum\ExtensionManager\Command;
|
||||
|
||||
use Flarum\PackageManager\Composer\ComposerAdapter;
|
||||
use Flarum\PackageManager\Exception\ComposerCommandFailedException;
|
||||
use Flarum\PackageManager\Settings\LastUpdateCheck;
|
||||
use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\ExtensionManager\Composer\ComposerAdapter;
|
||||
use Flarum\ExtensionManager\Composer\ComposerJson;
|
||||
use Flarum\ExtensionManager\Exception\ComposerCommandFailedException;
|
||||
use Flarum\ExtensionManager\Settings\LastUpdateCheck;
|
||||
use Flarum\ExtensionManager\Support\Util;
|
||||
use Illuminate\Support\Collection;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
|
||||
class CheckForUpdatesHandler
|
||||
{
|
||||
public function __construct(
|
||||
private ComposerAdapter $composer,
|
||||
private LastUpdateCheck $lastUpdateCheck
|
||||
protected ComposerAdapter $composer,
|
||||
protected LastUpdateCheck $lastUpdateCheck,
|
||||
protected ExtensionManager $extensions,
|
||||
protected ComposerJson $composerJson
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -45,14 +51,10 @@ class CheckForUpdatesHandler
|
||||
$firstOutput = $this->runComposerCommand(false, $command);
|
||||
$firstOutput = json_decode($this->cleanJson($firstOutput), true);
|
||||
|
||||
$majorUpdates = false;
|
||||
|
||||
foreach ($firstOutput['installed'] as $package) {
|
||||
if (isset($package['latest-status']) && $package['latest-status'] === 'update-possible') {
|
||||
$majorUpdates = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$installed = new Collection($firstOutput['installed'] ?? []);
|
||||
$majorUpdates = $installed->contains(function (array $package) {
|
||||
return isset($package['latest-status']) && $package['latest-status'] === 'update-possible' && Util::isMajorUpdate($package['version'], $package['latest']);
|
||||
});
|
||||
|
||||
if ($majorUpdates) {
|
||||
$secondOutput = $this->runComposerCommand(true, $command);
|
||||
@@ -63,10 +65,22 @@ class CheckForUpdatesHandler
|
||||
$secondOutput = ['installed' => []];
|
||||
}
|
||||
|
||||
foreach ($firstOutput['installed'] as &$mainPackageUpdate) {
|
||||
$updates = new Collection();
|
||||
$composerJson = $this->composerJson->get();
|
||||
|
||||
foreach ($installed as $mainPackageUpdate) {
|
||||
// Skip if not an extension
|
||||
if (! $this->extensions->getExtension(Util::nameToId($mainPackageUpdate['name']))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mainPackageUpdate['latest-minor'] = $mainPackageUpdate['latest-major'] = null;
|
||||
|
||||
if (isset($mainPackageUpdate['latest-status']) && $mainPackageUpdate['latest-status'] === 'update-possible') {
|
||||
if ($mainPackageUpdate['latest-status'] === 'up-to-date' && Util::isMajorUpdate($mainPackageUpdate['version'], $mainPackageUpdate['latest'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($mainPackageUpdate['latest-status']) && $mainPackageUpdate['latest-status'] === 'update-possible' && Util::isMajorUpdate($mainPackageUpdate['version'], $mainPackageUpdate['latest'])) {
|
||||
$mainPackageUpdate['latest-major'] = $mainPackageUpdate['latest'];
|
||||
|
||||
$minorPackageUpdate = array_filter($secondOutput['installed'], function ($package) use ($mainPackageUpdate) {
|
||||
@@ -79,10 +93,14 @@ class CheckForUpdatesHandler
|
||||
} else {
|
||||
$mainPackageUpdate['latest-minor'] = $mainPackageUpdate['latest'] ?? null;
|
||||
}
|
||||
|
||||
$mainPackageUpdate['required-as'] = $composerJson['require'][$mainPackageUpdate['name']] ?? null;
|
||||
|
||||
$updates->push($mainPackageUpdate);
|
||||
}
|
||||
|
||||
return $this->lastUpdateCheck
|
||||
->with('installed', $firstOutput['installed'])
|
||||
->with('installed', $updates->values()->toArray())
|
||||
->save();
|
||||
}
|
||||
|
||||
@@ -102,7 +120,6 @@ class CheckForUpdatesHandler
|
||||
{
|
||||
$input = [
|
||||
'command' => 'outdated',
|
||||
'-D' => true,
|
||||
'--format' => 'json',
|
||||
];
|
||||
|
||||
|
@@ -7,9 +7,9 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Command;
|
||||
namespace Flarum\ExtensionManager\Command;
|
||||
|
||||
use Flarum\PackageManager\Task\Operation;
|
||||
use Flarum\ExtensionManager\Task\Task;
|
||||
use Flarum\User\User;
|
||||
|
||||
class GlobalUpdate extends AbstractActionCommand
|
||||
@@ -19,8 +19,8 @@ class GlobalUpdate extends AbstractActionCommand
|
||||
) {
|
||||
}
|
||||
|
||||
public function getOperationName(): Operation
|
||||
public function getOperationName(): string
|
||||
{
|
||||
return Operation::UPDATE_GLOBAL;
|
||||
return Task::UPDATE_GLOBAL;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user