mirror of
https://github.com/flarum/core.git
synced 2025-08-16 13:24:11 +02:00
Compare commits
1 Commits
sqlite-dri
...
sm/itemlis
Author | SHA1 | Date | |
---|---|---|---|
|
13f997d784 |
9
.github/workflows/REUSABLE_backend.yml
vendored
9
.github/workflows/REUSABLE_backend.yml
vendored
@@ -31,8 +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"]'
|
||||
default: '["8.1", "8.2"]'
|
||||
|
||||
php_extensions:
|
||||
description: PHP extensions to install.
|
||||
@@ -52,16 +51,10 @@ 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,15 +90,11 @@ 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
29
.github/workflows/build-install-packages.yml
vendored
@@ -1,29 +0,0 @@
|
||||
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,4 +38,3 @@ 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\\ExtensionManager\\": "extensions/package-manager/src",
|
||||
"Flarum\\PackageManager\\": "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\\ExtensionManager\\Tests\\": "extensions/package-manager/tests",
|
||||
"Flarum\\PackageManager\\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/extension-manager": "self.version",
|
||||
"flarum/package-manager": "self.version",
|
||||
"flarum/pusher": "self.version",
|
||||
"flarum/statistics": "self.version",
|
||||
"flarum/sticky": "self.version",
|
||||
@@ -112,7 +112,6 @@
|
||||
"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",
|
||||
@@ -131,7 +130,7 @@
|
||||
"illuminate/support": "^10.0",
|
||||
"illuminate/validation": "^10.0",
|
||||
"illuminate/view": "^10.0",
|
||||
"intervention/image": "^3.2",
|
||||
"intervention/image": "^2.7.2",
|
||||
"jenssegers/agent": "^2.6",
|
||||
"laminas/laminas-diactoros": "^3.0",
|
||||
"laminas/laminas-httphandlerrunner": "^2.6",
|
||||
@@ -169,7 +168,7 @@
|
||||
"mockery/mockery": "^1.5",
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"phpstan/phpstan": "^1.10.0",
|
||||
"larastan/larastan": "^2.7",
|
||||
"nunomaduro/larastan": "^2.6",
|
||||
"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,
|
||||
|
@@ -11,7 +11,6 @@ namespace Flarum\Approval\Listener;
|
||||
|
||||
use Flarum\Approval\Event\PostWasApproved;
|
||||
use Flarum\Post\Event\Saving;
|
||||
use Flarum\User\Exception\PermissionDeniedException;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
class ApproveContent
|
||||
@@ -21,42 +20,23 @@ class ApproveContent
|
||||
$events->listen(Saving::class, $this->approvePost(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PermissionDeniedException
|
||||
*/
|
||||
public function approvePost(Saving $event): void
|
||||
{
|
||||
$attributes = $event->data['attributes'];
|
||||
$post = $event->post;
|
||||
|
||||
// Nothing to do if it is already approved.
|
||||
if ($post->is_approved) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* We approve a post in one of two cases:
|
||||
* - The post was unapproved and the allowed action is approving it. We trigger an event.
|
||||
* - The post was unapproved and the allowed actor is hiding or un-hiding it.
|
||||
* We approve it silently if the action is unhiding.
|
||||
*/
|
||||
$approvingSilently = false;
|
||||
|
||||
if (isset($attributes['isApproved'])) {
|
||||
$event->actor->assertCan('approve', $post);
|
||||
|
||||
$isApproved = (bool) $attributes['isApproved'];
|
||||
} elseif (isset($attributes['isHidden']) && $event->actor->can('approve', $post)) {
|
||||
} elseif (! empty($attributes['isHidden']) && $event->actor->can('approve', $post)) {
|
||||
$isApproved = true;
|
||||
$approvingSilently = $attributes['isHidden'];
|
||||
}
|
||||
|
||||
if (! empty($isApproved)) {
|
||||
$post->is_approved = true;
|
||||
|
||||
if (! $approvingSilently) {
|
||||
$post->raise(new PostWasApproved($post, $event->actor));
|
||||
}
|
||||
$post->raise(new PostWasApproved($post, $event->actor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,23 +10,19 @@
|
||||
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([
|
||||
User::class => [
|
||||
'users' => [
|
||||
['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],
|
||||
],
|
||||
Discussion::class => [
|
||||
'discussions' => [
|
||||
['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],
|
||||
@@ -35,7 +31,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],
|
||||
],
|
||||
Post::class => [
|
||||
'posts' => [
|
||||
['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],
|
||||
@@ -49,7 +45,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],
|
||||
],
|
||||
Group::class => [
|
||||
'groups' => [
|
||||
['id' => 4, 'name_singular' => 'Acme', 'name_plural' => 'Acme', 'is_hidden' => 0]
|
||||
],
|
||||
'group_user' => [
|
||||
|
@@ -50,7 +50,7 @@
|
||||
padding: 15px 15px;
|
||||
|
||||
.scrolled & {
|
||||
box-shadow: 0 2px 6px var(--shadow-color);
|
||||
.box-shadow(0 2px 6px var(--shadow-color));
|
||||
}
|
||||
}
|
||||
|
||||
|
6
extensions/emoji/js/.prettierrc.json
Normal file
6
extensions/emoji/js/.prettierrc.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"printWidth": 150,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
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
@@ -1,47 +1,47 @@
|
||||
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';
|
||||
import cdn from './cdn';
|
||||
|
||||
export default function addComposerAutocomplete() {
|
||||
const $container = $('<div class="ComposerBody-emojiDropdownContainer"></div>');
|
||||
const dropdown = new AutocompleteDropdown();
|
||||
let emojiMap = null;
|
||||
|
||||
extend('flarum/common/components/TextEditor', 'oninit', function () {
|
||||
this._loaders.push(async () => await import('./emojiMap').then((m) => (emojiMap = m.default)));
|
||||
// prettier-ignore
|
||||
this.commonEmoji = [
|
||||
'😀', '😁', '😂', '😃', '😄', '😅', '😆', '😇', '😈', '😉', '😊', '😋', '😌', '😍', '😎', '😏', '😐️', '😑', '😒',
|
||||
'😓', '😔', '😕', '😖', '😗', '😘', '😙', '😚', '😛', '😜', '😝', '😞', '😟', '😠', '😡', '😢', '😣', '😤', '😥',
|
||||
'😦', '😧', '😨', '😩', '😪', '😫', '😬', '😭', '😮', '😮💨', '😯', '😰', '😱', '😲', '😳', '😴', '😵', '😵💫',
|
||||
'😶', '😶🌫️', '😷', '😸', '😹', '😺', '😻', '😼', '😽', '😾', '😿', '🙀', '🙁', '🙂', '🙃', '🙄',
|
||||
];
|
||||
});
|
||||
|
||||
extend('flarum/common/components/TextEditor', 'onbuild', function () {
|
||||
this.emojiDropdown = new AutocompleteDropdown();
|
||||
const $editor = this.$('.TextEditor-editor').wrap('<div class="ComposerBody-emojiWrapper"></div>');
|
||||
|
||||
this.navigator = new KeyboardNavigatable();
|
||||
this.navigator
|
||||
.when(() => this.emojiDropdown.active)
|
||||
.onUp(() => this.emojiDropdown.navigate(-1))
|
||||
.onDown(() => this.emojiDropdown.navigate(1))
|
||||
.onSelect(this.emojiDropdown.complete.bind(this.emojiDropdown))
|
||||
.onCancel(this.emojiDropdown.hide.bind(this.emojiDropdown))
|
||||
.when(() => dropdown.active)
|
||||
.onUp(() => dropdown.navigate(-1))
|
||||
.onDown(() => dropdown.navigate(1))
|
||||
.onSelect(dropdown.complete.bind(dropdown))
|
||||
.onCancel(dropdown.hide.bind(dropdown))
|
||||
.bindTo($editor);
|
||||
|
||||
$editor.after($('<div class="ComposerBody-emojiDropdownContainer"></div>'));
|
||||
$editor.after($container);
|
||||
});
|
||||
|
||||
extend('flarum/common/components/TextEditor', 'buildEditorParams', function (params) {
|
||||
const emojiKeys = Object.keys(emojiMap);
|
||||
|
||||
const autocompleteReader = new AutocompleteReader(':');
|
||||
let relEmojiStart;
|
||||
let absEmojiStart;
|
||||
let typed;
|
||||
|
||||
const applySuggestion = (replacement) => {
|
||||
this.attrs.composer.editor.replaceBeforeCursor(absEmojiStart - 1, replacement + ' ');
|
||||
|
||||
dropdown.hide();
|
||||
};
|
||||
|
||||
params.inputListeners.push(() => {
|
||||
const selection = this.attrs.composer.editor.getSelectionRange();
|
||||
@@ -50,34 +50,42 @@ 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);
|
||||
const autocompleting = autocompleteReader.check(lastChunk, cursor, /[a-z0-9]|\+|\-|_|\:/);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
this.emojiDropdown.hide();
|
||||
this.emojiDropdown.active = false;
|
||||
dropdown.hide();
|
||||
dropdown.active = false;
|
||||
|
||||
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();
|
||||
};
|
||||
if (absEmojiStart) {
|
||||
typed = lastChunk.substring(relEmojiStart).toLowerCase();
|
||||
|
||||
const makeSuggestion = function ({ emoji, name, code }) {
|
||||
return (
|
||||
<Tooltip text={name}>
|
||||
<button
|
||||
key={emoji}
|
||||
onclick={() => applySuggestion(emoji)}
|
||||
onmouseenter={function () {
|
||||
emojiDropdown.setIndex($(this).parent().index() - 1);
|
||||
}}
|
||||
>
|
||||
<img alt={emoji} className="emoji" draggable="false" loading="lazy" src={`${cdn}72x72/${code}.png`} title={name} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<button
|
||||
key={emoji}
|
||||
onclick={() => applySuggestion(emoji)}
|
||||
onmouseenter={function () {
|
||||
dropdown.setIndex($(this).parent().index() - 1);
|
||||
}}
|
||||
>
|
||||
<img alt={emoji} className="emoji" draggable="false" loading="lazy" src={`${cdn}72x72/${code}.png`} />
|
||||
{name}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -91,7 +99,7 @@ export default function addComposerAutocomplete() {
|
||||
};
|
||||
const regTyped = fuzzyRegexp(typed);
|
||||
|
||||
let maxSuggestions = 40;
|
||||
let maxSuggestions = 7;
|
||||
|
||||
const findMatchingEmojis = (matcher) => {
|
||||
for (let i = 0; i < emojiKeys.length && maxSuggestions > 0; i++) {
|
||||
@@ -100,7 +108,7 @@ export default function addComposerAutocomplete() {
|
||||
if (similarEmoji.indexOf(curEmoji) === -1) {
|
||||
const names = emojiMap[curEmoji];
|
||||
for (let name of names) {
|
||||
if (matcher(name, curEmoji)) {
|
||||
if (matcher(name)) {
|
||||
--maxSuggestions;
|
||||
similarEmoji.push(curEmoji);
|
||||
break;
|
||||
@@ -111,17 +119,10 @@ export default function addComposerAutocomplete() {
|
||||
};
|
||||
|
||||
// First, try to find all emojis starting with the given string
|
||||
findMatchingEmojis((emojiName, emoji) => {
|
||||
// If no input is provided yet, match the most common emojis.
|
||||
if (!typed) {
|
||||
return this.commonEmoji?.includes(emoji);
|
||||
}
|
||||
|
||||
return emojiName.indexOf(typed) === 0;
|
||||
});
|
||||
findMatchingEmojis((emoji) => emoji.indexOf(typed) === 0);
|
||||
|
||||
// If there are still suggestions left, try for some fuzzy matches
|
||||
findMatchingEmojis((emojiName) => regTyped.test(emojiName));
|
||||
findMatchingEmojis((emoji) => regTyped.test(emoji));
|
||||
|
||||
const suggestions = similarEmoji
|
||||
.map((emoji) => ({
|
||||
@@ -132,14 +133,14 @@ export default function addComposerAutocomplete() {
|
||||
.map(makeSuggestion);
|
||||
|
||||
if (suggestions.length) {
|
||||
this.emojiDropdown.items = suggestions;
|
||||
m.render(this.$('.ComposerBody-emojiDropdownContainer')[0], this.emojiDropdown.render());
|
||||
dropdown.items = suggestions;
|
||||
m.render($container[0], dropdown.render());
|
||||
|
||||
this.emojiDropdown.show();
|
||||
const coordinates = this.attrs.composer.editor.getCaretCoordinates(autocompleting.absoluteStart);
|
||||
const width = this.emojiDropdown.$().outerWidth();
|
||||
const height = this.emojiDropdown.$().outerHeight();
|
||||
const parent = this.emojiDropdown.$().offsetParent();
|
||||
dropdown.show();
|
||||
const coordinates = this.attrs.composer.editor.getCaretCoordinates(absEmojiStart);
|
||||
const width = dropdown.$().outerWidth();
|
||||
const height = dropdown.$().outerHeight();
|
||||
const parent = dropdown.$().offsetParent();
|
||||
let left = coordinates.left;
|
||||
let top = coordinates.top + 15;
|
||||
|
||||
@@ -155,15 +156,15 @@ export default function addComposerAutocomplete() {
|
||||
top = Math.max(-(parent.offset().top - $(document).scrollTop()), top);
|
||||
left = Math.max(-parent.offset().left, left);
|
||||
|
||||
this.emojiDropdown.show(left, top);
|
||||
dropdown.show(left, top);
|
||||
}
|
||||
};
|
||||
|
||||
buildSuggestions();
|
||||
|
||||
this.emojiDropdown.setIndex(0);
|
||||
this.emojiDropdown.$().scrollTop(0);
|
||||
this.emojiDropdown.active = true;
|
||||
dropdown.setIndex(0);
|
||||
dropdown.$().scrollTop(0);
|
||||
dropdown.active = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@@ -7,28 +7,29 @@ img.emoji {
|
||||
.EmojiDropdown {
|
||||
max-width: 500px;
|
||||
max-height: 200px;
|
||||
overflow: visible;
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
padding: 8px;
|
||||
margin: 5px 0 !important;
|
||||
|
||||
> li {
|
||||
display: inline-block;
|
||||
> li > button {
|
||||
color: var(--text-color);
|
||||
font-weight: bold;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
padding-left: 45px;
|
||||
|
||||
> button {
|
||||
color: var(--text-color);
|
||||
font-weight: bold;
|
||||
padding: 8px;
|
||||
border-radius: var(--border-radius);
|
||||
.emoji {
|
||||
float: left;
|
||||
margin-left: -30px;
|
||||
}
|
||||
}
|
||||
|
||||
> .Dropdown-header {
|
||||
display: block;
|
||||
.Dropdown-header {
|
||||
color: var(--muted-more-color);
|
||||
text-transform: none;
|
||||
font-weight: normal;
|
||||
padding: 4px 8px;
|
||||
margin: 0 0 4px 0;
|
||||
padding-bottom: 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
|
1
extensions/flags/js/dist-typings/forum/components/FlagList.d.ts
generated
vendored
1
extensions/flags/js/dist-typings/forum/components/FlagList.d.ts
generated
vendored
@@ -8,5 +8,4 @@ export interface IFlagListAttrs extends ComponentAttrs {
|
||||
export default class FlagList<CustomAttrs extends IFlagListAttrs = IFlagListAttrs> extends Component<CustomAttrs, FlagListState> {
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>): void;
|
||||
view(): JSX.Element;
|
||||
content(state: FlagListState): JSX.Element[][] | null;
|
||||
}
|
||||
|
4
extensions/flags/js/dist-typings/forum/components/FlagPostModal.d.ts
generated
vendored
4
extensions/flags/js/dist-typings/forum/components/FlagPostModal.d.ts
generated
vendored
@@ -1,5 +1,5 @@
|
||||
/// <reference types="flarum/@types/translator-icu-rich" />
|
||||
export default class FlagPostModal extends FormModal<import("flarum/common/components/FormModal").IFormModalAttrs, undefined> {
|
||||
export default class FlagPostModal extends Modal<import("flarum/common/components/Modal").IInternalModalAttrs, undefined> {
|
||||
constructor();
|
||||
oninit(vnode: any): void;
|
||||
success: boolean | undefined;
|
||||
@@ -10,6 +10,6 @@ export default class FlagPostModal extends FormModal<import("flarum/common/compo
|
||||
flagReasons(): ItemList<any>;
|
||||
onsubmit(e: any): void;
|
||||
}
|
||||
import FormModal from "flarum/common/components/FormModal";
|
||||
import Modal from "flarum/common/components/Modal";
|
||||
import Stream from "flarum/common/utils/Stream";
|
||||
import ItemList from "flarum/common/utils/ItemList";
|
||||
|
10
extensions/flags/js/dist-typings/forum/states/FlagListState.d.ts
generated
vendored
10
extensions/flags/js/dist-typings/forum/states/FlagListState.d.ts
generated
vendored
@@ -1,13 +1,15 @@
|
||||
import type ForumApplication from 'flarum/forum/ForumApplication';
|
||||
import type Flag from '../models/Flag';
|
||||
import PaginatedListState from 'flarum/common/states/PaginatedListState';
|
||||
export default class FlagListState extends PaginatedListState<Flag> {
|
||||
import type Post from 'flarum/common/models/Post';
|
||||
export default class FlagListState {
|
||||
app: ForumApplication;
|
||||
loading: boolean;
|
||||
cache: Flag[] | null;
|
||||
index: Post | false | null;
|
||||
constructor(app: ForumApplication);
|
||||
get type(): string;
|
||||
/**
|
||||
* Load flags into the application's cache if they haven't already
|
||||
* been loaded.
|
||||
*/
|
||||
load(): Promise<void>;
|
||||
load(): void;
|
||||
}
|
||||
|
2
extensions/flags/js/dist/forum.js
generated
vendored
2
extensions/flags/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/flags/js/dist/forum.js.map
generated
vendored
2
extensions/flags/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -54,7 +54,7 @@ export default function () {
|
||||
|
||||
const controls = PostControls.destructiveControls(this.attrs.post);
|
||||
|
||||
Object.keys(controls.toObject()).forEach((k) => {
|
||||
Object.keys(controls.items).forEach((k) => {
|
||||
const attrs = controls.get(k).attrs;
|
||||
|
||||
attrs.className = 'Button';
|
||||
|
@@ -8,8 +8,6 @@ import HeaderListItem from 'flarum/forum/components/HeaderListItem';
|
||||
import type Mithril from 'mithril';
|
||||
import type Post from 'flarum/common/models/Post';
|
||||
import type FlagListState from '../states/FlagListState';
|
||||
import type Flag from '../models/Flag';
|
||||
import { Page } from 'flarum/common/states/PaginatedListState';
|
||||
|
||||
export interface IFlagListAttrs extends ComponentAttrs {
|
||||
state: FlagListState;
|
||||
@@ -18,55 +16,49 @@ export interface IFlagListAttrs extends ComponentAttrs {
|
||||
export default class FlagList<CustomAttrs extends IFlagListAttrs = IFlagListAttrs> extends Component<CustomAttrs, FlagListState> {
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
this.state = this.attrs.state;
|
||||
}
|
||||
|
||||
view() {
|
||||
const state = this.attrs.state;
|
||||
const flags = this.state.cache || [];
|
||||
|
||||
return (
|
||||
<HeaderList
|
||||
className="FlagList"
|
||||
title={app.translator.trans('flarum-flags.forum.flagged_posts.title')}
|
||||
hasItems={state.hasItems()}
|
||||
loading={state.isLoading()}
|
||||
hasItems={flags.length}
|
||||
loading={this.state.loading}
|
||||
emptyText={app.translator.trans('flarum-flags.forum.flagged_posts.empty_text')}
|
||||
loadMore={() => state.hasNext() && !state.isLoadingNext() && state.loadNext()}
|
||||
>
|
||||
<ul className="HeaderListGroup-content">{this.content(state)}</ul>
|
||||
<ul className="HeaderListGroup-content">
|
||||
{!this.state.loading &&
|
||||
flags.map((flag) => {
|
||||
const post = flag.post() as Post;
|
||||
|
||||
return (
|
||||
<li>
|
||||
<HeaderListItem
|
||||
className="Flag"
|
||||
avatar={<Avatar user={post.user() || null} />}
|
||||
icon="fas fa-flag"
|
||||
content={app.translator.trans('flarum-flags.forum.flagged_posts.item_text', {
|
||||
username: username(post.user()),
|
||||
em: <em />,
|
||||
discussion: post.discussion().title(),
|
||||
})}
|
||||
excerpt={post.contentPlain()}
|
||||
datetime={flag.createdAt()}
|
||||
href={app.route.post(post)}
|
||||
onclick={(e: MouseEvent) => {
|
||||
app.flags.index = post;
|
||||
e.redraw = false;
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</HeaderList>
|
||||
);
|
||||
}
|
||||
|
||||
content(state: FlagListState) {
|
||||
if (!state.isLoading() && state.hasItems()) {
|
||||
return state.getPages().map((page: Page<Flag>) => {
|
||||
return page.items.map((flag: Flag) => {
|
||||
const post = flag.post() as Post;
|
||||
|
||||
return (
|
||||
<li>
|
||||
<HeaderListItem
|
||||
className="Flag"
|
||||
avatar={<Avatar user={post.user() || null} />}
|
||||
icon="fas fa-flag"
|
||||
content={app.translator.trans('flarum-flags.forum.flagged_posts.item_text', {
|
||||
username: username(post.user()),
|
||||
em: <em />,
|
||||
discussion: post.discussion().title(),
|
||||
})}
|
||||
excerpt={post.contentPlain()}
|
||||
datetime={flag.createdAt()}
|
||||
href={app.route.post(post)}
|
||||
onclick={(e: MouseEvent) => {
|
||||
e.redraw = false;
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import FormModal from 'flarum/common/components/FormModal';
|
||||
import Modal from 'flarum/common/components/Modal';
|
||||
import Form from 'flarum/common/components/Form';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
|
||||
@@ -7,7 +7,7 @@ import Stream from 'flarum/common/utils/Stream';
|
||||
import withAttr from 'flarum/common/utils/withAttr';
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
|
||||
export default class FlagPostModal extends FormModal {
|
||||
export default class FlagPostModal extends Modal {
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
|
||||
|
@@ -25,7 +25,7 @@ export default class FlagsDropdown<CustomAttrs extends IFlagsDropdownAttrs = IFl
|
||||
}
|
||||
|
||||
getUnreadCount() {
|
||||
return app.forum.attribute<number>('flagCount');
|
||||
return app.flags.cache ? app.flags.cache.length : app.forum.attribute<number>('flagCount');
|
||||
}
|
||||
|
||||
getNewCount() {
|
||||
|
@@ -1,33 +1,39 @@
|
||||
import type ForumApplication from 'flarum/forum/ForumApplication';
|
||||
import type Flag from '../models/Flag';
|
||||
import PaginatedListState from 'flarum/common/states/PaginatedListState';
|
||||
import type Post from 'flarum/common/models/Post';
|
||||
|
||||
export default class FlagListState extends PaginatedListState<Flag> {
|
||||
export default class FlagListState {
|
||||
public app: ForumApplication;
|
||||
public loading = false;
|
||||
public cache: Flag[] | null = null;
|
||||
public index: Post | false | null = null;
|
||||
|
||||
constructor(app: ForumApplication) {
|
||||
super({}, 1, null);
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
get type(): string {
|
||||
return 'flags';
|
||||
}
|
||||
|
||||
/**
|
||||
* Load flags into the application's cache if they haven't already
|
||||
* been loaded.
|
||||
*/
|
||||
load(): Promise<void> {
|
||||
if (this.app.session.user?.attribute<number>('newFlagCount')) {
|
||||
this.pages = [];
|
||||
this.location = { page: 1 };
|
||||
load() {
|
||||
if (this.cache && !this.app.session.user!.attribute<number>('newFlagCount')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pages.length > 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.loading = true;
|
||||
m.redraw();
|
||||
|
||||
return super.loadNext();
|
||||
this.app.store
|
||||
.find<Flag[]>('flags')
|
||||
.then((flags) => {
|
||||
this.app.session.user!.pushAttributes({ newFlagCount: 0 });
|
||||
this.cache = flags.sort((a, b) => b.createdAt()!.getTime() - a.createdAt()!.getTime());
|
||||
})
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -32,7 +32,6 @@ 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;
|
||||
}
|
||||
|
@@ -14,7 +14,6 @@ use Flarum\Api\Controller\AbstractListController;
|
||||
use Flarum\Flags\Api\Serializer\FlagSerializer;
|
||||
use Flarum\Flags\Flag;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
@@ -29,52 +28,26 @@ class ListFlagsController extends AbstractListController
|
||||
'post.discussion'
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
protected UrlGenerator $url
|
||||
) {
|
||||
}
|
||||
|
||||
protected function data(ServerRequestInterface $request, Document $document): iterable
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$include = $this->extractInclude($request);
|
||||
|
||||
$actor->assertRegistered();
|
||||
|
||||
$actor->read_flags_at = Carbon::now();
|
||||
$actor->save();
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$include = $this->extractInclude($request);
|
||||
$flags = Flag::whereVisibleTo($actor)
|
||||
->latest('flags.created_at')
|
||||
->groupBy('post_id')
|
||||
->get();
|
||||
|
||||
if (in_array('post.user', $include)) {
|
||||
$include[] = 'post.user.groups';
|
||||
}
|
||||
|
||||
$flags = Flag::whereVisibleTo($actor)
|
||||
->latest('flags.created_at')
|
||||
->groupBy('post_id')
|
||||
->limit($limit + 1)
|
||||
->offset($offset)
|
||||
->get();
|
||||
|
||||
$this->loadRelations($flags, $include, $request);
|
||||
|
||||
$flags = $flags->all();
|
||||
|
||||
$areMoreResults = false;
|
||||
|
||||
if (count($flags) > $limit) {
|
||||
array_pop($flags);
|
||||
$areMoreResults = true;
|
||||
}
|
||||
|
||||
$this->addPaginationData(
|
||||
$document,
|
||||
$request,
|
||||
$this->url->to('api')->route('flags.index'),
|
||||
$areMoreResults ? null : 0
|
||||
);
|
||||
$this->loadRelations($flags, $include);
|
||||
|
||||
return $flags;
|
||||
}
|
||||
|
@@ -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),
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Flags\Command;
|
||||
|
||||
use Flarum\Flags\Event\Deleting;
|
||||
use Flarum\Flags\Event\FlagsWillBeDeleted;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Post\PostRepository;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
@@ -30,6 +31,9 @@ class DeleteFlagsHandler
|
||||
|
||||
$actor->assertCan('viewFlags', $post->discussion);
|
||||
|
||||
// Deprecated, removed v2.0
|
||||
$this->events->dispatch(new FlagsWillBeDeleted($post, $actor, $command->data));
|
||||
|
||||
foreach ($post->flags as $flag) {
|
||||
$this->events->dispatch(new Deleting($flag, $actor, $command->data));
|
||||
}
|
||||
|
27
extensions/flags/src/Event/FlagsWillBeDeleted.php
Normal file
27
extensions/flags/src/Event/FlagsWillBeDeleted.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?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\Flags\Event;
|
||||
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\User\User;
|
||||
|
||||
/**
|
||||
* @deprecated v2.0
|
||||
* Listen for Flarum\Flags\Event\Deleting instead
|
||||
*/
|
||||
class FlagsWillBeDeleted
|
||||
{
|
||||
public function __construct(
|
||||
public Post $post,
|
||||
public User $actor,
|
||||
public array $data = []
|
||||
) {
|
||||
}
|
||||
}
|
@@ -9,12 +9,9 @@
|
||||
|
||||
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
|
||||
@@ -31,7 +28,7 @@ class ListTest extends TestCase
|
||||
$this->extension('flarum-flags');
|
||||
|
||||
$this->prepareDatabase([
|
||||
User::class => [
|
||||
'users' => [
|
||||
$this->normalUser(),
|
||||
[
|
||||
'id' => 3,
|
||||
@@ -47,10 +44,10 @@ class ListTest extends TestCase
|
||||
'group_permission' => [
|
||||
['group_id' => Group::MODERATOR_ID, 'permission' => 'discussion.viewFlags'],
|
||||
],
|
||||
Discussion::class => [
|
||||
'discussions' => [
|
||||
['id' => 1, 'title' => '', 'user_id' => 1, 'comment_count' => 1],
|
||||
],
|
||||
Post::class => [
|
||||
'posts' => [
|
||||
['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,13 +9,9 @@
|
||||
|
||||
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
|
||||
@@ -33,13 +29,13 @@ class ListWithTagsTest extends TestCase
|
||||
$this->extension('flarum-tags');
|
||||
|
||||
$this->prepareDatabase([
|
||||
Tag::class => [
|
||||
'tags' => [
|
||||
['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],
|
||||
],
|
||||
User::class => [
|
||||
'users' => [
|
||||
$this->normalUser(),
|
||||
[
|
||||
'id' => 3,
|
||||
@@ -59,7 +55,7 @@ class ListWithTagsTest extends TestCase
|
||||
['group_id' => Group::MODERATOR_ID, 'permission' => 'tag4.viewDiscussions'],
|
||||
['group_id' => Group::MODERATOR_ID, 'permission' => 'tag4.discussion.viewFlags'],
|
||||
],
|
||||
Discussion::class => [
|
||||
'discussions' => [
|
||||
['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],
|
||||
@@ -72,7 +68,7 @@ class ListWithTagsTest extends TestCase
|
||||
['discussion_id' => 4, 'tag_id' => 4],
|
||||
['discussion_id' => 5, 'tag_id' => 1],
|
||||
],
|
||||
Post::class => [
|
||||
'posts' => [
|
||||
// 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>'],
|
||||
|
@@ -19,10 +19,9 @@ use Flarum\Likes\Event\PostWasUnliked;
|
||||
use Flarum\Likes\Notification\PostLikedBlueprint;
|
||||
use Flarum\Likes\Query\LikedByFilter;
|
||||
use Flarum\Likes\Query\LikedFilter;
|
||||
use Flarum\Post\Filter\PostSearcher;
|
||||
use Flarum\Post\Filter\PostFilterer;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Search\Database\DatabaseSearchDriver;
|
||||
use Flarum\User\Search\UserSearcher;
|
||||
use Flarum\User\Filter\UserFilterer;
|
||||
use Flarum\User\User;
|
||||
|
||||
return [
|
||||
@@ -77,9 +76,11 @@ return [
|
||||
->listen(PostWasUnliked::class, Listener\SendNotificationWhenPostIsUnliked::class)
|
||||
->subscribe(Listener\SaveLikesToDatabase::class),
|
||||
|
||||
(new Extend\SearchDriver(DatabaseSearchDriver::class))
|
||||
->addFilter(PostSearcher::class, LikedByFilter::class)
|
||||
->addFilter(UserSearcher::class, LikedFilter::class),
|
||||
(new Extend\Filter(PostFilterer::class))
|
||||
->addFilter(LikedByFilter::class),
|
||||
|
||||
(new Extend\Filter(UserFilterer::class))
|
||||
->addFilter(LikedFilter::class),
|
||||
|
||||
(new Extend\Settings())
|
||||
->default('flarum-likes.like_own_post', true),
|
||||
|
@@ -9,14 +9,10 @@
|
||||
|
||||
namespace Flarum\Likes\Query;
|
||||
|
||||
use Flarum\Search\Database\DatabaseSearchState;
|
||||
use Flarum\Search\Filter\FilterInterface;
|
||||
use Flarum\Search\SearchState;
|
||||
use Flarum\Search\ValidateFilterTrait;
|
||||
use Flarum\Filter\FilterInterface;
|
||||
use Flarum\Filter\FilterState;
|
||||
use Flarum\Filter\ValidateFilterTrait;
|
||||
|
||||
/**
|
||||
* @implements FilterInterface<DatabaseSearchState>
|
||||
*/
|
||||
class LikedByFilter implements FilterInterface
|
||||
{
|
||||
use ValidateFilterTrait;
|
||||
@@ -26,11 +22,11 @@ class LikedByFilter implements FilterInterface
|
||||
return 'likedBy';
|
||||
}
|
||||
|
||||
public function filter(SearchState $state, string|array $value, bool $negate): void
|
||||
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
|
||||
{
|
||||
$likedId = $this->asInt($value);
|
||||
$likedId = $this->asInt($filterValue);
|
||||
|
||||
$state
|
||||
$filterState
|
||||
->getQuery()
|
||||
->whereIn('id', function ($query) use ($likedId, $negate) {
|
||||
$query->select('post_id')
|
||||
|
@@ -9,14 +9,10 @@
|
||||
|
||||
namespace Flarum\Likes\Query;
|
||||
|
||||
use Flarum\Search\Database\DatabaseSearchState;
|
||||
use Flarum\Search\Filter\FilterInterface;
|
||||
use Flarum\Search\SearchState;
|
||||
use Flarum\Search\ValidateFilterTrait;
|
||||
use Flarum\Filter\FilterInterface;
|
||||
use Flarum\Filter\FilterState;
|
||||
use Flarum\Filter\ValidateFilterTrait;
|
||||
|
||||
/**
|
||||
* @implements FilterInterface<DatabaseSearchState>
|
||||
*/
|
||||
class LikedFilter implements FilterInterface
|
||||
{
|
||||
use ValidateFilterTrait;
|
||||
@@ -26,11 +22,11 @@ class LikedFilter implements FilterInterface
|
||||
return 'liked';
|
||||
}
|
||||
|
||||
public function filter(SearchState $state, string|array $value, bool $negate): void
|
||||
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
|
||||
{
|
||||
$likedId = $this->asString($value);
|
||||
$likedId = $this->asString($filterValue);
|
||||
|
||||
$state
|
||||
$filterState
|
||||
->getQuery()
|
||||
->whereIn('id', function ($query) use ($likedId) {
|
||||
$query->select('user_id')
|
||||
|
@@ -10,13 +10,9 @@
|
||||
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
|
||||
@@ -30,21 +26,21 @@ class LikePostTest extends TestCase
|
||||
$this->extension('flarum-likes');
|
||||
|
||||
$this->prepareDatabase([
|
||||
User::class => [
|
||||
'users' => [
|
||||
['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],
|
||||
],
|
||||
Discussion::class => [
|
||||
'discussions' => [
|
||||
['id' => 1, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 2],
|
||||
],
|
||||
Post::class => [
|
||||
'posts' => [
|
||||
['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>'],
|
||||
],
|
||||
Group::class => [
|
||||
'groups' => [
|
||||
['id' => 5, 'name_singular' => 'Acme', 'name_plural' => 'Acme', 'is_hidden' => 0],
|
||||
['id' => 6, 'name_singular' => 'Acme1', 'name_plural' => 'Acme1', 'is_hidden' => 0]
|
||||
],
|
||||
|
@@ -10,13 +10,10 @@
|
||||
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
|
||||
@@ -33,13 +30,13 @@ class ListPostsTest extends TestCase
|
||||
$this->extension('flarum-likes');
|
||||
|
||||
$this->prepareDatabase([
|
||||
Discussion::class => [
|
||||
'discussions' => [
|
||||
['id' => 100, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'user_id' => 1, 'first_post_id' => 101, 'comment_count' => 1],
|
||||
],
|
||||
Post::class => [
|
||||
'posts' => [
|
||||
['id' => 101, 'discussion_id' => 100, 'created_at' => Carbon::now(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>text</p></t>'],
|
||||
],
|
||||
User::class => [
|
||||
'users' => [
|
||||
$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],
|
||||
|
@@ -11,16 +11,16 @@ use Flarum\Api\Serializer\BasicDiscussionSerializer;
|
||||
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\Event\Saving;
|
||||
use Flarum\Discussion\Filter\DiscussionFilterer;
|
||||
use Flarum\Discussion\Search\DiscussionSearcher;
|
||||
use Flarum\Extend;
|
||||
use Flarum\Lock\Access;
|
||||
use Flarum\Lock\Event\DiscussionWasLocked;
|
||||
use Flarum\Lock\Event\DiscussionWasUnlocked;
|
||||
use Flarum\Lock\Filter\LockedFilter;
|
||||
use Flarum\Lock\Listener;
|
||||
use Flarum\Lock\Notification\DiscussionLockedBlueprint;
|
||||
use Flarum\Lock\Post\DiscussionLockedPost;
|
||||
use Flarum\Search\Database\DatabaseSearchDriver;
|
||||
use Flarum\Lock\Query\LockedFilterGambit;
|
||||
|
||||
return [
|
||||
(new Extend\Frontend('forum'))
|
||||
@@ -57,6 +57,9 @@ return [
|
||||
(new Extend\Policy())
|
||||
->modelPolicy(Discussion::class, Access\DiscussionPolicy::class),
|
||||
|
||||
(new Extend\SearchDriver(DatabaseSearchDriver::class))
|
||||
->addFilter(DiscussionSearcher::class, LockedFilter::class),
|
||||
(new Extend\Filter(DiscussionFilterer::class))
|
||||
->addFilter(LockedFilterGambit::class),
|
||||
|
||||
(new Extend\SimpleFlarumSearch(DiscussionSearcher::class))
|
||||
->addGambit(LockedFilterGambit::class),
|
||||
];
|
||||
|
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 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})();
|
||||
(()=>{var e={n:o=>{var r=o&&o.__esModule?()=>o.default:()=>o;return e.d(r,{a:r}),r},d:(o,r)=>{for(var a in r)e.o(r,a)&&!e.o(o,a)&&Object.defineProperty(o,a,{enumerable:!0,get:r[a]})},o:(e,o)=>Object.prototype.hasOwnProperty.call(e,o),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},o={};(()=>{"use strict";e.r(o);const r=flarum.reg.get("core","admin/app");var a=e.n(r);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=o})();
|
||||
//# 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,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":""}
|
||||
{"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,+BCL9D,MAAM,EAA+BC,OAAOC,IAAIV,IAAI,OAAQ,a,aCC5D,qBAAqB,QAAQ,KAC3B,sBAAsB,eAAeW,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/./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');","import app from 'flarum/admin/app';\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","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: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})();
|
||||
(()=>{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:()=>P});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 u=o.n(i);const l=flarum.reg.get("core","common/components/Badge");var d=o.n(l);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 v=flarum.reg.get("core","common/extenders");var y=o.n(v);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=[(new(y().PostTypes)).add("discussionLocked",h),new(y().Model)(u()).attribute("isLocked").attribute("canLock")];r().initializers.add("flarum-lock",(()=>{r().notificationComponents.discussionLocked=a,(0,e.extend)(u().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})();
|
||||
//# 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 +0,0 @@
|
||||
export { default as default } from '../common/extend';
|
@@ -1,7 +1,5 @@
|
||||
import app from 'flarum/admin/app';
|
||||
|
||||
export { default as extend } from './extend';
|
||||
|
||||
app.initializers.add('lock', () => {
|
||||
app.extensionData.for('flarum-lock').registerPermission(
|
||||
{
|
||||
|
@@ -1,7 +0,0 @@
|
||||
import Extend from 'flarum/common/extenders';
|
||||
import LockedGambit from './query/discussions/LockedGambit';
|
||||
|
||||
export default [
|
||||
new Extend.Search() //
|
||||
.gambit('discussions', LockedGambit),
|
||||
];
|
@@ -1,12 +0,0 @@
|
||||
import { BooleanGambit } from 'flarum/common/query/IGambit';
|
||||
import app from 'flarum/common/app';
|
||||
|
||||
export default class LockedGambit extends BooleanGambit {
|
||||
key(): string {
|
||||
return app.translator.trans('flarum-lock.lib.gambits.discussions.locked.key', {}, true);
|
||||
}
|
||||
|
||||
filterKey(): string {
|
||||
return 'locked';
|
||||
}
|
||||
}
|
@@ -2,11 +2,7 @@ import Extend from 'flarum/common/extenders';
|
||||
import Discussion from 'flarum/common/models/Discussion';
|
||||
import DiscussionLockedPost from './components/DiscussionLockedPost';
|
||||
|
||||
import commonExtend from '../common/extend';
|
||||
|
||||
export default [
|
||||
...commonExtend,
|
||||
|
||||
new Extend.PostTypes() //
|
||||
.add('discussionLocked', DiscussionLockedPost),
|
||||
|
||||
|
@@ -35,12 +35,3 @@ 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
|
||||
|
@@ -1,36 +0,0 @@
|
||||
<?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\Lock\Filter;
|
||||
|
||||
use Flarum\Search\Database\DatabaseSearchState;
|
||||
use Flarum\Search\Filter\FilterInterface;
|
||||
use Flarum\Search\SearchState;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
/**
|
||||
* @implements FilterInterface<DatabaseSearchState>
|
||||
*/
|
||||
class LockedFilter implements FilterInterface
|
||||
{
|
||||
public function getFilterKey(): string
|
||||
{
|
||||
return 'locked';
|
||||
}
|
||||
|
||||
public function filter(SearchState $state, string|array $value, bool $negate): void
|
||||
{
|
||||
$this->constrain($state->getQuery(), $negate);
|
||||
}
|
||||
|
||||
protected function constrain(Builder $query, bool $negate): void
|
||||
{
|
||||
$query->where('is_locked', ! $negate);
|
||||
}
|
||||
}
|
44
extensions/lock/src/Query/LockedFilterGambit.php
Normal file
44
extensions/lock/src/Query/LockedFilterGambit.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?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\Lock\Query;
|
||||
|
||||
use Flarum\Filter\FilterInterface;
|
||||
use Flarum\Filter\FilterState;
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\SearchState;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
class LockedFilterGambit extends AbstractRegexGambit implements FilterInterface
|
||||
{
|
||||
protected function getGambitPattern(): string
|
||||
{
|
||||
return 'is:locked';
|
||||
}
|
||||
|
||||
protected function conditions(SearchState $search, array $matches, bool $negate): void
|
||||
{
|
||||
$this->constrain($search->getQuery(), $negate);
|
||||
}
|
||||
|
||||
public function getFilterKey(): string
|
||||
{
|
||||
return 'locked';
|
||||
}
|
||||
|
||||
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
|
||||
{
|
||||
$this->constrain($filterState->getQuery(), $negate);
|
||||
}
|
||||
|
||||
protected function constrain(Builder $query, bool $negate): void
|
||||
{
|
||||
$query->where('is_locked', ! $negate);
|
||||
}
|
||||
}
|
@@ -24,9 +24,8 @@ use Flarum\Post\Event\Hidden;
|
||||
use Flarum\Post\Event\Posted;
|
||||
use Flarum\Post\Event\Restored;
|
||||
use Flarum\Post\Event\Revised;
|
||||
use Flarum\Post\Filter\PostSearcher;
|
||||
use Flarum\Post\Filter\PostFilterer;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Search\Database\DatabaseSearchDriver;
|
||||
use Flarum\Tags\Api\Serializer\TagSerializer;
|
||||
use Flarum\User\User;
|
||||
|
||||
@@ -115,9 +114,9 @@ return [
|
||||
->listen(Hidden::class, Listener\UpdateMentionsMetadataWhenInvisible::class)
|
||||
->listen(Deleted::class, Listener\UpdateMentionsMetadataWhenInvisible::class),
|
||||
|
||||
(new Extend\SearchDriver(DatabaseSearchDriver::class))
|
||||
->addFilter(PostSearcher::class, Filter\MentionedFilter::class)
|
||||
->addFilter(PostSearcher::class, Filter\MentionedPostFilter::class),
|
||||
(new Extend\Filter(PostFilterer::class))
|
||||
->addFilter(Filter\MentionedFilter::class)
|
||||
->addFilter(Filter\MentionedPostFilter::class),
|
||||
|
||||
(new Extend\ApiSerializer(CurrentUserSerializer::class))
|
||||
->attribute('canMentionGroups', function (CurrentUserSerializer $serializer, User $user): bool {
|
||||
|
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,33 +2,45 @@ 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';
|
||||
|
||||
export default function addComposerAutocomplete() {
|
||||
const $container = $('<div class="ComposerBody-mentionsDropdownContainer"></div>');
|
||||
const dropdown = new AutocompleteDropdown();
|
||||
|
||||
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();
|
||||
this.navigator
|
||||
.when(() => this.mentionsDropdown.active)
|
||||
.onUp(() => this.mentionsDropdown.navigate(-1))
|
||||
.onDown(() => this.mentionsDropdown.navigate(1))
|
||||
.onSelect(this.mentionsDropdown.complete.bind(this.mentionsDropdown))
|
||||
.onCancel(this.mentionsDropdown.hide.bind(this.mentionsDropdown))
|
||||
.when(() => dropdown.active)
|
||||
.onUp(() => dropdown.navigate(-1))
|
||||
.onDown(() => dropdown.navigate(1))
|
||||
.onSelect(dropdown.complete.bind(dropdown))
|
||||
.onCancel(dropdown.hide.bind(dropdown))
|
||||
.bindTo($editor);
|
||||
|
||||
$editor.after($('<div class="ComposerBody-mentionsDropdownContainer"></div>'));
|
||||
$editor.after($container);
|
||||
});
|
||||
|
||||
extend('flarum/common/components/TextEditor', 'buildEditorParams', function (params) {
|
||||
let relMentionStart;
|
||||
let absMentionStart;
|
||||
let matchTyped;
|
||||
|
||||
let mentionables = new MentionableModels({
|
||||
onmouseenter: function () {
|
||||
dropdown.setIndex($(this).parent().index());
|
||||
},
|
||||
onclick: (replacement) => {
|
||||
this.attrs.composer.editor.replaceBeforeCursor(absMentionStart - 1, replacement + ' ');
|
||||
|
||||
dropdown.hide();
|
||||
},
|
||||
});
|
||||
|
||||
const suggestionsInputListener = () => {
|
||||
const selection = this.attrs.composer.editor.getSelectionRange();
|
||||
|
||||
@@ -36,27 +48,30 @@ 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;
|
||||
const autocompleteReader = new AutocompleteReader((character) => !!(activeFormat = app.mentionFormats.get(character)));
|
||||
const autocompleting = autocompleteReader.check(this.attrs.composer.editor.getLastNChars(30), cursor, /\S+/);
|
||||
for (let i = lastChunk.length - 1; i >= 0; i--) {
|
||||
const character = lastChunk.substr(i, 1);
|
||||
activeFormat = app.mentionFormats.get(character);
|
||||
|
||||
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();
|
||||
},
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
this.mentionsDropdown.hide();
|
||||
this.mentionsDropdown.active = false;
|
||||
dropdown.hide();
|
||||
dropdown.active = false;
|
||||
|
||||
if (autocompleting) {
|
||||
mentionables.init(activeFormat.makeMentionables());
|
||||
matchTyped = activeFormat.queryFromTyped(autocompleting.typed);
|
||||
if (absMentionStart) {
|
||||
const typed = lastChunk.substring(relMentionStart).toLowerCase();
|
||||
matchTyped = activeFormat.queryFromTyped(typed);
|
||||
|
||||
if (!matchTyped) return;
|
||||
|
||||
@@ -68,14 +83,14 @@ export default function addComposerAutocomplete() {
|
||||
const suggestions = mentionables.buildSuggestions();
|
||||
|
||||
if (suggestions.length) {
|
||||
this.mentionsDropdown.items = suggestions;
|
||||
m.render(this.$('.ComposerBody-mentionsDropdownContainer')[0], this.mentionsDropdown.render());
|
||||
dropdown.items = suggestions;
|
||||
m.render($container[0], dropdown.render());
|
||||
|
||||
this.mentionsDropdown.show();
|
||||
const coordinates = this.attrs.composer.editor.getCaretCoordinates(autocompleting.absoluteStart);
|
||||
const width = this.mentionsDropdown.$().outerWidth();
|
||||
const height = this.mentionsDropdown.$().outerHeight();
|
||||
const parent = this.mentionsDropdown.$().offsetParent();
|
||||
dropdown.show();
|
||||
const coordinates = this.attrs.composer.editor.getCaretCoordinates(absMentionStart);
|
||||
const width = dropdown.$().outerWidth();
|
||||
const height = dropdown.$().outerHeight();
|
||||
const parent = dropdown.$().offsetParent();
|
||||
let left = coordinates.left;
|
||||
let top = coordinates.top + 15;
|
||||
|
||||
@@ -91,21 +106,21 @@ export default function addComposerAutocomplete() {
|
||||
top = Math.max(-(parent.offset().top - $(document).scrollTop()), top);
|
||||
left = Math.max(-parent.offset().left, left);
|
||||
|
||||
this.mentionsDropdown.show(left, top);
|
||||
dropdown.show(left, top);
|
||||
} else {
|
||||
this.mentionsDropdown.active = false;
|
||||
this.mentionsDropdown.hide();
|
||||
dropdown.active = false;
|
||||
dropdown.hide();
|
||||
}
|
||||
};
|
||||
|
||||
this.mentionsDropdown.active = true;
|
||||
dropdown.active = true;
|
||||
|
||||
buildSuggestions();
|
||||
|
||||
this.mentionsDropdown.setIndex(0);
|
||||
this.mentionsDropdown.$().scrollTop(0);
|
||||
dropdown.setIndex(0);
|
||||
dropdown.$().scrollTop(0);
|
||||
|
||||
this.searchMentions(mentionables, buildSuggestions);
|
||||
mentionables.search()?.then(buildSuggestions);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import './components/UserMentionedNotification';
|
||||
import './fragments/AutocompleteDropdown';
|
||||
import './fragments/PostQuoteButton';
|
||||
import './utils/getCleanDisplayName';
|
||||
import './utils/getMentionText';
|
||||
import './utils/reply';
|
||||
import './utils/selectedText';
|
||||
import './utils/textFormatter';
|
||||
|
@@ -2,6 +2,7 @@ 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[];
|
||||
@@ -32,7 +33,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 = async (): Promise<void> => {
|
||||
public readonly search = throttle(250, async (): Promise<void> => {
|
||||
if (!this.typed || this.typed.length <= 1) return;
|
||||
|
||||
const typedLower = this.typed.toLowerCase();
|
||||
@@ -50,7 +51,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() || '');
|
||||
|
21
extensions/mentions/js/src/forum/utils/getMentionText.js
Normal file
21
extensions/mentions/js/src/forum/utils/getMentionText.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import app from 'flarum/forum/app';
|
||||
|
||||
/**
|
||||
* Fetches the mention text for a specified user (and optionally a post ID for replies or group).
|
||||
*
|
||||
* Automatically determines which mention syntax to be used based on the option in the
|
||||
* admin dashboard. Also performs display name clean-up automatically.
|
||||
*
|
||||
* @deprecated Use `app.mentionables.get('user').replacement(user)` instead. Will be removed in 2.0.
|
||||
*/
|
||||
export default function getMentionText(user, postId, group) {
|
||||
if (user !== undefined && postId === undefined) {
|
||||
return app.mentionables.get('user').replacement(user);
|
||||
} else if (user !== undefined && postId !== undefined) {
|
||||
return app.mentionables.get('post').replacement(app.store.getById('posts', postId));
|
||||
} else if (group !== undefined) {
|
||||
return app.mentionables.get('group').replacement(group);
|
||||
}
|
||||
|
||||
throw 'No parameters were passed';
|
||||
}
|
@@ -9,14 +9,10 @@
|
||||
|
||||
namespace Flarum\Mentions\Filter;
|
||||
|
||||
use Flarum\Search\Database\DatabaseSearchState;
|
||||
use Flarum\Search\Filter\FilterInterface;
|
||||
use Flarum\Search\SearchState;
|
||||
use Flarum\Search\ValidateFilterTrait;
|
||||
use Flarum\Filter\FilterInterface;
|
||||
use Flarum\Filter\FilterState;
|
||||
use Flarum\Filter\ValidateFilterTrait;
|
||||
|
||||
/**
|
||||
* @implements FilterInterface<DatabaseSearchState>
|
||||
*/
|
||||
class MentionedFilter implements FilterInterface
|
||||
{
|
||||
use ValidateFilterTrait;
|
||||
@@ -26,11 +22,11 @@ class MentionedFilter implements FilterInterface
|
||||
return 'mentioned';
|
||||
}
|
||||
|
||||
public function filter(SearchState $state, string|array $value, bool $negate): void
|
||||
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
|
||||
{
|
||||
$mentionedId = $this->asInt($value);
|
||||
$mentionedId = $this->asInt($filterValue);
|
||||
|
||||
$state
|
||||
$filterState
|
||||
->getQuery()
|
||||
->join('post_mentions_user', 'posts.id', '=', 'post_mentions_user.post_id')
|
||||
->where('post_mentions_user.mentions_user_id', $negate ? '!=' : '=', $mentionedId);
|
||||
|
@@ -9,13 +9,9 @@
|
||||
|
||||
namespace Flarum\Mentions\Filter;
|
||||
|
||||
use Flarum\Search\Database\DatabaseSearchState;
|
||||
use Flarum\Search\Filter\FilterInterface;
|
||||
use Flarum\Search\SearchState;
|
||||
use Flarum\Filter\FilterInterface;
|
||||
use Flarum\Filter\FilterState;
|
||||
|
||||
/**
|
||||
* @implements FilterInterface<DatabaseSearchState>
|
||||
*/
|
||||
class MentionedPostFilter implements FilterInterface
|
||||
{
|
||||
public function getFilterKey(): string
|
||||
@@ -23,11 +19,11 @@ class MentionedPostFilter implements FilterInterface
|
||||
return 'mentionedPost';
|
||||
}
|
||||
|
||||
public function filter(SearchState $state, string|array $value, bool $negate): void
|
||||
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
|
||||
{
|
||||
$mentionedId = trim($value, '"');
|
||||
$mentionedId = trim($filterValue, '"');
|
||||
|
||||
$state
|
||||
$filterState
|
||||
->getQuery()
|
||||
->join('post_mentions_post', 'posts.id', '=', 'post_mentions_post.post_id')
|
||||
->where('post_mentions_post.mentions_post_id', $negate ? '!=' : '=', $mentionedId);
|
||||
|
@@ -12,7 +12,6 @@ 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;
|
||||
@@ -25,22 +24,12 @@ class FormatPostMentions
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
public function __invoke(Renderer $renderer, mixed $context, ?string $xml, Request $request = null): string
|
||||
{
|
||||
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']);
|
||||
$post = $context;
|
||||
|
||||
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,7 +10,6 @@
|
||||
namespace Flarum\Mentions\Formatter;
|
||||
|
||||
use Flarum\Locale\TranslatorInterface;
|
||||
use Flarum\Post\Post;
|
||||
use s9e\TextFormatter\Utils;
|
||||
|
||||
class UnparsePostMentions
|
||||
@@ -32,11 +31,10 @@ class UnparsePostMentions
|
||||
*/
|
||||
protected function updatePostMentionTags(mixed $context, string $xml): string
|
||||
{
|
||||
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']);
|
||||
$post = $context;
|
||||
|
||||
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,10 +10,8 @@
|
||||
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;
|
||||
@@ -32,14 +30,14 @@ class GroupMentionsTest extends TestCase
|
||||
$this->extension('flarum-mentions');
|
||||
|
||||
$this->prepareDatabase([
|
||||
User::class => [
|
||||
'users' => [
|
||||
['id' => 3, 'username' => 'potato', 'email' => 'potato@machine.local', 'is_email_confirmed' => 1],
|
||||
['id' => 4, 'username' => 'toby', 'email' => 'toby@machine.local', 'is_email_confirmed' => 1],
|
||||
],
|
||||
Discussion::class => [
|
||||
'discussions' => [
|
||||
['id' => 2, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 3, 'first_post_id' => 4, 'comment_count' => 2],
|
||||
],
|
||||
Post::class => [
|
||||
'posts' => [
|
||||
['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>'],
|
||||
@@ -55,7 +53,7 @@ class GroupMentionsTest extends TestCase
|
||||
['group_id' => Group::MEMBER_ID, 'permission' => 'postWithoutThrottle'],
|
||||
['group_id' => 9, 'permission' => 'mentionGroups'],
|
||||
],
|
||||
Group::class => [
|
||||
'groups' => [
|
||||
['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,12 +10,9 @@
|
||||
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
|
||||
@@ -32,10 +29,10 @@ class ListPostsTest extends TestCase
|
||||
$this->extension('flarum-mentions');
|
||||
|
||||
$this->prepareDatabase([
|
||||
Discussion::class => [
|
||||
'discussions' => [
|
||||
['id' => 1, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1],
|
||||
],
|
||||
Post::class => [
|
||||
'posts' => [
|
||||
['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>'],
|
||||
@@ -46,7 +43,7 @@ class ListPostsTest extends TestCase
|
||||
['post_id' => 3, 'mentions_user_id' => 1],
|
||||
['post_id' => 4, 'mentions_user_id' => 2]
|
||||
],
|
||||
User::class => [
|
||||
'users' => [
|
||||
$this->normalUser(),
|
||||
]
|
||||
]);
|
||||
@@ -115,10 +112,10 @@ class ListPostsTest extends TestCase
|
||||
protected function prepareMentionedByData(): void
|
||||
{
|
||||
$this->prepareDatabase([
|
||||
Discussion::class => [
|
||||
'discussions' => [
|
||||
['id' => 100, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'user_id' => 1, 'first_post_id' => 101, 'comment_count' => 12],
|
||||
],
|
||||
Post::class => [
|
||||
'posts' => [
|
||||
['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,11 +10,8 @@
|
||||
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;
|
||||
@@ -34,16 +31,16 @@ class PostMentionsTest extends TestCase
|
||||
$this->extension('flarum-mentions');
|
||||
|
||||
$this->prepareDatabase([
|
||||
User::class => [
|
||||
'users' => [
|
||||
['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],
|
||||
],
|
||||
Discussion::class => [
|
||||
'discussions' => [
|
||||
['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],
|
||||
],
|
||||
Post::class => [
|
||||
'posts' => [
|
||||
['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>'],
|
||||
@@ -541,40 +538,6 @@ 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,14 +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\Tags\Tag;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Flarum\User\User;
|
||||
|
||||
class TagMentionsTest extends TestCase
|
||||
{
|
||||
@@ -30,20 +26,20 @@ class TagMentionsTest extends TestCase
|
||||
$this->extension('flarum-tags', 'flarum-mentions');
|
||||
|
||||
$this->prepareDatabase([
|
||||
User::class => [
|
||||
'users' => [
|
||||
['id' => 3, 'username' => 'potato', 'email' => 'potato@machine.local', 'is_email_confirmed' => 1],
|
||||
['id' => 4, 'username' => 'toby', 'email' => 'toby@machine.local', 'is_email_confirmed' => 1],
|
||||
],
|
||||
Discussion::class => [
|
||||
'discussions' => [
|
||||
['id' => 2, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 3, 'first_post_id' => 4, 'comment_count' => 2],
|
||||
],
|
||||
Post::class => [
|
||||
'posts' => [
|
||||
['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>'],
|
||||
],
|
||||
Tag::class => [
|
||||
'tags' => [
|
||||
['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,10 +10,8 @@
|
||||
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;
|
||||
@@ -33,16 +31,16 @@ class UserMentionsTest extends TestCase
|
||||
$this->extension('flarum-mentions');
|
||||
|
||||
$this->prepareDatabase([
|
||||
User::class => [
|
||||
'users' => [
|
||||
$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],
|
||||
],
|
||||
Discussion::class => [
|
||||
'discussions' => [
|
||||
['id' => 2, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 3, 'first_post_id' => 4, 'comment_count' => 2],
|
||||
],
|
||||
Post::class => [
|
||||
'posts' => [
|
||||
['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>'],
|
||||
|
@@ -12,7 +12,6 @@ namespace Flarum\Nicknames;
|
||||
use Flarum\Api\Serializer\UserSerializer;
|
||||
use Flarum\Extend;
|
||||
use Flarum\Nicknames\Access\UserPolicy;
|
||||
use Flarum\Search\Database\DatabaseSearchDriver;
|
||||
use Flarum\User\Event\Saving;
|
||||
use Flarum\User\Search\UserSearcher;
|
||||
use Flarum\User\User;
|
||||
@@ -45,16 +44,15 @@ return [
|
||||
->default('flarum-nicknames.set_on_registration', true)
|
||||
->default('flarum-nicknames.min', 1)
|
||||
->default('flarum-nicknames.max', 150)
|
||||
->default('display_name_driver', 'username')
|
||||
->serializeToForum('displayNameDriver', 'display_name_driver')
|
||||
->serializeToForum('displayNameDriver', 'display_name_driver', null, 'username')
|
||||
->serializeToForum('setNicknameOnRegistration', 'flarum-nicknames.set_on_registration', 'boolval')
|
||||
->serializeToForum('randomizeUsernameOnRegistration', 'flarum-nicknames.random_username', 'boolval'),
|
||||
|
||||
(new Extend\Validator(UserValidator::class))
|
||||
->configure(AddNicknameValidation::class),
|
||||
|
||||
(new Extend\SearchDriver(DatabaseSearchDriver::class))
|
||||
->setFulltext(UserSearcher::class, NicknameFullTextFilter::class),
|
||||
(new Extend\SimpleFlarumSearch(UserSearcher::class))
|
||||
->setFullTextGambit(NicknameFullTextGambit::class),
|
||||
|
||||
(new Extend\Policy())
|
||||
->modelPolicy(User::class, UserPolicy::class),
|
||||
|
2
extensions/nicknames/js/dist/admin.js
generated
vendored
2
extensions/nicknames/js/dist/admin.js
generated
vendored
@@ -1,2 +1,2 @@
|
||||
(()=>{var e={n:n=>{var a=n&&n.__esModule?()=>n.default:()=>n;return e.d(a,{a}),a},d:(n,a)=>{for(var t in a)e.o(a,t)&&!e.o(n,t)&&Object.defineProperty(n,t,{enumerable:!0,get:a[t]})},o:(e,n)=>Object.prototype.hasOwnProperty.call(e,n),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},n={};(()=>{"use strict";e.r(n);const a=flarum.reg.get("core","admin/app");var t=e.n(a);const r=flarum.reg.get("core","common/components/Alert");var s=e.n(r);const i=flarum.reg.get("core","common/components/Link");var l=e.n(i);const o=flarum.reg.get("core","admin/components/BasicsPage");var c=e.n(o);const g=flarum.reg.get("core","common/utils/extractText");var u=e.n(g);const d=flarum.reg.get("core","common/extend");t().initializers.add("flarum/nicknames",(()=>{t().extensionData.for("flarum-nicknames").registerSetting((function(){if("nickname"!==t().data.settings.display_name_driver)return m("div",{className:"Form-group"},m(s(),{dismissible:!1},t().translator.trans("flarum-nicknames.admin.wrong_driver",{a:m(l(),{href:t().route("basics")})})))})).registerSetting({setting:"flarum-nicknames.set_on_registration",type:"boolean",label:t().translator.trans("flarum-nicknames.admin.settings.set_on_registration_label")}).registerSetting({setting:"flarum-nicknames.random_username",type:"boolean",label:t().translator.trans("flarum-nicknames.admin.settings.random_username_label"),help:t().translator.trans("flarum-nicknames.admin.settings.random_username_help")}).registerSetting({setting:"flarum-nicknames.unique",type:"boolean",label:t().translator.trans("flarum-nicknames.admin.settings.unique_label")}).registerSetting({setting:"flarum-nicknames.regex",type:"text",label:t().translator.trans("flarum-nicknames.admin.settings.regex_label")}).registerSetting({setting:"flarum-nicknames.min",type:"number",label:t().translator.trans("flarum-nicknames.admin.settings.min_label")}).registerSetting({setting:"flarum-nicknames.max",type:"number",label:t().translator.trans("flarum-nicknames.admin.settings.max_label")}).registerPermission({icon:"fas fa-user-tag",label:t().translator.trans("flarum-nicknames.admin.permissions.edit_own_nickname_label"),permission:"user.editOwnNickname"},"start"),(0,d.extend)(c().prototype,"driverLocale",(function(e){e.display_name.nickname=u()(t().translator.trans("flarum-nicknames.admin.basics.display_name_driver_options.nickname"))}))}))})(),module.exports=n})();
|
||||
(()=>{var e={n:n=>{var a=n&&n.__esModule?()=>n.default:()=>n;return e.d(a,{a}),a},d:(n,a)=>{for(var t in a)e.o(a,t)&&!e.o(n,t)&&Object.defineProperty(n,t,{enumerable:!0,get:a[t]})},o:(e,n)=>Object.prototype.hasOwnProperty.call(e,n),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},n={};(()=>{"use strict";e.r(n);const a=flarum.reg.get("core","admin/app");var t=e.n(a);const r=flarum.reg.get("core","common/components/Alert");var s=e.n(r);const i=flarum.reg.get("core","common/components/Link");var l=e.n(i);t().initializers.add("flarum/nicknames",(()=>{t().extensionData.for("flarum-nicknames").registerSetting((function(){if("nickname"!==t().data.settings.display_name_driver)return m("div",{className:"Form-group"},m(s(),{dismissible:!1},t().translator.trans("flarum-nicknames.admin.wrong_driver",{a:m(l(),{href:t().route("basics")})})))})).registerSetting({setting:"flarum-nicknames.set_on_registration",type:"boolean",label:t().translator.trans("flarum-nicknames.admin.settings.set_on_registration_label")}).registerSetting({setting:"flarum-nicknames.random_username",type:"boolean",label:t().translator.trans("flarum-nicknames.admin.settings.random_username_label"),help:t().translator.trans("flarum-nicknames.admin.settings.random_username_help")}).registerSetting({setting:"flarum-nicknames.unique",type:"boolean",label:t().translator.trans("flarum-nicknames.admin.settings.unique_label")}).registerSetting({setting:"flarum-nicknames.regex",type:"text",label:t().translator.trans("flarum-nicknames.admin.settings.regex_label")}).registerSetting({setting:"flarum-nicknames.min",type:"number",label:t().translator.trans("flarum-nicknames.admin.settings.min_label")}).registerSetting({setting:"flarum-nicknames.max",type:"number",label:t().translator.trans("flarum-nicknames.admin.settings.max_label")}).registerPermission({icon:"fas fa-user-tag",label:t().translator.trans("flarum-nicknames.admin.permissions.edit_own_nickname_label"),permission:"user.editOwnNickname"},"start")}))})(),module.exports=n})();
|
||||
//# sourceMappingURL=admin.js.map
|
2
extensions/nicknames/js/dist/admin.js.map
generated
vendored
2
extensions/nicknames/js/dist/admin.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/nicknames/js/dist/forum.js
generated
vendored
2
extensions/nicknames/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/nicknames/js/dist/forum.js.map
generated
vendored
2
extensions/nicknames/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -1,9 +1,6 @@
|
||||
import app from 'flarum/admin/app';
|
||||
import Alert from 'flarum/common/components/Alert';
|
||||
import Link from 'flarum/common/components/Link';
|
||||
import BasicsPage from 'flarum/admin/components/BasicsPage';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import { extend } from 'flarum/common/extend';
|
||||
|
||||
app.initializers.add('flarum/nicknames', () => {
|
||||
app.extensionData
|
||||
@@ -58,8 +55,4 @@ app.initializers.add('flarum/nicknames', () => {
|
||||
},
|
||||
'start'
|
||||
);
|
||||
|
||||
extend(BasicsPage.prototype, 'driverLocale', function (locale) {
|
||||
locale.display_name['nickname'] = extractText(app.translator.trans('flarum-nicknames.admin.basics.display_name_driver_options.nickname'));
|
||||
});
|
||||
});
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import FormModal from 'flarum/common/components/FormModal';
|
||||
import Modal from 'flarum/common/components/Modal';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
import Form from '@flarum/core/src/common/components/Form';
|
||||
|
||||
export default class NicknameModal extends FormModal {
|
||||
export default class NicknameModal extends Modal {
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
this.nickname = Stream(app.session.user.displayName());
|
||||
|
@@ -1,8 +1,5 @@
|
||||
flarum-nicknames:
|
||||
admin:
|
||||
basics:
|
||||
display_name_driver_options:
|
||||
nickname: Nickname
|
||||
permissions:
|
||||
edit_own_nickname_label: Edit own nickname
|
||||
settings:
|
||||
|
@@ -9,16 +9,19 @@
|
||||
|
||||
namespace Flarum\Nicknames;
|
||||
|
||||
use Flarum\Search\AbstractFulltextFilter;
|
||||
use Flarum\Search\Database\DatabaseSearchState;
|
||||
/*
|
||||
* 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 Flarum\Search\GambitInterface;
|
||||
use Flarum\Search\SearchState;
|
||||
use Flarum\User\UserRepository;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* @extends AbstractFulltextFilter<DatabaseSearchState>
|
||||
*/
|
||||
class NicknameFullTextFilter extends AbstractFulltextFilter
|
||||
class NicknameFullTextGambit implements GambitInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected UserRepository $users
|
||||
@@ -34,12 +37,14 @@ class NicknameFullTextFilter extends AbstractFulltextFilter
|
||||
->orWhere('nickname', 'like', "{$searchValue}%");
|
||||
}
|
||||
|
||||
public function search(SearchState $state, string $value): void
|
||||
public function apply(SearchState $search, string $bit): bool
|
||||
{
|
||||
$state->getQuery()
|
||||
$search->getQuery()
|
||||
->whereIn(
|
||||
'id',
|
||||
$this->getUserSearchSubQuery($value)
|
||||
$this->getUserSearchSubQuery($bit)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -27,7 +27,7 @@ class UpdateTest extends TestCase
|
||||
|
||||
$this->extension('flarum-nicknames');
|
||||
$this->prepareDatabase([
|
||||
User::class => [
|
||||
'users' => [
|
||||
$this->normalUser(),
|
||||
],
|
||||
]);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2024 Stichting Flarum (Flarum Foundation)
|
||||
Copyright (c) Sami Mazouz
|
||||
|
||||
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,18 +1,5 @@
|
||||
# Extension Manager
|
||||
# Package Manager
|
||||
|
||||
The extension manager is a tool that allows you to easily install and manage extensions. It runs [composer](https://getcomposer.org/) under the hood.
|
||||
*An Experiment.*
|
||||
|
||||
## 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).
|
||||
Read: https://github.com/flarum/package-manager/wiki
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "flarum/extension-manager",
|
||||
"description": "An extension manager to install, update and remove extension packages from the interface (Wrapper around composer).",
|
||||
"name": "flarum/package-manager",
|
||||
"description": "A Flarum Package Manager.",
|
||||
"keywords": [
|
||||
"extensions",
|
||||
"composer",
|
||||
@@ -18,12 +18,12 @@
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/flarum/framework/issues",
|
||||
"source": "https://github.com/flarum/extension-manager"
|
||||
"issues": "https://github.com/flarum/package-manager/issues",
|
||||
"source": "https://github.com/flarum/package-manager"
|
||||
},
|
||||
"require": {
|
||||
"flarum/core": "^2.0",
|
||||
"composer/composer": "^2.7"
|
||||
"composer/composer": "^2.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"flarum/testing": "^2.0",
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"extra": {
|
||||
"flarum-extension": {
|
||||
"title": "Extension Manager",
|
||||
"title": "Package Manager",
|
||||
"icon": {
|
||||
"name": "fas fa-box-open",
|
||||
"backgroundColor": "#117187",
|
||||
@@ -69,12 +69,12 @@
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Flarum\\ExtensionManager\\": "src/"
|
||||
"Flarum\\PackageManager\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Flarum\\ExtensionManager\\Tests\\": "tests/"
|
||||
"Flarum\\PackageManager\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
@@ -7,26 +7,32 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\ExtensionManager;
|
||||
namespace Flarum\PackageManager;
|
||||
|
||||
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('/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),
|
||||
->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),
|
||||
|
||||
(new Extend\Frontend('admin'))
|
||||
->css(__DIR__.'/less/admin.less')
|
||||
@@ -34,34 +40,31 @@ return [
|
||||
->content(function (Document $document) {
|
||||
$paths = resolve(Paths::class);
|
||||
|
||||
$document->payload['flarum-extension-manager.writable_dirs'] = is_writable($paths->vendor)
|
||||
$document->payload['flarum-package-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-extension-manager.using_sync_queue'] = resolve(Queue::class) instanceof SyncQueue;
|
||||
$document->payload['flarum-package-manager.using_sync_queue'] = resolve(Queue::class) instanceof SyncQueue;
|
||||
}),
|
||||
|
||||
new Extend\Locales(__DIR__.'/locale'),
|
||||
|
||||
(new Extend\Settings())
|
||||
->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),
|
||||
->default(LastUpdateCheck::key(), json_encode(LastUpdateCheck::default()))
|
||||
->default(LastUpdateRun::key(), json_encode(LastUpdateRun::default()))
|
||||
->default('flarum-package-manager.queue_jobs', false),
|
||||
|
||||
(new Extend\ServiceProvider)
|
||||
->register(ExtensionManagerServiceProvider::class),
|
||||
->register(PackageManagerServiceProvider::class),
|
||||
|
||||
(new Extend\ErrorHandling)
|
||||
->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)
|
||||
->handler(ComposerCommandFailedException::class, ExceptionHandler::class)
|
||||
->handler(ComposerRequireFailedException::class, ExceptionHandler::class)
|
||||
->handler(ComposerUpdateFailedException::class, ExceptionHandler::class)
|
||||
->handler(MajorUpdateFailedException::class, ExceptionHandler::class)
|
||||
->status('extension_already_installed', 409)
|
||||
->status('extension_not_installed', 409)
|
||||
->status('no_new_major_version', 409)
|
||||
->status('extension_not_directly_dependency', 409),
|
||||
->status('no_new_major_version', 409),
|
||||
];
|
||||
|
19
extensions/package-manager/js/dist-typings/components/AuthMethodModal.d.ts
generated
vendored
19
extensions/package-manager/js/dist-typings/components/AuthMethodModal.d.ts
generated
vendored
@@ -1,19 +0,0 @@
|
||||
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
10
extensions/package-manager/js/dist-typings/components/ConfigureAuth.d.ts
generated
vendored
@@ -1,10 +0,0 @@
|
||||
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
14
extensions/package-manager/js/dist-typings/components/ConfigureComposer.d.ts
generated
vendored
@@ -1,14 +0,0 @@
|
||||
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
24
extensions/package-manager/js/dist-typings/components/ConfigureJson.d.ts
generated
vendored
@@ -1,24 +0,0 @@
|
||||
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,10 +5,7 @@ import { UpdatedPackage } from '../states/ControlSectionState';
|
||||
export interface ExtensionItemAttrs extends ComponentAttrs {
|
||||
extension: Extension;
|
||||
updates: UpdatedPackage;
|
||||
onClickUpdate: CallableFunction | {
|
||||
soft: CallableFunction;
|
||||
hard: CallableFunction;
|
||||
};
|
||||
onClickUpdate: CallableFunction;
|
||||
whyNotWarning?: boolean;
|
||||
isCore?: boolean;
|
||||
updatable?: boolean;
|
||||
|
18
extensions/package-manager/js/dist-typings/components/RepositoryModal.d.ts
generated
vendored
18
extensions/package-manager/js/dist-typings/components/RepositoryModal.d.ts
generated
vendored
@@ -1,18 +0,0 @@
|
||||
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,7 +2,5 @@ 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/core/dist-typings/@types/translator-icu-rich" />
|
||||
/// <reference types="flarum/@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/core/dist-typings/@types/translator-icu-rich" />
|
||||
/// <reference types="flarum/@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,7 +6,6 @@ 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,8 +9,6 @@ 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 = {
|
||||
@@ -33,7 +31,7 @@ export declare type LastUpdateRun = {
|
||||
} & {
|
||||
limitedPackages: () => string[];
|
||||
};
|
||||
export declare type LoadingTypes = UpdaterLoadingTypes | InstallerLoadingTypes | MajorUpdaterLoadingTypes | 'queued-action';
|
||||
export declare type LoadingTypes = UpdaterLoadingTypes | InstallerLoadingTypes | MajorUpdaterLoadingTypes;
|
||||
export declare type CoreUpdate = {
|
||||
package: UpdatedPackage;
|
||||
extension: Extension;
|
||||
@@ -47,17 +45,13 @@ export default class ControlSectionState {
|
||||
get lastUpdateRun(): LastUpdateRun;
|
||||
constructor();
|
||||
isLoading(name?: LoadingTypes): boolean;
|
||||
hasOperationRunning(): boolean;
|
||||
isLoadingOtherThan(name: LoadingTypes): boolean;
|
||||
setLoading(name: LoadingTypes): void;
|
||||
requirePackage(data: any): void;
|
||||
checkForUpdates(): void;
|
||||
updateCoreMinor(): void;
|
||||
updateExtension(extension: Extension, updateMode: 'soft' | 'hard'): void;
|
||||
updateExtension(extension: Extension): 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
6
extensions/package-manager/js/dist-typings/states/ExtensionManagerState.d.ts
generated
vendored
@@ -1,6 +0,0 @@
|
||||
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,12 +1,11 @@
|
||||
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, actionTaken?: boolean): Promise<Task[]>;
|
||||
load(params?: ApiQueryParamsPlural): Promise<import("flarum/common/Store").ApiResponsePlural<Task>>;
|
||||
getItems(): Task[] | null;
|
||||
getTotalPages(): number;
|
||||
pageNumber(): number;
|
||||
@@ -14,6 +13,4 @@ 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/extension-manager",
|
||||
"name": "@flarum/package-manager",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"prettier": "@flarum/prettier-config",
|
||||
|
@@ -1,88 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
@@ -1,120 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@@ -1,115 +0,0 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,94 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@@ -6,7 +6,6 @@ import { ComponentAttrs } from 'flarum/common/Component';
|
||||
import Installer from './Installer';
|
||||
import Updater from './Updater';
|
||||
import Mithril from 'mithril';
|
||||
import Form from 'flarum/common/components/Form';
|
||||
|
||||
export default class ControlSection extends Component<ComponentAttrs> {
|
||||
oninit(vnode: Mithril.Vnode<ComponentAttrs, this>) {
|
||||
@@ -15,22 +14,22 @@ export default class ControlSection extends Component<ComponentAttrs> {
|
||||
|
||||
view() {
|
||||
return (
|
||||
<div className="ExtensionPage-permissions ExtensionManager-controlSection">
|
||||
<div className="ExtensionPage-permissions PackageManager-controlSection">
|
||||
<div className="ExtensionPage-permissions-header">
|
||||
<div className="container">
|
||||
<h2 className="ExtensionTitle">{app.translator.trans('flarum-extension-manager.admin.sections.control.title')}</h2>
|
||||
<h2 className="ExtensionTitle">{app.translator.trans('flarum-package-manager.admin.sections.control.title')}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container">
|
||||
{app.data['flarum-extension-manager.writable_dirs'] ? (
|
||||
<Form>
|
||||
{app.data['flarum-package-manager.writable_dirs'] ? (
|
||||
<>
|
||||
<Installer />
|
||||
<Updater />
|
||||
</Form>
|
||||
</>
|
||||
) : (
|
||||
<div className="Form-group">
|
||||
<Alert type="warning" dismissible={false}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.file_permissions')}
|
||||
{app.translator.trans('flarum-package-manager.admin.file_permissions')}
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user