mirror of
https://github.com/flarum/core.git
synced 2025-07-12 20:36:28 +02:00
chore: enable and set up prettier for flarum/tags
(#3496)
This commit is contained in:
2
.github/workflows/flarum-tags-frontend.yml
vendored
2
.github/workflows/flarum-tags-frontend.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
|||||||
uses: ./.github/workflows/REUSABLE_frontend.yml
|
uses: ./.github/workflows/REUSABLE_frontend.yml
|
||||||
with:
|
with:
|
||||||
enable_bundlewatch: false
|
enable_bundlewatch: false
|
||||||
enable_prettier: false
|
enable_prettier: true
|
||||||
enable_typescript: true
|
enable_typescript: true
|
||||||
|
|
||||||
frontend_directory: ./extensions/tags/js
|
frontend_directory: ./extensions/tags/js
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"name": "@flarum/tags",
|
"name": "@flarum/tags",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
"prettier": "@flarum/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"sortablejs": "^1.14.0"
|
"sortablejs": "^1.14.0"
|
||||||
},
|
},
|
||||||
@ -13,14 +14,17 @@
|
|||||||
"build-typings": "yarn run clean-typings && ([ -e src/@types ] && cp -r src/@types dist-typings/@types || true) && tsc && yarn run post-build-typings",
|
"build-typings": "yarn run clean-typings && ([ -e src/@types ] && cp -r src/@types dist-typings/@types || true) && tsc && yarn run post-build-typings",
|
||||||
"post-build-typings": "find dist-typings -type f -name '*.d.ts' -print0 | xargs -0 sed -i 's,../src/@types,@types,g'",
|
"post-build-typings": "find dist-typings -type f -name '*.d.ts' -print0 | xargs -0 sed -i 's,../src/@types,@types,g'",
|
||||||
"check-typings": "tsc --noEmit --emitDeclarationOnly false",
|
"check-typings": "tsc --noEmit --emitDeclarationOnly false",
|
||||||
"check-typings-coverage": "typescript-coverage-report"
|
"check-typings-coverage": "typescript-coverage-report",
|
||||||
|
"format": "prettier --write src",
|
||||||
|
"format-check": "prettier --check src"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"flarum-webpack-config": "^2.0.0",
|
|
||||||
"webpack": "^5.65.0",
|
|
||||||
"webpack-cli": "^4.9.1",
|
|
||||||
"flarum-tsconfig": "^1.0.2",
|
"flarum-tsconfig": "^1.0.2",
|
||||||
|
"flarum-webpack-config": "^2.0.0",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
"typescript-coverage-report": "^0.6.1"
|
"typescript-coverage-report": "^0.6.1",
|
||||||
|
"webpack": "^5.65.0",
|
||||||
|
"webpack-cli": "^4.9.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
extensions/tags/js/src/@types/shims.d.ts
vendored
4
extensions/tags/js/src/@types/shims.d.ts
vendored
@ -1,5 +1,5 @@
|
|||||||
import type Tag from "../common/models/Tag";
|
import type Tag from '../common/models/Tag';
|
||||||
import type TagListState from "../forum/states/TagListState";
|
import type TagListState from '../forum/states/TagListState';
|
||||||
|
|
||||||
declare module 'flarum/forum/routes' {
|
declare module 'flarum/forum/routes' {
|
||||||
export interface ForumRoutes {
|
export interface ForumRoutes {
|
||||||
|
@ -2,26 +2,30 @@ import { extend } from 'flarum/common/extend';
|
|||||||
import PermissionGrid from 'flarum/admin/components/PermissionGrid';
|
import PermissionGrid from 'flarum/admin/components/PermissionGrid';
|
||||||
import SettingDropdown from 'flarum/admin/components/SettingDropdown';
|
import SettingDropdown from 'flarum/admin/components/SettingDropdown';
|
||||||
|
|
||||||
export default function() {
|
export default function () {
|
||||||
extend(PermissionGrid.prototype, 'startItems', items => {
|
extend(PermissionGrid.prototype, 'startItems', (items) => {
|
||||||
items.add('allowTagChange', {
|
items.add(
|
||||||
icon: 'fas fa-tag',
|
'allowTagChange',
|
||||||
label: app.translator.trans('flarum-tags.admin.permissions.allow_edit_tags_label'),
|
{
|
||||||
setting: () => {
|
icon: 'fas fa-tag',
|
||||||
const minutes = parseInt(app.data.settings.allow_tag_change, 10);
|
label: app.translator.trans('flarum-tags.admin.permissions.allow_edit_tags_label'),
|
||||||
|
setting: () => {
|
||||||
|
const minutes = parseInt(app.data.settings.allow_tag_change, 10);
|
||||||
|
|
||||||
return SettingDropdown.component({
|
return SettingDropdown.component({
|
||||||
defaultLabel: minutes
|
defaultLabel: minutes
|
||||||
? app.translator.trans('core.admin.permissions_controls.allow_some_minutes_button', {count: minutes})
|
? app.translator.trans('core.admin.permissions_controls.allow_some_minutes_button', { count: minutes })
|
||||||
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
|
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
|
||||||
key: 'allow_tag_change',
|
key: 'allow_tag_change',
|
||||||
options: [
|
options: [
|
||||||
{value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button')},
|
{ value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button') },
|
||||||
{value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button')},
|
{ value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button') },
|
||||||
{value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button')}
|
{ value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button') },
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
}, 90);
|
},
|
||||||
|
90
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
export default function () {
|
export default function () {
|
||||||
app.extensionData
|
app.extensionData
|
||||||
.for('flarum-tags')
|
.for('flarum-tags')
|
||||||
.registerPermission({
|
.registerPermission(
|
||||||
icon: 'fas fa-tag',
|
{
|
||||||
label: app.translator.trans('flarum-tags.admin.permissions.tag_discussions_label'),
|
icon: 'fas fa-tag',
|
||||||
permission: 'discussion.tag',
|
label: app.translator.trans('flarum-tags.admin.permissions.tag_discussions_label'),
|
||||||
}, 'moderate', 95)
|
permission: 'discussion.tag',
|
||||||
.registerPermission({
|
},
|
||||||
icon: 'fas fa-tags',
|
'moderate',
|
||||||
label: app.translator.trans('flarum-tags.admin.permissions.bypass_tag_counts_label'),
|
95
|
||||||
permission: 'bypassTagCounts',
|
)
|
||||||
}, 'start', 89);
|
.registerPermission(
|
||||||
|
{
|
||||||
|
icon: 'fas fa-tags',
|
||||||
|
label: app.translator.trans('flarum-tags.admin.permissions.bypass_tag_counts_label'),
|
||||||
|
permission: 'bypassTagCounts',
|
||||||
|
},
|
||||||
|
'start',
|
||||||
|
89
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { extend } from 'flarum/common/extend';
|
import { extend } from 'flarum/common/extend';
|
||||||
import BasicsPage from 'flarum/admin/components/BasicsPage';
|
import BasicsPage from 'flarum/admin/components/BasicsPage';
|
||||||
|
|
||||||
export default function() {
|
export default function () {
|
||||||
extend(BasicsPage.prototype, 'homePageItems', items => {
|
extend(BasicsPage.prototype, 'homePageItems', (items) => {
|
||||||
items.add('tags', {
|
items.add('tags', {
|
||||||
path: '/tags',
|
path: '/tags',
|
||||||
label: app.translator.trans('flarum-tags.admin.basics.tags_label')
|
label: app.translator.trans('flarum-tags.admin.basics.tags_label'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,10 @@ import tagIcon from '../common/helpers/tagIcon';
|
|||||||
import sortTags from '../common/utils/sortTags';
|
import sortTags from '../common/utils/sortTags';
|
||||||
import Tag from '../common/models/Tag';
|
import Tag from '../common/models/Tag';
|
||||||
|
|
||||||
export default function() {
|
export default function () {
|
||||||
extend(PermissionGrid.prototype, 'oninit', function () {
|
extend(PermissionGrid.prototype, 'oninit', function () {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
})
|
});
|
||||||
|
|
||||||
extend(PermissionGrid.prototype, 'oncreate', function () {
|
extend(PermissionGrid.prototype, 'oncreate', function () {
|
||||||
app.store.find<Tag[]>('tags', {}).then(() => {
|
app.store.find<Tag[]>('tags', {}).then(() => {
|
||||||
@ -30,7 +30,7 @@ export default function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return original(vnode);
|
return original(vnode);
|
||||||
})
|
});
|
||||||
|
|
||||||
override(app, 'getRequiredPermissions', (original, permission) => {
|
override(app, 'getRequiredPermissions', (original, permission) => {
|
||||||
const tagPrefix = permission.match(/^tag\d+\./);
|
const tagPrefix = permission.match(/^tag\d+\./);
|
||||||
@ -40,45 +40,60 @@ export default function() {
|
|||||||
|
|
||||||
const required = original(globalPermission);
|
const required = original(globalPermission);
|
||||||
|
|
||||||
return required.map(required => tagPrefix[0] + required);
|
return required.map((required) => tagPrefix[0] + required);
|
||||||
}
|
}
|
||||||
|
|
||||||
return original(permission);
|
return original(permission);
|
||||||
});
|
});
|
||||||
|
|
||||||
extend(PermissionGrid.prototype, 'scopeItems', items => {
|
extend(PermissionGrid.prototype, 'scopeItems', (items) => {
|
||||||
sortTags(app.store.all('tags'))
|
sortTags(app.store.all('tags'))
|
||||||
.filter(tag => tag.isRestricted())
|
.filter((tag) => tag.isRestricted())
|
||||||
.forEach(tag => items.add('tag' + tag.id(), {
|
.forEach((tag) =>
|
||||||
label: tagLabel(tag),
|
items.add('tag' + tag.id(), {
|
||||||
onremove: () => tag.save({isRestricted: false}),
|
label: tagLabel(tag),
|
||||||
render: item => {
|
onremove: () => tag.save({ isRestricted: false }),
|
||||||
if ('setting' in item) return '';
|
render: (item) => {
|
||||||
|
if ('setting' in item) return '';
|
||||||
|
|
||||||
if (item.permission === 'viewForum'
|
if (
|
||||||
|| item.permission === 'startDiscussion'
|
item.permission === 'viewForum' ||
|
||||||
|| (item.permission && item.permission.indexOf('discussion.') === 0 && item.tagScoped !== false)
|
item.permission === 'startDiscussion' ||
|
||||||
|| item.tagScoped) {
|
(item.permission && item.permission.indexOf('discussion.') === 0 && item.tagScoped !== false) ||
|
||||||
return PermissionDropdown.component({
|
item.tagScoped
|
||||||
permission: 'tag' + tag.id() + '.' + item.permission,
|
) {
|
||||||
allowGuest: item.allowGuest
|
return PermissionDropdown.component({
|
||||||
});
|
permission: 'tag' + tag.id() + '.' + item.permission,
|
||||||
}
|
allowGuest: item.allowGuest,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
},
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
extend(PermissionGrid.prototype, 'scopeControlItems', items => {
|
extend(PermissionGrid.prototype, 'scopeControlItems', (items) => {
|
||||||
const tags = sortTags(app.store.all<Tag>('tags').filter(tag => !tag.isRestricted()));
|
const tags = sortTags(app.store.all<Tag>('tags').filter((tag) => !tag.isRestricted()));
|
||||||
|
|
||||||
if (tags.length) {
|
if (tags.length) {
|
||||||
items.add('tag', <Dropdown className='Dropdown--restrictByTag' buttonClassName='Button Button--text' label={app.translator.trans('flarum-tags.admin.permissions.restrict_by_tag_heading')} icon='fas fa-plus' caretIcon={null}>
|
items.add(
|
||||||
{tags.map(tag => <Button icon={true} onclick={() => tag.save({ isRestricted: true })}>
|
'tag',
|
||||||
{[tagIcon(tag, { className: 'Button-icon' }), ' ', tag.name()]}
|
<Dropdown
|
||||||
</Button>)}
|
className="Dropdown--restrictByTag"
|
||||||
</Dropdown>);
|
buttonClassName="Button Button--text"
|
||||||
|
label={app.translator.trans('flarum-tags.admin.permissions.restrict_by_tag_heading')}
|
||||||
|
icon="fas fa-plus"
|
||||||
|
caretIcon={null}
|
||||||
|
>
|
||||||
|
{tags.map((tag) => (
|
||||||
|
<Button icon={true} onclick={() => tag.save({ isRestricted: true })}>
|
||||||
|
{[tagIcon(tag, { className: 'Button-icon' }), ' ', tag.name()]}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -59,9 +59,7 @@ export default class EditTagModal extends Modal<EditTagModalAttrs> {
|
|||||||
content() {
|
content() {
|
||||||
return (
|
return (
|
||||||
<div className="Modal-body">
|
<div className="Modal-body">
|
||||||
<div className="Form">
|
<div className="Form">{this.fields().toArray()}</div>
|
||||||
{this.fields().toArray()}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -69,59 +67,97 @@ export default class EditTagModal extends Modal<EditTagModalAttrs> {
|
|||||||
fields() {
|
fields() {
|
||||||
const items = new ItemList();
|
const items = new ItemList();
|
||||||
|
|
||||||
items.add('name', <div className="Form-group">
|
items.add(
|
||||||
<label>{app.translator.trans('flarum-tags.admin.edit_tag.name_label')}</label>
|
'name',
|
||||||
<input className="FormControl" placeholder={app.translator.trans('flarum-tags.admin.edit_tag.name_placeholder')} value={this.name()} oninput={(e: InputEvent) => {
|
<div className="Form-group">
|
||||||
const target = e.target as HTMLInputElement;
|
<label>{app.translator.trans('flarum-tags.admin.edit_tag.name_label')}</label>
|
||||||
this.name(target.value);
|
<input
|
||||||
this.slug(slug(target.value));
|
className="FormControl"
|
||||||
}} />
|
placeholder={app.translator.trans('flarum-tags.admin.edit_tag.name_placeholder')}
|
||||||
</div>, 50);
|
value={this.name()}
|
||||||
|
oninput={(e: InputEvent) => {
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
|
this.name(target.value);
|
||||||
|
this.slug(slug(target.value));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
50
|
||||||
|
);
|
||||||
|
|
||||||
items.add('slug', <div className="Form-group">
|
items.add(
|
||||||
<label>{app.translator.trans('flarum-tags.admin.edit_tag.slug_label')}</label>
|
'slug',
|
||||||
<input className="FormControl" bidi={this.slug} />
|
<div className="Form-group">
|
||||||
</div>, 40);
|
<label>{app.translator.trans('flarum-tags.admin.edit_tag.slug_label')}</label>
|
||||||
|
<input className="FormControl" bidi={this.slug} />
|
||||||
|
</div>,
|
||||||
|
40
|
||||||
|
);
|
||||||
|
|
||||||
items.add('description', <div className="Form-group">
|
items.add(
|
||||||
<label>{app.translator.trans('flarum-tags.admin.edit_tag.description_label')}</label>
|
'description',
|
||||||
<textarea className="FormControl" bidi={this.description} />
|
<div className="Form-group">
|
||||||
</div>, 30);
|
<label>{app.translator.trans('flarum-tags.admin.edit_tag.description_label')}</label>
|
||||||
|
<textarea className="FormControl" bidi={this.description} />
|
||||||
|
</div>,
|
||||||
|
30
|
||||||
|
);
|
||||||
|
|
||||||
items.add('color', <div className="Form-group">
|
items.add(
|
||||||
<label>{app.translator.trans('flarum-tags.admin.edit_tag.color_label')}</label>
|
'color',
|
||||||
<ColorPreviewInput className="FormControl" placeholder="#aaaaaa" bidi={this.color} />
|
<div className="Form-group">
|
||||||
</div>, 20);
|
<label>{app.translator.trans('flarum-tags.admin.edit_tag.color_label')}</label>
|
||||||
|
<ColorPreviewInput className="FormControl" placeholder="#aaaaaa" bidi={this.color} />
|
||||||
|
</div>,
|
||||||
|
20
|
||||||
|
);
|
||||||
|
|
||||||
items.add('icon', <div className="Form-group">
|
items.add(
|
||||||
<label>{app.translator.trans('flarum-tags.admin.edit_tag.icon_label')}</label>
|
'icon',
|
||||||
<div className="helpText">
|
<div className="Form-group">
|
||||||
{app.translator.trans('flarum-tags.admin.edit_tag.icon_text', { a: <a href="https://fontawesome.com/icons?m=free" tabindex="-1" /> })}
|
<label>{app.translator.trans('flarum-tags.admin.edit_tag.icon_label')}</label>
|
||||||
</div>
|
<div className="helpText">
|
||||||
<input className="FormControl" placeholder="fas fa-bolt" bidi={this.icon} />
|
{app.translator.trans('flarum-tags.admin.edit_tag.icon_text', { a: <a href="https://fontawesome.com/icons?m=free" tabindex="-1" /> })}
|
||||||
</div>, 10);
|
</div>
|
||||||
|
<input className="FormControl" placeholder="fas fa-bolt" bidi={this.icon} />
|
||||||
|
</div>,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
items.add('hidden', <div className="Form-group">
|
items.add(
|
||||||
<div>
|
'hidden',
|
||||||
<label className="checkbox">
|
<div className="Form-group">
|
||||||
<input type="checkbox" bidi={this.isHidden} />
|
<div>
|
||||||
{app.translator.trans('flarum-tags.admin.edit_tag.hide_label')}
|
<label className="checkbox">
|
||||||
</label>
|
<input type="checkbox" bidi={this.isHidden} />
|
||||||
</div>
|
{app.translator.trans('flarum-tags.admin.edit_tag.hide_label')}
|
||||||
</div>, 10);
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
items.add('submit', <div className="Form-group">
|
items.add(
|
||||||
{Button.component({
|
'submit',
|
||||||
type: 'submit',
|
<div className="Form-group">
|
||||||
className: 'Button Button--primary EditTagModal-save',
|
{Button.component(
|
||||||
loading: this.loading,
|
{
|
||||||
}, app.translator.trans('flarum-tags.admin.edit_tag.submit_button'))}
|
type: 'submit',
|
||||||
{this.tag.exists ? (
|
className: 'Button Button--primary EditTagModal-save',
|
||||||
<button type="button" className="Button EditTagModal-delete" onclick={this.delete.bind(this)}>
|
loading: this.loading,
|
||||||
{app.translator.trans('flarum-tags.admin.edit_tag.delete_tag_button')}
|
},
|
||||||
</button>
|
app.translator.trans('flarum-tags.admin.edit_tag.submit_button')
|
||||||
) : ''}
|
)}
|
||||||
</div>, -10);
|
{this.tag.exists ? (
|
||||||
|
<button type="button" className="Button EditTagModal-delete" onclick={this.delete.bind(this)}>
|
||||||
|
{app.translator.trans('flarum-tags.admin.edit_tag.delete_tag_button')}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</div>,
|
||||||
|
-10
|
||||||
|
);
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
@ -147,20 +183,22 @@ export default class EditTagModal extends Modal<EditTagModalAttrs> {
|
|||||||
// This is done for better error visibility on smaller screen heights.
|
// This is done for better error visibility on smaller screen heights.
|
||||||
this.tag.save(this.submitData()).then(
|
this.tag.save(this.submitData()).then(
|
||||||
() => this.hide(),
|
() => this.hide(),
|
||||||
() => this.loading = false
|
() => (this.loading = false)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete() {
|
delete() {
|
||||||
if (confirm(extractText(app.translator.trans('flarum-tags.admin.edit_tag.delete_tag_confirmation')))) {
|
if (confirm(extractText(app.translator.trans('flarum-tags.admin.edit_tag.delete_tag_confirmation')))) {
|
||||||
const children = app.store.all<Tag>('tags').filter(tag => tag.parent() === this.tag);
|
const children = app.store.all<Tag>('tags').filter((tag) => tag.parent() === this.tag);
|
||||||
|
|
||||||
this.tag.delete().then(() => {
|
this.tag.delete().then(() => {
|
||||||
children.forEach(tag => tag.pushData({
|
children.forEach((tag) =>
|
||||||
attributes: { isChild: false },
|
tag.pushData({
|
||||||
// @deprecated. Temporary hack for type safety, remove before v1.3.
|
attributes: { isChild: false },
|
||||||
relationships: { parent: null as any as [] }
|
// @deprecated. Temporary hack for type safety, remove before v1.3.
|
||||||
}));
|
relationships: { parent: null as any as [] },
|
||||||
|
})
|
||||||
|
);
|
||||||
m.redraw();
|
m.redraw();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import sortable from 'sortablejs';
|
import sortable from 'sortablejs';
|
||||||
|
|
||||||
|
import app from 'flarum/admin/app';
|
||||||
import ExtensionPage from 'flarum/admin/components/ExtensionPage';
|
import ExtensionPage from 'flarum/admin/components/ExtensionPage';
|
||||||
import Button from 'flarum/common/components/Button';
|
import Button from 'flarum/common/components/Button';
|
||||||
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
||||||
@ -18,16 +19,18 @@ function tagItem(tag) {
|
|||||||
{Button.component({
|
{Button.component({
|
||||||
className: 'Button Button--link',
|
className: 'Button Button--link',
|
||||||
icon: 'fas fa-pencil-alt',
|
icon: 'fas fa-pencil-alt',
|
||||||
onclick: () => app.modal.show(EditTagModal, { model: tag })
|
onclick: () => app.modal.show(EditTagModal, { model: tag }),
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{!tag.isChild() && tag.position() !== null ? (
|
{!tag.isChild() && tag.position() !== null ? (
|
||||||
<ol className="TagListItem-children TagList">
|
<ol className="TagListItem-children TagList">
|
||||||
{sortTags(app.store.all('tags'))
|
{sortTags(app.store.all('tags'))
|
||||||
.filter(child => child.parent() === tag)
|
.filter((child) => child.parent() === tag)
|
||||||
.map(tagItem)}
|
.map(tagItem)}
|
||||||
</ol>
|
</ol>
|
||||||
) : ''}
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -62,81 +65,78 @@ export default class TagsPage extends ExtensionPage {
|
|||||||
const minSecondaryTags = this.setting('flarum-tags.min_secondary_tags', 0);
|
const minSecondaryTags = this.setting('flarum-tags.min_secondary_tags', 0);
|
||||||
const maxSecondaryTags = this.setting('flarum-tags.max_secondary_tags', 0);
|
const maxSecondaryTags = this.setting('flarum-tags.max_secondary_tags', 0);
|
||||||
|
|
||||||
const tags = sortTags(app.store.all('tags').filter(tag => !tag.parent()));
|
const tags = sortTags(app.store.all('tags').filter((tag) => !tag.parent()));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="TagsContent">
|
<div className="TagsContent">
|
||||||
<div className="TagsContent-list">
|
<div className="TagsContent-list">
|
||||||
<div className="container" key={this.forcedRefreshKey} oncreate={this.onListOnCreate.bind(this)}><div className="SettingsGroups">
|
<div className="container" key={this.forcedRefreshKey} oncreate={this.onListOnCreate.bind(this)}>
|
||||||
<div className="TagGroup">
|
<div className="SettingsGroups">
|
||||||
<label>{app.translator.trans('flarum-tags.admin.tags.primary_heading')}</label>
|
<div className="TagGroup">
|
||||||
<ol className="TagList TagList--primary">
|
<label>{app.translator.trans('flarum-tags.admin.tags.primary_heading')}</label>
|
||||||
{tags
|
<ol className="TagList TagList--primary">{tags.filter((tag) => tag.position() !== null && !tag.isChild()).map(tagItem)}</ol>
|
||||||
.filter(tag => tag.position() !== null && !tag.isChild())
|
{Button.component(
|
||||||
.map(tagItem)}
|
{
|
||||||
</ol>
|
className: 'Button TagList-button',
|
||||||
{Button.component(
|
icon: 'fas fa-plus',
|
||||||
{
|
onclick: () => app.modal.show(EditTagModal, { primary: true }),
|
||||||
className: 'Button TagList-button',
|
},
|
||||||
icon: 'fas fa-plus',
|
app.translator.trans('flarum-tags.admin.tags.create_primary_tag_button')
|
||||||
onclick: () => app.modal.show(EditTagModal, { primary: true }),
|
)}
|
||||||
},
|
</div>
|
||||||
app.translator.trans('flarum-tags.admin.tags.create_primary_tag_button')
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="TagGroup TagGroup--secondary">
|
<div className="TagGroup TagGroup--secondary">
|
||||||
<label>{app.translator.trans('flarum-tags.admin.tags.secondary_heading')}</label>
|
<label>{app.translator.trans('flarum-tags.admin.tags.secondary_heading')}</label>
|
||||||
<ul className="TagList">
|
<ul className="TagList">
|
||||||
{tags
|
{tags
|
||||||
.filter(tag => tag.position() === null)
|
.filter((tag) => tag.position() === null)
|
||||||
.sort((a, b) => a.name().localeCompare(b.name()))
|
.sort((a, b) => a.name().localeCompare(b.name()))
|
||||||
.map(tagItem)}
|
.map(tagItem)}
|
||||||
</ul>
|
</ul>
|
||||||
{Button.component(
|
{Button.component(
|
||||||
{
|
{
|
||||||
className: 'Button TagList-button',
|
className: 'Button TagList-button',
|
||||||
icon: 'fas fa-plus',
|
icon: 'fas fa-plus',
|
||||||
onclick: () => app.modal.show(EditTagModal, { primary: false }),
|
onclick: () => app.modal.show(EditTagModal, { primary: false }),
|
||||||
},
|
},
|
||||||
app.translator.trans('flarum-tags.admin.tags.create_secondary_tag_button')
|
app.translator.trans('flarum-tags.admin.tags.create_secondary_tag_button')
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
<div className="Form">
|
|
||||||
<label>{app.translator.trans('flarum-tags.admin.tags.settings_heading')}</label>
|
|
||||||
<div className="Form-group">
|
|
||||||
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_heading')}</label>
|
|
||||||
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_text')}</div>
|
|
||||||
<div className="TagSettings-rangeInput">
|
|
||||||
<input
|
|
||||||
className="FormControl"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
value={minPrimaryTags()}
|
|
||||||
oninput={withAttr('value', this.setMinTags.bind(this, minPrimaryTags, maxPrimaryTags))}
|
|
||||||
/>
|
|
||||||
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
|
|
||||||
<input className="FormControl" type="number" min={minPrimaryTags()} bidi={maxPrimaryTags} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="Form-group">
|
<div className="Form">
|
||||||
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_heading')}</label>
|
<label>{app.translator.trans('flarum-tags.admin.tags.settings_heading')}</label>
|
||||||
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_text')}</div>
|
<div className="Form-group">
|
||||||
<div className="TagSettings-rangeInput">
|
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_heading')}</label>
|
||||||
<input
|
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_text')}</div>
|
||||||
className="FormControl"
|
<div className="TagSettings-rangeInput">
|
||||||
type="number"
|
<input
|
||||||
min="0"
|
className="FormControl"
|
||||||
value={minSecondaryTags()}
|
type="number"
|
||||||
oninput={withAttr('value', this.setMinTags.bind(this, minSecondaryTags, maxSecondaryTags))}
|
min="0"
|
||||||
/>
|
value={minPrimaryTags()}
|
||||||
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
|
oninput={withAttr('value', this.setMinTags.bind(this, minPrimaryTags, maxPrimaryTags))}
|
||||||
<input className="FormControl" type="number" min={minSecondaryTags()} bidi={maxSecondaryTags} />
|
/>
|
||||||
|
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
|
||||||
|
<input className="FormControl" type="number" min={minPrimaryTags()} bidi={maxPrimaryTags} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="Form-group">
|
||||||
|
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_heading')}</label>
|
||||||
|
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_text')}</div>
|
||||||
|
<div className="TagSettings-rangeInput">
|
||||||
|
<input
|
||||||
|
className="FormControl"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
value={minSecondaryTags()}
|
||||||
|
oninput={withAttr('value', this.setMinTags.bind(this, minSecondaryTags, maxSecondaryTags))}
|
||||||
|
/>
|
||||||
|
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
|
||||||
|
<input className="FormControl" type="number" min={minSecondaryTags()} bidi={maxSecondaryTags} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="Form-group">{this.submitButton()}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="Form-group">{this.submitButton()}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="TagsContent-footer">
|
<div className="TagsContent-footer">
|
||||||
<p>{app.translator.trans('flarum-tags.admin.tags.about_tags_text')}</p>
|
<p>{app.translator.trans('flarum-tags.admin.tags.about_tags_text')}</p>
|
||||||
</div>
|
</div>
|
||||||
@ -147,19 +147,21 @@ export default class TagsPage extends ExtensionPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onListOnCreate(vnode) {
|
onListOnCreate(vnode) {
|
||||||
this.$('.TagList').get().map(e => {
|
this.$('.TagList')
|
||||||
sortable.create(e, {
|
.get()
|
||||||
group: 'tags',
|
.map((e) => {
|
||||||
delay: 50,
|
sortable.create(e, {
|
||||||
delayOnTouchOnly: true,
|
group: 'tags',
|
||||||
touchStartThreshold: 5,
|
delay: 50,
|
||||||
animation: 150,
|
delayOnTouchOnly: true,
|
||||||
swapThreshold: 0.65,
|
touchStartThreshold: 5,
|
||||||
dragClass: 'sortable-dragging',
|
animation: 150,
|
||||||
ghostClass: 'sortable-placeholder',
|
swapThreshold: 0.65,
|
||||||
onSort: (e) => this.onSortUpdate(e)
|
dragClass: 'sortable-dragging',
|
||||||
})
|
ghostClass: 'sortable-placeholder',
|
||||||
});
|
onSort: (e) => this.onSortUpdate(e),
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setMinTags(minTags, maxTags, value) {
|
setMinTags(minTags, maxTags, value) {
|
||||||
@ -175,9 +177,9 @@ export default class TagsPage extends ExtensionPage {
|
|||||||
app.store.getById('tags', e.item.getAttribute('data-id')).pushData({
|
app.store.getById('tags', e.item.getAttribute('data-id')).pushData({
|
||||||
attributes: {
|
attributes: {
|
||||||
position: null,
|
position: null,
|
||||||
isChild: false
|
isChild: false,
|
||||||
},
|
},
|
||||||
relationships: { parent: null }
|
relationships: { parent: null },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,12 +189,15 @@ export default class TagsPage extends ExtensionPage {
|
|||||||
.map(function () {
|
.map(function () {
|
||||||
return {
|
return {
|
||||||
id: $(this).data('id'),
|
id: $(this).data('id'),
|
||||||
children: $(this).find('li')
|
children: $(this)
|
||||||
|
.find('li')
|
||||||
.map(function () {
|
.map(function () {
|
||||||
return $(this).data('id');
|
return $(this).data('id');
|
||||||
}).get()
|
})
|
||||||
|
.get(),
|
||||||
};
|
};
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
// Now that we have an accurate representation of the order which the
|
// Now that we have an accurate representation of the order which the
|
||||||
// primary tags are in, we will update the tag attributes in our local
|
// primary tags are in, we will update the tag attributes in our local
|
||||||
@ -202,18 +207,18 @@ export default class TagsPage extends ExtensionPage {
|
|||||||
parent.pushData({
|
parent.pushData({
|
||||||
attributes: {
|
attributes: {
|
||||||
position: i,
|
position: i,
|
||||||
isChild: false
|
isChild: false,
|
||||||
},
|
},
|
||||||
relationships: { parent: null }
|
relationships: { parent: null },
|
||||||
});
|
});
|
||||||
|
|
||||||
tag.children.forEach((child, j) => {
|
tag.children.forEach((child, j) => {
|
||||||
app.store.getById('tags', child).pushData({
|
app.store.getById('tags', child).pushData({
|
||||||
attributes: {
|
attributes: {
|
||||||
position: j,
|
position: j,
|
||||||
isChild: true
|
isChild: true,
|
||||||
},
|
},
|
||||||
relationships: { parent }
|
relationships: { parent },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -221,7 +226,7 @@ export default class TagsPage extends ExtensionPage {
|
|||||||
app.request({
|
app.request({
|
||||||
url: app.forum.attribute('apiUrl') + '/tags/order',
|
url: app.forum.attribute('apiUrl') + '/tags/order',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { order }
|
body: { order },
|
||||||
});
|
});
|
||||||
|
|
||||||
this.forcedRefreshKey++;
|
this.forcedRefreshKey++;
|
||||||
|
@ -6,7 +6,7 @@ import addTagsHomePageOption from './addTagsHomePageOption';
|
|||||||
import addTagChangePermission from './addTagChangePermission';
|
import addTagChangePermission from './addTagChangePermission';
|
||||||
import TagsPage from './components/TagsPage';
|
import TagsPage from './components/TagsPage';
|
||||||
|
|
||||||
app.initializers.add('flarum-tags', app => {
|
app.initializers.add('flarum-tags', (app) => {
|
||||||
app.store.models.tags = Tag;
|
app.store.models.tags = Tag;
|
||||||
|
|
||||||
app.extensionData.for('flarum-tags').registerPage(TagsPage);
|
app.extensionData.for('flarum-tags').registerPage(TagsPage);
|
||||||
@ -17,7 +17,6 @@ app.initializers.add('flarum-tags', app => {
|
|||||||
addTagChangePermission();
|
addTagChangePermission();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Expose compat API
|
// Expose compat API
|
||||||
import tagsCompat from './compat';
|
import tagsCompat from './compat';
|
||||||
import { compat } from '@flarum/core/admin';
|
import { compat } from '@flarum/core/admin';
|
||||||
|
@ -9,5 +9,5 @@ export default {
|
|||||||
'tags/models/Tag': Tag,
|
'tags/models/Tag': Tag,
|
||||||
'tags/helpers/tagsLabel': tagsLabel,
|
'tags/helpers/tagsLabel': tagsLabel,
|
||||||
'tags/helpers/tagIcon': tagIcon,
|
'tags/helpers/tagIcon': tagIcon,
|
||||||
'tags/helpers/tagLabel': tagLabel
|
'tags/helpers/tagLabel': tagLabel,
|
||||||
};
|
};
|
||||||
|
@ -4,11 +4,7 @@ export default function tagIcon(tag, attrs = {}, settings = {}) {
|
|||||||
const hasIcon = tag && tag.icon();
|
const hasIcon = tag && tag.icon();
|
||||||
const { useColor = true } = settings;
|
const { useColor = true } = settings;
|
||||||
|
|
||||||
attrs.className = classList([
|
attrs.className = classList([attrs.className, 'icon', hasIcon ? tag.icon() : 'TagIcon']);
|
||||||
attrs.className,
|
|
||||||
'icon',
|
|
||||||
hasIcon ? tag.icon() : 'TagIcon'
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (tag && useColor) {
|
if (tag && useColor) {
|
||||||
attrs.style = attrs.style || {};
|
attrs.style = attrs.style || {};
|
||||||
@ -21,5 +17,5 @@ export default function tagIcon(tag, attrs = {}, settings = {}) {
|
|||||||
attrs.className += ' untagged';
|
attrs.className += ' untagged';
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasIcon ? <i {...attrs}/> : <span {...attrs}/>;
|
return hasIcon ? <i {...attrs} /> : <span {...attrs} />;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ export default function tagLabel(tag, attrs = {}) {
|
|||||||
|
|
||||||
if (link) {
|
if (link) {
|
||||||
attrs.title = tag.description() || '';
|
attrs.title = tag.description() || '';
|
||||||
attrs.href = app.route('tag', {tags: tag.slug()});
|
attrs.href = app.route('tag', { tags: tag.slug() });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag.isChild()) {
|
if (tag.isChild()) {
|
||||||
@ -28,11 +28,11 @@ export default function tagLabel(tag, attrs = {}) {
|
|||||||
attrs.className += ' untagged';
|
attrs.className += ' untagged';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return m(
|
||||||
m((link ? Link : 'span'), attrs,
|
link ? Link : 'span',
|
||||||
<span className="TagLabel-text">
|
attrs,
|
||||||
{tag && tag.icon() && tagIcon(tag, {}, {useColor: false})} {tagText}
|
<span className="TagLabel-text">
|
||||||
</span>
|
{tag && tag.icon() && tagIcon(tag, {}, { useColor: false })} {tagText}
|
||||||
)
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,9 @@ export default function tagsLabel(tags, attrs = {}) {
|
|||||||
attrs.className = 'TagsLabel ' + (attrs.className || '');
|
attrs.className = 'TagsLabel ' + (attrs.className || '');
|
||||||
|
|
||||||
if (tags) {
|
if (tags) {
|
||||||
sortTags(tags).forEach(tag => {
|
sortTags(tags).forEach((tag) => {
|
||||||
if (tag || tags.length === 1) {
|
if (tag || tags.length === 1) {
|
||||||
children.push(tagLabel(tag, {link}));
|
children.push(tagLabel(tag, { link }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Tag from "../models/Tag";
|
import Tag from '../models/Tag';
|
||||||
|
|
||||||
export default function sortTags(tags: Tag[]) {
|
export default function sortTags(tags: Tag[]) {
|
||||||
return tags.slice(0).sort((a, b) => {
|
return tags.slice(0).sort((a, b) => {
|
||||||
@ -7,8 +7,7 @@ export default function sortTags(tags: Tag[]) {
|
|||||||
|
|
||||||
// If they're both secondary tags, sort them by their discussions count,
|
// If they're both secondary tags, sort them by their discussions count,
|
||||||
// descending.
|
// descending.
|
||||||
if (aPos === null && bPos === null)
|
if (aPos === null && bPos === null) return b.discussionCount() - a.discussionCount();
|
||||||
return b.discussionCount() - a.discussionCount();
|
|
||||||
|
|
||||||
// If just one is a secondary tag, then the primary tag should
|
// If just one is a secondary tag, then the primary tag should
|
||||||
// come first.
|
// come first.
|
||||||
@ -23,20 +22,14 @@ export default function sortTags(tags: Tag[]) {
|
|||||||
// If they both have the same parent, then their positions are local,
|
// If they both have the same parent, then their positions are local,
|
||||||
// so we can compare them directly.
|
// so we can compare them directly.
|
||||||
if (aParent === bParent) return aPos - bPos;
|
if (aParent === bParent) return aPos - bPos;
|
||||||
|
|
||||||
// If they are both child tags, then we will compare the positions of their
|
// If they are both child tags, then we will compare the positions of their
|
||||||
// parents.
|
// parents.
|
||||||
else if (aParent && bParent)
|
else if (aParent && bParent) return aParent.position()! - bParent.position()!;
|
||||||
return aParent.position()! - bParent.position()!;
|
|
||||||
|
|
||||||
// If we are comparing a child tag with its parent, then we let the parent
|
// If we are comparing a child tag with its parent, then we let the parent
|
||||||
// come first. If we are comparing an unrelated parent/child, then we
|
// come first. If we are comparing an unrelated parent/child, then we
|
||||||
// compare both of the parents.
|
// compare both of the parents.
|
||||||
else if (aParent)
|
else if (aParent) return aParent === b ? 1 : aParent.position()! - bPos;
|
||||||
return aParent === b ? 1 : aParent.position()! - bPos;
|
else if (bParent) return bParent === a ? -1 : aPos - bParent.position()!;
|
||||||
|
|
||||||
else if (bParent)
|
|
||||||
return bParent === a ? -1 : aPos - bParent.position()!;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
@ -15,15 +15,14 @@ export default function () {
|
|||||||
if (tag) {
|
if (tag) {
|
||||||
const parent = tag.parent();
|
const parent = tag.parent();
|
||||||
const tags = parent ? [parent, tag] : [tag];
|
const tags = parent ? [parent, tag] : [tag];
|
||||||
promise.then(composer => composer.fields.tags = tags);
|
promise.then((composer) => (composer.fields.tags = tags));
|
||||||
} else {
|
} else {
|
||||||
app.composer.fields.tags = [];
|
app.composer.fields.tags = [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
extend(DiscussionComposer.prototype, 'oninit', function () {
|
extend(DiscussionComposer.prototype, 'oninit', function () {
|
||||||
app.tagList.load(['parent']).then(() => m.redraw())
|
app.tagList.load(['parent']).then(() => m.redraw());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add tag-selection abilities to the discussion composer.
|
// Add tag-selection abilities to the discussion composer.
|
||||||
@ -34,10 +33,10 @@ export default function () {
|
|||||||
|
|
||||||
app.modal.show(TagDiscussionModal, {
|
app.modal.show(TagDiscussionModal, {
|
||||||
selectedTags: (this.composer.fields.tags || []).slice(0),
|
selectedTags: (this.composer.fields.tags || []).slice(0),
|
||||||
onsubmit: tags => {
|
onsubmit: (tags) => {
|
||||||
this.composer.fields.tags = tags;
|
this.composer.fields.tags = tags;
|
||||||
this.$('textarea').focus();
|
this.$('textarea').focus();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,32 +46,38 @@ export default function () {
|
|||||||
const tags = this.composer.fields.tags || [];
|
const tags = this.composer.fields.tags || [];
|
||||||
const selectableTags = getSelectableTags();
|
const selectableTags = getSelectableTags();
|
||||||
|
|
||||||
items.add('tags', (
|
items.add(
|
||||||
|
'tags',
|
||||||
<a className={classList(['DiscussionComposer-changeTags', !selectableTags.length && 'disabled'])} onclick={this.chooseTags.bind(this)}>
|
<a className={classList(['DiscussionComposer-changeTags', !selectableTags.length && 'disabled'])} onclick={this.chooseTags.bind(this)}>
|
||||||
{tags.length
|
{tags.length ? (
|
||||||
? tagsLabel(tags)
|
tagsLabel(tags)
|
||||||
: <span className="TagLabel untagged">{app.translator.trans('flarum-tags.forum.composer_discussion.choose_tags_link')}</span>}
|
) : (
|
||||||
</a>
|
<span className="TagLabel untagged">{app.translator.trans('flarum-tags.forum.composer_discussion.choose_tags_link')}</span>
|
||||||
), 10);
|
)}
|
||||||
|
</a>,
|
||||||
|
10
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
override(DiscussionComposer.prototype, 'onsubmit', function (original) {
|
override(DiscussionComposer.prototype, 'onsubmit', function (original) {
|
||||||
const chosenTags = this.composer.fields.tags || [];
|
const chosenTags = this.composer.fields.tags || [];
|
||||||
const chosenPrimaryTags = chosenTags.filter(tag => tag.position() !== null && !tag.isChild());
|
const chosenPrimaryTags = chosenTags.filter((tag) => tag.position() !== null && !tag.isChild());
|
||||||
const chosenSecondaryTags = chosenTags.filter(tag => tag.position() === null);
|
const chosenSecondaryTags = chosenTags.filter((tag) => tag.position() === null);
|
||||||
const selectableTags = getSelectableTags();
|
const selectableTags = getSelectableTags();
|
||||||
|
|
||||||
if ((!chosenTags.length
|
if (
|
||||||
|| (chosenPrimaryTags.length < app.forum.attribute('minPrimaryTags'))
|
(!chosenTags.length ||
|
||||||
|| (chosenSecondaryTags.length < app.forum.attribute('minSecondaryTags'))
|
chosenPrimaryTags.length < app.forum.attribute('minPrimaryTags') ||
|
||||||
) && selectableTags.length) {
|
chosenSecondaryTags.length < app.forum.attribute('minSecondaryTags')) &&
|
||||||
|
selectableTags.length
|
||||||
|
) {
|
||||||
app.modal.show(TagDiscussionModal, {
|
app.modal.show(TagDiscussionModal, {
|
||||||
selectedTags: chosenTags,
|
selectedTags: chosenTags,
|
||||||
onsubmit: tags => {
|
onsubmit: (tags) => {
|
||||||
this.composer.fields.tags = tags;
|
this.composer.fields.tags = tags;
|
||||||
original();
|
original();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
original();
|
original();
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,16 @@ import Button from 'flarum/common/components/Button';
|
|||||||
|
|
||||||
import TagDiscussionModal from './components/TagDiscussionModal';
|
import TagDiscussionModal from './components/TagDiscussionModal';
|
||||||
|
|
||||||
export default function() {
|
export default function () {
|
||||||
// Add a control allowing the discussion to be moved to another category.
|
// Add a control allowing the discussion to be moved to another category.
|
||||||
extend(DiscussionControls, 'moderationControls', function(items, discussion) {
|
extend(DiscussionControls, 'moderationControls', function (items, discussion) {
|
||||||
if (discussion.canTag()) {
|
if (discussion.canTag()) {
|
||||||
items.add('tags', <Button icon="fas fa-tag" onclick={() => app.modal.show(TagDiscussionModal, { discussion })}>
|
items.add(
|
||||||
{app.translator.trans('flarum-tags.forum.discussion_controls.edit_tags_button')}
|
'tags',
|
||||||
</Button>);
|
<Button icon="fas fa-tag" onclick={() => app.modal.show(TagDiscussionModal, { discussion })}>
|
||||||
|
{app.translator.trans('flarum-tags.forum.discussion_controls.edit_tags_button')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,10 @@ import TagHero from './components/TagHero';
|
|||||||
import Tag from '../common/models/Tag';
|
import Tag from '../common/models/Tag';
|
||||||
import { ComponentAttrs } from 'flarum/common/Component';
|
import { ComponentAttrs } from 'flarum/common/Component';
|
||||||
|
|
||||||
const findTag = (slug: string) => app.store.all<Tag>('tags').find(tag => tag.slug().localeCompare(slug, undefined, { sensitivity: 'base' }) === 0);
|
const findTag = (slug: string) => app.store.all<Tag>('tags').find((tag) => tag.slug().localeCompare(slug, undefined, { sensitivity: 'base' }) === 0);
|
||||||
|
|
||||||
export default function() {
|
export default function () {
|
||||||
IndexPage.prototype.currentTag = function() {
|
IndexPage.prototype.currentTag = function () {
|
||||||
if (this.currentActiveTag) {
|
if (this.currentActiveTag) {
|
||||||
return this.currentActiveTag;
|
return this.currentActiveTag;
|
||||||
}
|
}
|
||||||
@ -25,7 +25,7 @@ export default function() {
|
|||||||
tag = findTag(slug);
|
tag = findTag(slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slug && !tag || (tag && !tag.isChild() && !tag.children())) {
|
if ((slug && !tag) || (tag && !tag.isChild() && !tag.children())) {
|
||||||
if (this.currentTagLoading) {
|
if (this.currentTagLoading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -36,13 +36,16 @@ export default function() {
|
|||||||
// a child tag page, then either:
|
// a child tag page, then either:
|
||||||
// - We loaded in that child tag (and its siblings) in the API document
|
// - We loaded in that child tag (and its siblings) in the API document
|
||||||
// - We first navigated to the current tag's parent, which would have loaded in the current tag's siblings.
|
// - We first navigated to the current tag's parent, which would have loaded in the current tag's siblings.
|
||||||
app.store.find('tags', slug, { include: 'children,children.parent,parent,state'}).then(() => {
|
app.store
|
||||||
this.currentActiveTag = findTag(slug);
|
.find('tags', slug, { include: 'children,children.parent,parent,state' })
|
||||||
|
.then(() => {
|
||||||
|
this.currentActiveTag = findTag(slug);
|
||||||
|
|
||||||
m.redraw();
|
m.redraw();
|
||||||
}).finally(() => {
|
})
|
||||||
this.currentTagLoading = false;
|
.finally(() => {
|
||||||
});
|
this.currentTagLoading = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag) {
|
if (tag) {
|
||||||
@ -54,7 +57,7 @@ export default function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// If currently viewing a tag, insert a tag hero at the top of the view.
|
// If currently viewing a tag, insert a tag hero at the top of the view.
|
||||||
override(IndexPage.prototype, 'hero', function(original) {
|
override(IndexPage.prototype, 'hero', function (original) {
|
||||||
const tag = this.currentTag();
|
const tag = this.currentTag();
|
||||||
|
|
||||||
if (tag) return <TagHero model={tag} />;
|
if (tag) return <TagHero model={tag} />;
|
||||||
@ -62,13 +65,13 @@ export default function() {
|
|||||||
return original();
|
return original();
|
||||||
});
|
});
|
||||||
|
|
||||||
extend(IndexPage.prototype, 'view', function(vdom: Mithril.Vnode<ComponentAttrs, {}>) {
|
extend(IndexPage.prototype, 'view', function (vdom: Mithril.Vnode<ComponentAttrs, {}>) {
|
||||||
const tag = this.currentTag();
|
const tag = this.currentTag();
|
||||||
|
|
||||||
if (tag) vdom.attrs.className += ' IndexPage--tag'+tag.id();
|
if (tag) vdom.attrs.className += ' IndexPage--tag' + tag.id();
|
||||||
});
|
});
|
||||||
|
|
||||||
extend(IndexPage.prototype, 'setTitle', function() {
|
extend(IndexPage.prototype, 'setTitle', function () {
|
||||||
const tag = this.currentTag();
|
const tag = this.currentTag();
|
||||||
|
|
||||||
if (tag) {
|
if (tag) {
|
||||||
@ -78,7 +81,7 @@ export default function() {
|
|||||||
|
|
||||||
// If currently viewing a tag, restyle the 'new discussion' button to use
|
// If currently viewing a tag, restyle the 'new discussion' button to use
|
||||||
// the tag's color, and disable if the user isn't allowed to edit.
|
// the tag's color, and disable if the user isn't allowed to edit.
|
||||||
extend(IndexPage.prototype, 'sidebarItems', function(items) {
|
extend(IndexPage.prototype, 'sidebarItems', function (items) {
|
||||||
const tag = this.currentTag();
|
const tag = this.currentTag();
|
||||||
|
|
||||||
if (tag) {
|
if (tag) {
|
||||||
@ -92,18 +95,20 @@ export default function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
newDiscussion.attrs.disabled = !canStartDiscussion;
|
newDiscussion.attrs.disabled = !canStartDiscussion;
|
||||||
newDiscussion.children = app.translator.trans(canStartDiscussion ? 'core.forum.index.start_discussion_button' : 'core.forum.index.cannot_start_discussion_button');
|
newDiscussion.children = app.translator.trans(
|
||||||
|
canStartDiscussion ? 'core.forum.index.start_discussion_button' : 'core.forum.index.cannot_start_discussion_button'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add a parameter for the global search state to pass on to the
|
// Add a parameter for the global search state to pass on to the
|
||||||
// DiscussionListState that will let us filter discussions by tag.
|
// DiscussionListState that will let us filter discussions by tag.
|
||||||
extend(GlobalSearchState.prototype, 'params', function(params) {
|
extend(GlobalSearchState.prototype, 'params', function (params) {
|
||||||
params.tags = m.route.param('tags');
|
params.tags = m.route.param('tags');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Translate that parameter into a gambit appended to the search query.
|
// Translate that parameter into a gambit appended to the search query.
|
||||||
extend(DiscussionListState.prototype, 'requestParams', function(this: DiscussionListState, params) {
|
extend(DiscussionListState.prototype, 'requestParams', function (this: DiscussionListState, params) {
|
||||||
if (typeof params.include === 'string') {
|
if (typeof params.include === 'string') {
|
||||||
params.include = [params.include];
|
params.include = [params.include];
|
||||||
} else {
|
} else {
|
||||||
@ -118,7 +123,7 @@ export default function() {
|
|||||||
if (q) {
|
if (q) {
|
||||||
filter.q = `${q} tag:${this.params.tags}`;
|
filter.q = `${q} tag:${this.params.tags}`;
|
||||||
}
|
}
|
||||||
params.filter = filter
|
params.filter = filter;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,9 @@ import DiscussionHero from 'flarum/forum/components/DiscussionHero';
|
|||||||
import tagsLabel from '../common/helpers/tagsLabel';
|
import tagsLabel from '../common/helpers/tagsLabel';
|
||||||
import sortTags from '../common/utils/sortTags';
|
import sortTags from '../common/utils/sortTags';
|
||||||
|
|
||||||
export default function() {
|
export default function () {
|
||||||
// Add tag labels to each discussion in the discussion list.
|
// Add tag labels to each discussion in the discussion list.
|
||||||
extend(DiscussionListItem.prototype, 'infoItems', function(items) {
|
extend(DiscussionListItem.prototype, 'infoItems', function (items) {
|
||||||
const tags = this.attrs.discussion.tags();
|
const tags = this.attrs.discussion.tags();
|
||||||
|
|
||||||
if (tags && tags.length) {
|
if (tags && tags.length) {
|
||||||
@ -16,7 +16,7 @@ export default function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Restyle a discussion's hero to use its first tag's color.
|
// Restyle a discussion's hero to use its first tag's color.
|
||||||
extend(DiscussionHero.prototype, 'view', function(view) {
|
extend(DiscussionHero.prototype, 'view', function (view) {
|
||||||
const tags = sortTags(this.attrs.discussion.tags());
|
const tags = sortTags(this.attrs.discussion.tags());
|
||||||
|
|
||||||
if (tags && tags.length) {
|
if (tags && tags.length) {
|
||||||
@ -30,11 +30,11 @@ export default function() {
|
|||||||
|
|
||||||
// Add a list of a discussion's tags to the discussion hero, displayed
|
// Add a list of a discussion's tags to the discussion hero, displayed
|
||||||
// before the title. Put the title on its own line.
|
// before the title. Put the title on its own line.
|
||||||
extend(DiscussionHero.prototype, 'items', function(items) {
|
extend(DiscussionHero.prototype, 'items', function (items) {
|
||||||
const tags = this.attrs.discussion.tags();
|
const tags = this.attrs.discussion.tags();
|
||||||
|
|
||||||
if (tags && tags.length) {
|
if (tags && tags.length) {
|
||||||
items.add('tags', tagsLabel(tags, {link: true}), 5);
|
items.add('tags', tagsLabel(tags, { link: true }), 5);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,17 @@ import TagLinkButton from './components/TagLinkButton';
|
|||||||
import TagsPage from './components/TagsPage';
|
import TagsPage from './components/TagsPage';
|
||||||
import sortTags from '../common/utils/sortTags';
|
import sortTags from '../common/utils/sortTags';
|
||||||
|
|
||||||
export default function() {
|
export default function () {
|
||||||
// Add a link to the tags page, as well as a list of all the tags,
|
// Add a link to the tags page, as well as a list of all the tags,
|
||||||
// to the index page's sidebar.
|
// to the index page's sidebar.
|
||||||
extend(IndexPage.prototype, 'navItems', function (items) {
|
extend(IndexPage.prototype, 'navItems', function (items) {
|
||||||
items.add('tags', <LinkButton icon="fas fa-th-large" href={app.route('tags')}>
|
items.add(
|
||||||
{app.translator.trans('flarum-tags.forum.index.tags_link')}
|
'tags',
|
||||||
</LinkButton>
|
<LinkButton icon="fas fa-th-large" href={app.route('tags')}>
|
||||||
, -10);
|
{app.translator.trans('flarum-tags.forum.index.tags_link')}
|
||||||
|
</LinkButton>,
|
||||||
|
-10
|
||||||
|
);
|
||||||
|
|
||||||
if (app.current.matches(TagsPage)) return;
|
if (app.current.matches(TagsPage)) return;
|
||||||
|
|
||||||
@ -24,7 +27,7 @@ export default function() {
|
|||||||
const tags = app.store.all('tags');
|
const tags = app.store.all('tags');
|
||||||
const currentTag = this.currentTag();
|
const currentTag = this.currentTag();
|
||||||
|
|
||||||
const addTag = tag => {
|
const addTag = (tag) => {
|
||||||
let active = currentTag === tag;
|
let active = currentTag === tag;
|
||||||
|
|
||||||
if (!active && currentTag) {
|
if (!active && currentTag) {
|
||||||
@ -36,23 +39,21 @@ export default function() {
|
|||||||
// use its children to populate the dropdown. The problem here is that `view`
|
// use its children to populate the dropdown. The problem here is that `view`
|
||||||
// on TagLinkButton is only called AFTER SelectDropdown, so no children are available
|
// on TagLinkButton is only called AFTER SelectDropdown, so no children are available
|
||||||
// for SelectDropdown to use at the time.
|
// for SelectDropdown to use at the time.
|
||||||
items.add('tag' + tag.id(), TagLinkButton.component({model: tag, params, active}, tag?.name()), -14);
|
items.add('tag' + tag.id(), TagLinkButton.component({ model: tag, params, active }, tag?.name()), -14);
|
||||||
};
|
};
|
||||||
|
|
||||||
sortTags(tags)
|
sortTags(tags)
|
||||||
.filter(tag => tag.position() !== null && (!tag.isChild() || (currentTag && (tag.parent() === currentTag || tag.parent() === currentTag.parent()))))
|
.filter(
|
||||||
|
(tag) => tag.position() !== null && (!tag.isChild() || (currentTag && (tag.parent() === currentTag || tag.parent() === currentTag.parent())))
|
||||||
|
)
|
||||||
.forEach(addTag);
|
.forEach(addTag);
|
||||||
|
|
||||||
const more = tags
|
const more = tags.filter((tag) => tag.position() === null).sort((a, b) => b.discussionCount() - a.discussionCount());
|
||||||
.filter(tag => tag.position() === null)
|
|
||||||
.sort((a, b) => b.discussionCount() - a.discussionCount());
|
|
||||||
|
|
||||||
more.splice(0, 3).forEach(addTag);
|
more.splice(0, 3).forEach(addTag);
|
||||||
|
|
||||||
if (more.length) {
|
if (more.length) {
|
||||||
items.add('moreTags', <LinkButton href={app.route('tags')}>
|
items.add('moreTags', <LinkButton href={app.route('tags')}>{app.translator.trans('flarum-tags.forum.index.more_link')}</LinkButton>, -16);
|
||||||
{app.translator.trans('flarum-tags.forum.index.more_link')}
|
|
||||||
</LinkButton>, -16)
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,7 @@ export default class DiscussionTaggedPost extends EventPost {
|
|||||||
const newTags = attrs.post.content()[1];
|
const newTags = attrs.post.content()[1];
|
||||||
|
|
||||||
function diffTags(tags1, tags2) {
|
function diffTags(tags1, tags2) {
|
||||||
return tags1
|
return tags1.filter((tag) => tags2.indexOf(tag) === -1).map((id) => app.store.getById('tags', id));
|
||||||
.filter(tag => tags2.indexOf(tag) === -1)
|
|
||||||
.map(id => app.store.getById('tags', id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs.tagsAdded = diffTags(newTags, oldTags);
|
attrs.tagsAdded = diffTags(newTags, oldTags);
|
||||||
@ -39,15 +37,15 @@ export default class DiscussionTaggedPost extends EventPost {
|
|||||||
|
|
||||||
if (this.attrs.tagsAdded.length) {
|
if (this.attrs.tagsAdded.length) {
|
||||||
data.tagsAdded = app.translator.trans('flarum-tags.forum.post_stream.tags_text', {
|
data.tagsAdded = app.translator.trans('flarum-tags.forum.post_stream.tags_text', {
|
||||||
tags: tagsLabel(this.attrs.tagsAdded, {link: true}),
|
tags: tagsLabel(this.attrs.tagsAdded, { link: true }),
|
||||||
count: this.attrs.tagsAdded.length
|
count: this.attrs.tagsAdded.length,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.attrs.tagsRemoved.length) {
|
if (this.attrs.tagsRemoved.length) {
|
||||||
data.tagsRemoved = app.translator.trans('flarum-tags.forum.post_stream.tags_text', {
|
data.tagsRemoved = app.translator.trans('flarum-tags.forum.post_stream.tags_text', {
|
||||||
tags: tagsLabel(this.attrs.tagsRemoved, {link: true}),
|
tags: tagsLabel(this.attrs.tagsRemoved, { link: true }),
|
||||||
count: this.attrs.tagsRemoved.length
|
count: this.attrs.tagsRemoved.length,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,11 +58,11 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
|
|||||||
const tags = sortTags(getSelectableTags(this.attrs.discussion));
|
const tags = sortTags(getSelectableTags(this.attrs.discussion));
|
||||||
this.tags = tags;
|
this.tags = tags;
|
||||||
|
|
||||||
const discussionTags = this.attrs.discussion?.tags()
|
const discussionTags = this.attrs.discussion?.tags();
|
||||||
if (this.attrs.selectedTags) {
|
if (this.attrs.selectedTags) {
|
||||||
this.attrs.selectedTags.map(this.addTag.bind(this));
|
this.attrs.selectedTags.map(this.addTag.bind(this));
|
||||||
} else if (discussionTags) {
|
} else if (discussionTags) {
|
||||||
discussionTags.forEach(tag => tag && this.addTag(tag));
|
discussionTags.forEach((tag) => tag && this.addTag(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectedTag = tags[0];
|
this.selectedTag = tags[0];
|
||||||
@ -72,11 +72,11 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
primaryCount() {
|
primaryCount() {
|
||||||
return this.selected.filter(tag => tag.isPrimary()).length;
|
return this.selected.filter((tag) => tag.isPrimary()).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
secondaryCount() {
|
secondaryCount() {
|
||||||
return this.selected.filter(tag => !tag.isPrimary()).length;
|
return this.selected.filter((tag) => !tag.isPrimary()).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -107,9 +107,7 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
|
|||||||
|
|
||||||
// Look through the list of selected tags for any tags which have the tag
|
// Look through the list of selected tags for any tags which have the tag
|
||||||
// we just removed as their parent. We'll need to remove them too.
|
// we just removed as their parent. We'll need to remove them too.
|
||||||
this.selected
|
this.selected.filter((selected) => selected.parent() === tag).forEach(this.removeTag.bind(this));
|
||||||
.filter(selected => selected.parent() === tag)
|
|
||||||
.forEach(this.removeTag.bind(this));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +117,7 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
|
|||||||
|
|
||||||
title() {
|
title() {
|
||||||
return this.attrs.discussion
|
return this.attrs.discussion
|
||||||
? app.translator.trans('flarum-tags.forum.choose_tags.edit_title', {title: <em>{this.attrs.discussion.title()}</em>})
|
? app.translator.trans('flarum-tags.forum.choose_tags.edit_title', { title: <em>{this.attrs.discussion.title()}</em> })
|
||||||
: app.translator.trans('flarum-tags.forum.choose_tags.title');
|
: app.translator.trans('flarum-tags.forum.choose_tags.title');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,10 +128,10 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
|
|||||||
|
|
||||||
if (primaryCount < this.minPrimary) {
|
if (primaryCount < this.minPrimary) {
|
||||||
const remaining = this.minPrimary - primaryCount;
|
const remaining = this.minPrimary - primaryCount;
|
||||||
return app.translator.trans('flarum-tags.forum.choose_tags.choose_primary_placeholder', {count: remaining});
|
return app.translator.trans('flarum-tags.forum.choose_tags.choose_primary_placeholder', { count: remaining });
|
||||||
} else if (secondaryCount < this.minSecondary) {
|
} else if (secondaryCount < this.minSecondary) {
|
||||||
const remaining = this.minSecondary - secondaryCount;
|
const remaining = this.minSecondary - secondaryCount;
|
||||||
return app.translator.trans('flarum-tags.forum.choose_tags.choose_secondary_placeholder', {count: remaining});
|
return app.translator.trans('flarum-tags.forum.choose_tags.choose_secondary_placeholder', { count: remaining });
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
@ -151,7 +149,7 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
|
|||||||
|
|
||||||
// Filter out all child tags whose parents have not been selected. This
|
// Filter out all child tags whose parents have not been selected. This
|
||||||
// makes it impossible to select a child if its parent hasn't been selected.
|
// makes it impossible to select a child if its parent hasn't been selected.
|
||||||
tags = tags.filter(tag => {
|
tags = tags.filter((tag) => {
|
||||||
const parent = tag.parent();
|
const parent = tag.parent();
|
||||||
return parent !== null && (parent === false || this.selected.includes(parent));
|
return parent !== null && (parent === false || this.selected.includes(parent));
|
||||||
});
|
});
|
||||||
@ -159,17 +157,17 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
|
|||||||
// If the number of selected primary/secondary tags is at the maximum, then
|
// If the number of selected primary/secondary tags is at the maximum, then
|
||||||
// we'll filter out all other tags of that type.
|
// we'll filter out all other tags of that type.
|
||||||
if (primaryCount >= this.maxPrimary && !this.bypassReqs) {
|
if (primaryCount >= this.maxPrimary && !this.bypassReqs) {
|
||||||
tags = tags.filter(tag => !tag.isPrimary() || this.selected.includes(tag));
|
tags = tags.filter((tag) => !tag.isPrimary() || this.selected.includes(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (secondaryCount >= this.maxSecondary && !this.bypassReqs) {
|
if (secondaryCount >= this.maxSecondary && !this.bypassReqs) {
|
||||||
tags = tags.filter(tag => tag.isPrimary() || this.selected.includes(tag));
|
tags = tags.filter((tag) => tag.isPrimary() || this.selected.includes(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user has entered text in the filter input, then filter by tags
|
// If the user has entered text in the filter input, then filter by tags
|
||||||
// whose name matches what they've entered.
|
// whose name matches what they've entered.
|
||||||
if (filter) {
|
if (filter) {
|
||||||
tags = tags.filter(tag => tag.name().substr(0, filter.length).toLowerCase() === filter);
|
tags = tags.filter((tag) => tag.name().substr(0, filter.length).toLowerCase() === filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.selectedTag || !tags.includes(this.selectedTag)) this.selectedTag = tags[0];
|
if (!this.selectedTag || !tags.includes(this.selectedTag)) this.selectedTag = tags[0];
|
||||||
@ -180,30 +178,38 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
|
|||||||
<div className="Modal-body">
|
<div className="Modal-body">
|
||||||
<div className="TagDiscussionModal-form">
|
<div className="TagDiscussionModal-form">
|
||||||
<div className="TagDiscussionModal-form-input">
|
<div className="TagDiscussionModal-form-input">
|
||||||
<div className={'TagsInput FormControl ' + (this.focused ? 'focus' : '')}
|
<div className={'TagsInput FormControl ' + (this.focused ? 'focus' : '')} onclick={() => this.$('.TagsInput input').focus()}>
|
||||||
onclick={() => this.$('.TagsInput input').focus()}
|
|
||||||
>
|
|
||||||
<span className="TagsInput-selected">
|
<span className="TagsInput-selected">
|
||||||
{this.selected.map(tag =>
|
{this.selected.map((tag) => (
|
||||||
<span className="TagsInput-tag" onclick={() => {
|
<span
|
||||||
this.removeTag(tag);
|
className="TagsInput-tag"
|
||||||
this.onready();
|
onclick={() => {
|
||||||
}}>
|
this.removeTag(tag);
|
||||||
|
this.onready();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{tagLabel(tag)}
|
{tagLabel(tag)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
))}
|
||||||
</span>
|
</span>
|
||||||
<input className="FormControl"
|
<input
|
||||||
|
className="FormControl"
|
||||||
placeholder={extractText(this.getInstruction(primaryCount, secondaryCount))}
|
placeholder={extractText(this.getInstruction(primaryCount, secondaryCount))}
|
||||||
bidi={this.filter}
|
bidi={this.filter}
|
||||||
style={{ width: inputWidth + 'ch' }}
|
style={{ width: inputWidth + 'ch' }}
|
||||||
onkeydown={this.navigator.navigate.bind(this.navigator)}
|
onkeydown={this.navigator.navigate.bind(this.navigator)}
|
||||||
onfocus={() => this.focused = true}
|
onfocus={() => (this.focused = true)}
|
||||||
onblur={() => this.focused = false}/>
|
onblur={() => (this.focused = false)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="TagDiscussionModal-form-submit App-primaryControl">
|
<div className="TagDiscussionModal-form-submit App-primaryControl">
|
||||||
<Button type="submit" className="Button Button--primary" disabled={!this.meetsRequirements(primaryCount, secondaryCount)} icon="fas fa-check">
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="Button Button--primary"
|
||||||
|
disabled={!this.meetsRequirements(primaryCount, secondaryCount)}
|
||||||
|
icon="fas fa-check"
|
||||||
|
>
|
||||||
{app.translator.trans('flarum-tags.forum.choose_tags.submit_button')}
|
{app.translator.trans('flarum-tags.forum.choose_tags.submit_button')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -213,41 +219,35 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
|
|||||||
<div className="Modal-footer">
|
<div className="Modal-footer">
|
||||||
<ul className="TagDiscussionModal-list SelectTagList">
|
<ul className="TagDiscussionModal-list SelectTagList">
|
||||||
{tags
|
{tags
|
||||||
.filter(tag => filter || !tag.parent() || this.selected.includes(tag.parent() as Tag))
|
.filter((tag) => filter || !tag.parent() || this.selected.includes(tag.parent() as Tag))
|
||||||
.map(tag => (
|
.map((tag) => (
|
||||||
<li data-index={tag.id()}
|
<li
|
||||||
|
data-index={tag.id()}
|
||||||
className={classList({
|
className={classList({
|
||||||
pinned: tag.position() !== null,
|
pinned: tag.position() !== null,
|
||||||
child: !!tag.parent(),
|
child: !!tag.parent(),
|
||||||
colored: !!tag.color(),
|
colored: !!tag.color(),
|
||||||
selected: this.selected.includes(tag),
|
selected: this.selected.includes(tag),
|
||||||
active: this.selectedTag === tag
|
active: this.selectedTag === tag,
|
||||||
})}
|
})}
|
||||||
style={{color: tag.color()}}
|
style={{ color: tag.color() }}
|
||||||
onmouseover={() => this.selectedTag = tag}
|
onmouseover={() => (this.selectedTag = tag)}
|
||||||
onclick={this.toggleTag.bind(this, tag)}
|
onclick={this.toggleTag.bind(this, tag)}
|
||||||
>
|
>
|
||||||
{tagIcon(tag)}
|
{tagIcon(tag)}
|
||||||
<span className="SelectTagListItem-name">
|
<span className="SelectTagListItem-name">{highlight(tag.name(), filter)}</span>
|
||||||
{highlight(tag.name(), filter)}
|
{tag.description() ? <span className="SelectTagListItem-description">{tag.description()}</span> : ''}
|
||||||
</span>
|
|
||||||
{tag.description()
|
|
||||||
? (
|
|
||||||
<span className="SelectTagListItem-description">
|
|
||||||
{tag.description()}
|
|
||||||
</span>
|
|
||||||
) : ''}
|
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
{!!app.forum.attribute('canBypassTagCounts') && (
|
{!!app.forum.attribute('canBypassTagCounts') && (
|
||||||
<div className="TagDiscussionModal-controls">
|
<div className="TagDiscussionModal-controls">
|
||||||
<ToggleButton className="Button" onclick={() => this.bypassReqs = !this.bypassReqs} isToggled={this.bypassReqs}>
|
<ToggleButton className="Button" onclick={() => (this.bypassReqs = !this.bypassReqs)} isToggled={this.bypassReqs}>
|
||||||
{app.translator.trans('flarum-tags.forum.choose_tags.bypass_requirements')}
|
{app.translator.trans('flarum-tags.forum.choose_tags.bypass_requirements')}
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +279,7 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
|
|||||||
|
|
||||||
select(e: KeyboardEvent) {
|
select(e: KeyboardEvent) {
|
||||||
// Ctrl + Enter submits the selection, just Enter completes the current entry
|
// Ctrl + Enter submits the selection, just Enter completes the current entry
|
||||||
if (e.metaKey || e.ctrlKey || this.selectedTag && this.selected.includes(this.selectedTag)) {
|
if (e.metaKey || e.ctrlKey || (this.selectedTag && this.selected.includes(this.selectedTag))) {
|
||||||
if (this.selected.length) {
|
if (this.selected.length) {
|
||||||
// The DOM submit method doesn't emit a `submit event, so we
|
// The DOM submit method doesn't emit a `submit event, so we
|
||||||
// simulate a manual submission so our `onsubmit` logic is run.
|
// simulate a manual submission so our `onsubmit` logic is run.
|
||||||
@ -297,9 +297,7 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
|
|||||||
getCurrentNumericIndex() {
|
getCurrentNumericIndex() {
|
||||||
if (!this.selectedTag) return -1;
|
if (!this.selectedTag) return -1;
|
||||||
|
|
||||||
return this.selectableItems().index(
|
return this.selectableItems().index(this.getItem(this.selectedTag));
|
||||||
this.getItem(this.selectedTag)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getItem(selectedTag: Tag) {
|
getItem(selectedTag: Tag) {
|
||||||
@ -337,7 +335,7 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof scrollTop !== 'undefined') {
|
if (typeof scrollTop !== 'undefined') {
|
||||||
$dropdown.stop(true).animate({scrollTop}, 100);
|
$dropdown.stop(true).animate({ scrollTop }, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -349,13 +347,12 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
|
|||||||
const tags = this.selected;
|
const tags = this.selected;
|
||||||
|
|
||||||
if (discussion) {
|
if (discussion) {
|
||||||
discussion.save({relationships: {tags}})
|
discussion.save({ relationships: { tags } }).then(() => {
|
||||||
.then(() => {
|
if (app.current.matches(DiscussionPage)) {
|
||||||
if (app.current.matches(DiscussionPage)) {
|
app.current.get('stream').update();
|
||||||
app.current.get('stream').update();
|
}
|
||||||
}
|
m.redraw();
|
||||||
m.redraw();
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.attrs.onsubmit) this.attrs.onsubmit(tags);
|
if (this.attrs.onsubmit) this.attrs.onsubmit(tags);
|
||||||
|
@ -7,11 +7,12 @@ export default class TagHero extends Component {
|
|||||||
const color = tag.color();
|
const color = tag.color();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={'Hero TagHero' + (color ? ' TagHero--colored' : '')}
|
<header className={'Hero TagHero' + (color ? ' TagHero--colored' : '')} style={color ? { '--hero-bg': color } : ''}>
|
||||||
style={color ? { '--hero-bg': color } : ''}>
|
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="containerNarrow">
|
<div className="containerNarrow">
|
||||||
<h2 className="Hero-title">{tag.icon() && tagIcon(tag, {}, { useColor: false })} {tag.name()}</h2>
|
<h2 className="Hero-title">
|
||||||
|
{tag.icon() && tagIcon(tag, {}, { useColor: false })} {tag.name()}
|
||||||
|
</h2>
|
||||||
<div className="Hero-subtitle">{tag.description()}</div>
|
<div className="Hero-subtitle">{tag.description()}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,21 +8,12 @@ export default class TagLinkButton extends LinkButton {
|
|||||||
const tag = this.attrs.model;
|
const tag = this.attrs.model;
|
||||||
const active = this.constructor.isActive(this.attrs);
|
const active = this.constructor.isActive(this.attrs);
|
||||||
const description = tag && tag.description();
|
const description = tag && tag.description();
|
||||||
const className = classList([
|
const className = classList(['TagLinkButton', 'hasIcon', this.attrs.className, tag.isChild() && 'child']);
|
||||||
'TagLinkButton',
|
|
||||||
'hasIcon',
|
|
||||||
this.attrs.className,
|
|
||||||
tag.isChild() && 'child',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link className={className} href={this.attrs.route}
|
<Link className={className} href={this.attrs.route} style={tag ? { '--color': tag.color() } : ''} title={description || ''}>
|
||||||
style={tag ? { '--color': tag.color() } : ''}
|
|
||||||
title={description || ''}>
|
|
||||||
{tagIcon(tag, { className: 'Button-icon' })}
|
{tagIcon(tag, { className: 'Button-icon' })}
|
||||||
<span className="Button-label">
|
<span className="Button-label">{tag ? tag.name() : app.translator.trans('flarum-tags.forum.index.untagged_link')}</span>
|
||||||
{tag ? tag.name() : app.translator.trans('flarum-tags.forum.index.untagged_link')}
|
|
||||||
</span>
|
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,14 @@ export default class TagsPage extends Page {
|
|||||||
const preloaded = app.preloadedApiDocument();
|
const preloaded = app.preloadedApiDocument();
|
||||||
|
|
||||||
if (preloaded) {
|
if (preloaded) {
|
||||||
this.tags = sortTags(preloaded.filter(tag => !tag.isChild()));
|
this.tags = sortTags(preloaded.filter((tag) => !tag.isChild()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
app.tagList.load(['children', 'lastPostedDiscussion', 'parent']).then(() => {
|
app.tagList.load(['children', 'lastPostedDiscussion', 'parent']).then(() => {
|
||||||
this.tags = sortTags(app.store.all('tags').filter(tag => !tag.isChild()));
|
this.tags = sortTags(app.store.all('tags').filter((tag) => !tag.isChild()));
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
@ -40,8 +40,8 @@ export default class TagsPage extends Page {
|
|||||||
return <LoadingIndicator />;
|
return <LoadingIndicator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pinned = this.tags.filter(tag => tag.position() !== null);
|
const pinned = this.tags.filter((tag) => tag.position() !== null);
|
||||||
const cloud = this.tags.filter(tag => tag.position() === null);
|
const cloud = this.tags.filter((tag) => tag.position() === null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="TagsPage">
|
<div className="TagsPage">
|
||||||
@ -53,53 +53,41 @@ export default class TagsPage extends Page {
|
|||||||
|
|
||||||
<div className="TagsPage-content sideNavOffset">
|
<div className="TagsPage-content sideNavOffset">
|
||||||
<ul className="TagTiles">
|
<ul className="TagTiles">
|
||||||
{pinned.map(tag => {
|
{pinned.map((tag) => {
|
||||||
const lastPostedDiscussion = tag.lastPostedDiscussion();
|
const lastPostedDiscussion = tag.lastPostedDiscussion();
|
||||||
const children = sortTags(tag.children() || []);
|
const children = sortTags(tag.children() || []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={'TagTile ' + (tag.color() ? 'colored' : '')}
|
<li className={'TagTile ' + (tag.color() ? 'colored' : '')} style={{ '--tag-bg': tag.color() }}>
|
||||||
style={{ '--tag-bg': tag.color() }}>
|
|
||||||
<Link className="TagTile-info" href={app.route.tag(tag)}>
|
<Link className="TagTile-info" href={app.route.tag(tag)}>
|
||||||
{tag.icon() && tagIcon(tag, {}, { useColor: false })}
|
{tag.icon() && tagIcon(tag, {}, { useColor: false })}
|
||||||
<h3 className="TagTile-name">{tag.name()}</h3>
|
<h3 className="TagTile-name">{tag.name()}</h3>
|
||||||
<p className="TagTile-description">{tag.description()}</p>
|
<p className="TagTile-description">{tag.description()}</p>
|
||||||
{children
|
{children ? (
|
||||||
? (
|
<div className="TagTile-children">
|
||||||
<div className="TagTile-children">
|
{children.map((child) => [<Link href={app.route.tag(child)}>{child.name()}</Link>, ' '])}
|
||||||
{children.map(child => [
|
</div>
|
||||||
<Link href={app.route.tag(child)}>
|
|
||||||
{child.name()}
|
|
||||||
</Link>,
|
|
||||||
' '
|
|
||||||
])}
|
|
||||||
</div>
|
|
||||||
) : ''}
|
|
||||||
</Link>
|
|
||||||
{lastPostedDiscussion
|
|
||||||
? (
|
|
||||||
<Link className="TagTile-lastPostedDiscussion"
|
|
||||||
href={app.route.discussion(lastPostedDiscussion, lastPostedDiscussion.lastPostNumber())}
|
|
||||||
>
|
|
||||||
<span className="TagTile-lastPostedDiscussion-title">{lastPostedDiscussion.title()}</span>
|
|
||||||
{humanTime(lastPostedDiscussion.lastPostedAt())}
|
|
||||||
</Link>
|
|
||||||
) : (
|
) : (
|
||||||
<span className="TagTile-lastPostedDiscussion"/>
|
''
|
||||||
)}
|
)}
|
||||||
|
</Link>
|
||||||
|
{lastPostedDiscussion ? (
|
||||||
|
<Link
|
||||||
|
className="TagTile-lastPostedDiscussion"
|
||||||
|
href={app.route.discussion(lastPostedDiscussion, lastPostedDiscussion.lastPostNumber())}
|
||||||
|
>
|
||||||
|
<span className="TagTile-lastPostedDiscussion-title">{lastPostedDiscussion.title()}</span>
|
||||||
|
{humanTime(lastPostedDiscussion.lastPostedAt())}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span className="TagTile-lastPostedDiscussion" />
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{cloud.length ? (
|
{cloud.length ? <div className="TagCloud">{cloud.map((tag) => [tagLabel(tag, { link: true }), ' '])}</div> : ''}
|
||||||
<div className="TagCloud">
|
|
||||||
{cloud.map(tag => [
|
|
||||||
tagLabel(tag, {link: true}),
|
|
||||||
' ',
|
|
||||||
])}
|
|
||||||
</div>
|
|
||||||
) : ''}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,11 +15,11 @@ import addTagLabels from './addTagLabels';
|
|||||||
import addTagControl from './addTagControl';
|
import addTagControl from './addTagControl';
|
||||||
import addTagComposer from './addTagComposer';
|
import addTagComposer from './addTagComposer';
|
||||||
|
|
||||||
app.initializers.add('flarum-tags', function() {
|
app.initializers.add('flarum-tags', function () {
|
||||||
app.routes.tags = {path: '/tags', component: TagsPage };
|
app.routes.tags = { path: '/tags', component: TagsPage };
|
||||||
app.routes.tag = {path: '/t/:tags', component: IndexPage };
|
app.routes.tag = { path: '/t/:tags', component: IndexPage };
|
||||||
|
|
||||||
app.route.tag = (tag: Tag) => app.route('tag', {tags: tag.slug()});
|
app.route.tag = (tag: Tag) => app.route('tag', { tags: tag.slug() });
|
||||||
|
|
||||||
app.postComponents.discussionTagged = DiscussionTaggedPost;
|
app.postComponents.discussionTagged = DiscussionTaggedPost;
|
||||||
|
|
||||||
@ -37,7 +37,6 @@ app.initializers.add('flarum-tags', function() {
|
|||||||
addTagComposer();
|
addTagComposer();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Expose compat API
|
// Expose compat API
|
||||||
import tagsCompat from './compat';
|
import tagsCompat from './compat';
|
||||||
import { compat } from '@flarum/core/forum';
|
import { compat } from '@flarum/core/forum';
|
||||||
|
@ -1,23 +1,19 @@
|
|||||||
import app from "flarum/forum/app";
|
import app from 'flarum/forum/app';
|
||||||
import type Tag from "../../common/models/Tag";
|
import type Tag from '../../common/models/Tag';
|
||||||
|
|
||||||
export default class TagListState {
|
export default class TagListState {
|
||||||
loadedIncludes = new Set();
|
loadedIncludes = new Set();
|
||||||
|
|
||||||
async load(includes: string[] = []): Promise<Tag[]> {
|
async load(includes: string[] = []): Promise<Tag[]> {
|
||||||
const unloadedIncludes = includes.filter(
|
const unloadedIncludes = includes.filter((include) => !this.loadedIncludes.has(include));
|
||||||
(include) => !this.loadedIncludes.has(include)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (unloadedIncludes.length === 0) {
|
if (unloadedIncludes.length === 0) {
|
||||||
return Promise.resolve(app.store.all<Tag>("tags"));
|
return Promise.resolve(app.store.all<Tag>('tags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.store
|
return app.store.find<Tag[]>('tags', { include: unloadedIncludes.join(',') }).then((val) => {
|
||||||
.find<Tag[]>("tags", { include: unloadedIncludes.join(",") })
|
unloadedIncludes.forEach((include) => this.loadedIncludes.add(include));
|
||||||
.then((val) => {
|
return val;
|
||||||
unloadedIncludes.forEach((include) => this.loadedIncludes.add(include));
|
});
|
||||||
return val;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ export default function getSelectableTags(discussion) {
|
|||||||
let tags = app.store.all('tags');
|
let tags = app.store.all('tags');
|
||||||
|
|
||||||
if (discussion) {
|
if (discussion) {
|
||||||
tags = tags.filter(tag => tag.canAddToDiscussion() || discussion.tags().indexOf(tag) !== -1);
|
tags = tags.filter((tag) => tag.canAddToDiscussion() || discussion.tags().indexOf(tag) !== -1);
|
||||||
} else {
|
} else {
|
||||||
tags = tags.filter(tag => tag.canStartDiscussion());
|
tags = tags.filter((tag) => tag.canStartDiscussion());
|
||||||
}
|
}
|
||||||
|
|
||||||
return tags;
|
return tags;
|
||||||
|
@ -2526,6 +2526,11 @@ prettier@^2.4.1, prettier@^2.5.1:
|
|||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
|
||||||
integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
|
integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
|
||||||
|
|
||||||
|
prettier@^2.7.1:
|
||||||
|
version "2.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64"
|
||||||
|
integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
|
||||||
|
|
||||||
prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||||
version "15.8.1"
|
version "15.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||||
|
Reference in New Issue
Block a user