1
0
mirror of https://github.com/flarum/core.git synced 2025-08-18 14:22:02 +02:00

Compare commits

..

9 Commits

Author SHA1 Message Date
David Wheatley
7de9afdcb9 chore: remove moz vendor prefix 2021-12-24 13:51:32 +01:00
David Wheatley
140a6e4477 unformat 2021-12-24 13:49:06 +01:00
David Wheatley
ec0c233d15 chore: remove unneeded CSS vendor prefixes 2021-12-24 13:47:18 +01:00
flarum-bot
dc661bf144 Bundled output for commit 7a27f494c6
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-22 18:58:42 +00:00
David Wheatley
7a27f494c6 fix: hide WelcomeHero when content is empty (#3219) 2021-12-22 13:54:21 -05:00
Sebastian Kessler
edde6be301 docs: fix broken contribution link in README; add screenshot (#3211) 2021-12-20 14:55:25 -05:00
flarum-bot
96fdaac3ef Bundled output for commit e57655553f
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-20 16:32:17 +00:00
David Wheatley
e57655553f fix: breaking change in Search component - renaming of state property (#3212)
* fix: breaking change in search component's public api

* fix: add setter

* feat: add deprecation warning helper

This reduces bundle size as a result of deprecation warning in our JS, as well as maintaining a consistent format across warnings.

* feat: fire deprecation warning on usage of `Search.state`

* chore: use consistent deprecation warning across core

* fix: `/pull` not `/issue`

* chore: format
2021-12-20 16:28:28 +00:00
David Wheatley
a1cc456f3a fix(postmeta): use app baseUrl instead of location.origin (#3216) 2021-12-20 16:25:04 +00:00
24 changed files with 191 additions and 121 deletions

View File

@@ -9,7 +9,6 @@
<a href="https://github.styleci.io/repos/28257573"><img src="https://github.styleci.io/repos/28257573/shield?style=flat" alt="StyleCI"></a> <a href="https://github.styleci.io/repos/28257573"><img src="https://github.styleci.io/repos/28257573/shield?style=flat" alt="StyleCI"></a>
</p> </p>
## About Flarum ## About Flarum
**[Flarum](https://flarum.org/) is a delightfully simple discussion platform for your website.** It's fast and easy to use, with all the features you need to run a successful community. It is designed to be: **[Flarum](https://flarum.org/) is a delightfully simple discussion platform for your website.** It's fast and easy to use, with all the features you need to run a successful community. It is designed to be:
@@ -20,13 +19,15 @@
* **Powerful and extensible.** Customize, extend, and integrate Flarum to suit your community. Flarums architecture is amazingly flexible, with a powerful Extension API. * **Powerful and extensible.** Customize, extend, and integrate Flarum to suit your community. Flarums architecture is amazingly flexible, with a powerful Extension API.
![Screenshot of a Flarum instance, showing multiple discussions and tags.](https://flarum.org/assets/img/home-screenshot.png)
## Installation ## Installation
This repository contains Flarum's core code. If you want to set up a forum, visit the [Flarum skeleton repository](https://github.com/flarum/flarum). This repository contains Flarum's core code. If you want to set up a forum, visit the [Flarum skeleton repository](https://github.com/flarum/flarum). For support, refer to the [documentation](https://docs.flarum.org/), and ask questions on [Flarum Discuss](https://discuss.flarum.org/) (our community forum) or [Discord server](https://flarum.org/discord/).
## Contributing ## Contributing
Thank you for considering contributing to Flarum! Please read the **[Contributing guide](https://flarum.org/docs/contributing.html)** to learn how you can help. Thank you for considering contributing to Flarum! Please read the **[Contributing guide](https://docs.flarum.org/contributing)** to learn how you can help.
## Security Vulnerabilities ## Security Vulnerabilities

View File

@@ -10,3 +10,17 @@
* can fix. * can fix.
*/ */
export default function fireDebugWarning(...args: Parameters<typeof console.warn>): void; export default function fireDebugWarning(...args: Parameters<typeof console.warn>): void;
/**
* Fire a Flarum deprecation warning which is shown in the JS console.
*
* These warnings are only shown when the forum is in debug mode, and the function exists to
* reduce bundle size caused by multiple warnings across our JavaScript.
*
* @param message The message to display. (Short, but sweet, please!)
* @param githubId The PR or Issue ID with more info in relation to this change.
* @param [removedFrom] The version in which this feature will be completely removed. (default: 2.0)
* @param [repo] The repo which the issue or PR is located in. (default: flarum/core)
*
* @see {@link fireDebugWarning}
*/
export declare function fireDeprecationWarning(message: string, githubId: string, removedFrom?: string, repo?: string): void;

View File

@@ -42,12 +42,22 @@ export interface SearchAttrs extends ComponentAttrs {
* *
* - state: SearchState instance. * - state: SearchState instance.
*/ */
export default class Search<T extends SearchAttrs = SearchAttrs> extends Component<T> { export default class Search<T extends SearchAttrs = SearchAttrs> extends Component<T, SearchState> {
/** /**
* The minimum query length before sources are searched. * The minimum query length before sources are searched.
*/ */
protected static MIN_SEARCH_LEN: number; protected static MIN_SEARCH_LEN: number;
/**
* The instance of `SearchState` for this component.
*/
protected searchState: SearchState; protected searchState: SearchState;
/**
* The instance of `SearchState` for this component.
*
* @deprecated Replace with`this.searchState` instead.
*/
protected get state(): SearchState;
protected set state(state: SearchState);
/** /**
* Whether or not the search input has focus. * Whether or not the search input has focus.
*/ */

View File

@@ -1,13 +1,26 @@
import Component from '../../common/Component';
import type Mithril from 'mithril';
export interface IWelcomeHeroAttrs {
}
/** /**
* The `WelcomeHero` component displays a hero that welcomes the user to the * The `WelcomeHero` component displays a hero that welcomes the user to the
* forum. * forum.
*/ */
export default class WelcomeHero extends Component<import("../../common/Component").ComponentAttrs, undefined> { export default class WelcomeHero extends Component<IWelcomeHeroAttrs> {
constructor(); /**
hidden: string | boolean | null | undefined; * @deprecated Extend the `isHidden` method instead.
*/
hidden: boolean;
oninit(vnode: Mithril.Vnode<IWelcomeHeroAttrs, this>): void;
view(vnode: Mithril.Vnode<IWelcomeHeroAttrs, this>): JSX.Element | null;
/** /**
* Hide the welcome hero. * Hide the welcome hero.
*/ */
hide(): void; hide(): void;
/**
* Determines whether the welcome hero should be hidden.
*
* @returns if the welcome hero is hidden.
*/
isHidden(): boolean;
} }
import Component from "../../common/Component";

2
js/dist/admin.js generated vendored

File diff suppressed because one or more lines are too long

2
js/dist/admin.js.map generated vendored

File diff suppressed because one or more lines are too long

2
js/dist/forum.js generated vendored

File diff suppressed because one or more lines are too long

2
js/dist/forum.js.map generated vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
import app from '../common/app'; import app from '../common/app';
import { FlarumRequestOptions } from './Application'; import { FlarumRequestOptions } from './Application';
import fireDebugWarning from './helpers/fireDebugWarning'; import { fireDeprecationWarning } from './helpers/fireDebugWarning';
import Model, { ModelData, SavedModelData } from './Model'; import Model, { ModelData, SavedModelData } from './Model';
export interface MetaInformation { export interface MetaInformation {
@@ -123,11 +123,7 @@ export default class Store {
if (!this.models[data.type]) { if (!this.models[data.type]) {
if (!allowUnregistered) { if (!allowUnregistered) {
setTimeout(() => setTimeout(() =>
fireDebugWarning( fireDeprecationWarning(`Pushing object of type \`${data.type}\` not allowed, as type not yet registered in the store.`, '3206')
`[Flarum 2.0 Deprecation] Cannot push object of type \`${data.type}\`, as that type has not yet been registered in the store. This will throw an error in Flarum 2.0 and later.
For more information, see https://github.com/flarum/core/pull/3206.`
)
); );
} }

View File

@@ -16,3 +16,21 @@ export default function fireDebugWarning(...args: Parameters<typeof console.warn
console.warn(...args); console.warn(...args);
} }
/**
* Fire a Flarum deprecation warning which is shown in the JS console.
*
* These warnings are only shown when the forum is in debug mode, and the function exists to
* reduce bundle size caused by multiple warnings across our JavaScript.
*
* @param message The message to display. (Short, but sweet, please!)
* @param githubId The PR or Issue ID with more info in relation to this change.
* @param [removedFrom] The version in which this feature will be completely removed. (default: 2.0)
* @param [repo] The repo which the issue or PR is located in. (default: flarum/core)
*
* @see {@link fireDebugWarning}
*/
export function fireDeprecationWarning(message: string, githubId: string, removedFrom: string = '2.0', repo: string = 'flarum/core'): void {
// GitHub auto-redirects between `/pull` and `/issues` for us, so using `/pull` saves 2 bytes!
fireDebugWarning(`[Flarum ${removedFrom} Deprecation] ${message}\n\nSee: https://github.com/${repo}/pull/${githubId}`);
}

View File

@@ -1,15 +1,14 @@
import { fireDeprecationWarning } from '../helpers/fireDebugWarning';
const deprecatedNotice = 'The `evented` util is deprecated and no longer supported.';
const deprecationIssueId = '2547';
/** /**
* The `evented` mixin provides methods allowing an object to trigger events, * The `evented` mixin provides methods allowing an object to trigger events,
* running externally registered event handlers. * running externally registered event handlers.
* *
* @deprecated v1.2, to be removed in v2.0 * @deprecated v1.2, to be removed in v2.0
*/ */
import fireDebugWarning from '../helpers/fireDebugWarning';
const deprecatedNotice =
'The `evented` util is deprecated and will be removed in Flarum 2.0. For more info, please see https://github.com/flarum/core/issues/2547';
export default { export default {
/** /**
* Arrays of registered event handlers, grouped by the event name. * Arrays of registered event handlers, grouped by the event name.
@@ -27,7 +26,7 @@ export default {
* @protected * @protected
*/ */
getHandlers(event) { getHandlers(event) {
fireDebugWarning(deprecatedNotice); fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
this.handlers = this.handlers || {}; this.handlers = this.handlers || {};
@@ -44,7 +43,7 @@ export default {
* @public * @public
*/ */
trigger(event, ...args) { trigger(event, ...args) {
fireDebugWarning(deprecatedNotice); fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
this.getHandlers(event).forEach((handler) => handler.apply(this, args)); this.getHandlers(event).forEach((handler) => handler.apply(this, args));
}, },
@@ -56,7 +55,7 @@ export default {
* @param {function} handler The function to handle the event. * @param {function} handler The function to handle the event.
*/ */
on(event, handler) { on(event, handler) {
fireDebugWarning(deprecatedNotice); fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
this.getHandlers(event).push(handler); this.getHandlers(event).push(handler);
}, },
@@ -69,7 +68,7 @@ export default {
* @param {function} handler The function to handle the event. * @param {function} handler The function to handle the event.
*/ */
one(event, handler) { one(event, handler) {
fireDebugWarning(deprecatedNotice); fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
const wrapper = function () { const wrapper = function () {
handler.apply(this, arguments); handler.apply(this, arguments);
@@ -87,7 +86,7 @@ export default {
* @param {function} handler The function that handles the event. * @param {function} handler The function that handles the event.
*/ */
off(event, handler) { off(event, handler) {
fireDebugWarning(deprecatedNotice); fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
const handlers = this.getHandlers(event); const handlers = this.getHandlers(event);
const index = handlers.indexOf(handler); const index = handlers.indexOf(handler);

View File

@@ -6,7 +6,6 @@ import PostControls from '../utils/PostControls';
import listItems from '../../common/helpers/listItems'; import listItems from '../../common/helpers/listItems';
import ItemList from '../../common/utils/ItemList'; import ItemList from '../../common/utils/ItemList';
import LoadingIndicator from '../../common/components/LoadingIndicator'; import LoadingIndicator from '../../common/components/LoadingIndicator';
import classList from '../../common/utils/classList';
/** /**
* The `Post` component displays a single post. The basic post template just * The `Post` component displays a single post. The basic post template just
@@ -125,16 +124,24 @@ export default class Post extends Component {
* @returns {string[]} * @returns {string[]}
*/ */
classes(existing) { classes(existing) {
let classes = (existing || '').split(' ').concat(['Post']);
const user = this.attrs.post.user(); const user = this.attrs.post.user();
const discussion = this.attrs.post.discussion(); const discussion = this.attrs.post.discussion();
return classList(existing, 'Post', { if (this.loading) {
'Post--loading': this.loading, classes.push('Post--loading');
'Post--by-actor': user === app.session.user, }
'Post--by-start-user': user?.id() === discussion.attribute('startUserId'),
}) if (user && user === app.session.user) {
.split(' ') classes.push('Post--by-actor');
.filter((x) => !!x); }
if (user && user.id() === discussion.attribute('startUserId')) {
classes.push('Post--by-start-user');
}
return classes;
} }
/** /**

View File

@@ -55,6 +55,6 @@ export default class PostMeta extends Component {
* @returns {String} * @returns {String}
*/ */
getPermalink(post) { getPermalink(post) {
return window.location.origin + app.route.post(post); return app.forum.attribute('baseUrl') + app.route.post(post);
} }
} }

View File

@@ -9,8 +9,8 @@ import icon from '../../common/helpers/icon';
import SearchState from '../states/SearchState'; import SearchState from '../states/SearchState';
import DiscussionsSearchSource from './DiscussionsSearchSource'; import DiscussionsSearchSource from './DiscussionsSearchSource';
import UsersSearchSource from './UsersSearchSource'; import UsersSearchSource from './UsersSearchSource';
import { fireDeprecationWarning } from '../../common/helpers/fireDebugWarning';
import type Mithril from 'mithril'; import type Mithril from 'mithril';
import Model from '../../common/Model';
/** /**
* The `SearchSource` interface defines a section of search results in the * The `SearchSource` interface defines a section of search results in the
@@ -53,14 +53,33 @@ export interface SearchAttrs extends ComponentAttrs {
* *
* - state: SearchState instance. * - state: SearchState instance.
*/ */
export default class Search<T extends SearchAttrs = SearchAttrs> extends Component<T> { export default class Search<T extends SearchAttrs = SearchAttrs> extends Component<T, SearchState> {
/** /**
* The minimum query length before sources are searched. * The minimum query length before sources are searched.
*/ */
protected static MIN_SEARCH_LEN = 3; protected static MIN_SEARCH_LEN = 3;
/**
* The instance of `SearchState` for this component.
*/
protected searchState!: SearchState; protected searchState!: SearchState;
/**
* The instance of `SearchState` for this component.
*
* @deprecated Replace with`this.searchState` instead.
*/
// TODO: [Flarum 2.0] Remove this.
// @ts-expect-error This is a get accessor, while superclass defines this as a property. This is needed to prevent breaking changes, however.
protected get state() {
fireDeprecationWarning('`state` property of the Search component is deprecated', '3212');
return this.searchState;
}
protected set state(state: SearchState) {
fireDeprecationWarning('`state` property of the Search component is deprecated', '3212');
this.searchState = state;
}
/** /**
* Whether or not the search input has focus. * Whether or not the search input has focus.
*/ */

View File

@@ -1,50 +0,0 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import Button from '../../common/components/Button';
/**
* The `WelcomeHero` component displays a hero that welcomes the user to the
* forum.
*/
export default class WelcomeHero extends Component {
oninit(vnode) {
super.oninit(vnode);
this.hidden = localStorage.getItem('welcomeHidden');
}
view() {
if (this.hidden) return <div />;
const slideUp = () => {
this.$().slideUp(this.hide.bind(this));
};
return (
<header className="Hero WelcomeHero">
<div class="container">
{Button.component({
icon: 'fas fa-times',
onclick: slideUp,
className: 'Hero-close Button Button--icon Button--link',
'aria-label': app.translator.trans('core.forum.welcome_hero.hide'),
})}
<div className="containerNarrow">
<h2 className="Hero-title">{app.forum.attribute('welcomeTitle')}</h2>
<div className="Hero-subtitle">{m.trust(app.forum.attribute('welcomeMessage'))}</div>
</div>
</div>
</header>
);
}
/**
* Hide the welcome hero.
*/
hide() {
localStorage.setItem('welcomeHidden', 'true');
this.hidden = true;
}
}

View File

@@ -0,0 +1,69 @@
import app from '../app';
import Component from '../../common/Component';
import Button from '../../common/components/Button';
import type Mithril from 'mithril';
export interface IWelcomeHeroAttrs {}
const LOCAL_STORAGE_KEY = 'welcomeHidden';
/**
* The `WelcomeHero` component displays a hero that welcomes the user to the
* forum.
*/
export default class WelcomeHero extends Component<IWelcomeHeroAttrs> {
/**
* @deprecated Extend the `isHidden` method instead.
*/
hidden: boolean = false;
oninit(vnode: Mithril.Vnode<IWelcomeHeroAttrs, this>) {
super.oninit(vnode);
}
view(vnode: Mithril.Vnode<IWelcomeHeroAttrs, this>) {
if (this.isHidden()) return null;
const slideUp = () => {
this.$().slideUp(this.hide.bind(this));
};
return (
<header class="Hero WelcomeHero">
<div class="container">
<Button
icon="fas fa-times"
onclick={slideUp}
className="Hero-close Button Button--icon Button--link"
aria-label={app.translator.trans('core.forum.welcome_hero.hide')}
/>
<div class="containerNarrow">
<h2 class="Hero-title">{app.forum.attribute('welcomeTitle')}</h2>
<div class="Hero-subtitle">{m.trust(app.forum.attribute('welcomeMessage'))}</div>
</div>
</div>
</header>
);
}
/**
* Hide the welcome hero.
*/
hide() {
localStorage.setItem(LOCAL_STORAGE_KEY, 'true');
}
/**
* Determines whether the welcome hero should be hidden.
*
* @returns if the welcome hero is hidden.
*/
isHidden(): boolean {
if (!app.forum.attribute<string>('welcomeTitle')?.trim()) return true;
if (localStorage.getItem(LOCAL_STORAGE_KEY)) return true;
if (this.hidden) return true;
return false;
}
}

View File

@@ -50,7 +50,6 @@
color: var(--muted-color); color: var(--muted-color);
} }
thead th { thead th {
position: -webkit-sticky;
position: sticky; position: sticky;
top: 0; top: 0;
padding-bottom: 10px; padding-bottom: 10px;
@@ -74,7 +73,6 @@
} }
tbody { tbody {
th { th {
position: -webkit-sticky;
position: sticky; position: sticky;
left: 0; left: 0;
padding-right: 50px; padding-right: 50px;

View File

@@ -142,8 +142,7 @@
z-index: var(--zindex-modal); z-index: var(--zindex-modal);
.drawerOpen & { .drawerOpen & {
-webkit-transform: none !important; transform: none !important;
transform: none !important;
} }
} }
.drawer-backdrop { .drawer-backdrop {

View File

@@ -177,7 +177,6 @@
box-shadow: 0 2px 6px var(--shadow-color); box-shadow: 0 2px 6px var(--shadow-color);
visibility: hidden; visibility: hidden;
overflow: auto; overflow: auto;
-webkit-overflow-scrolling: touch;
transform: translate(0, 70vh); transform: translate(0, 70vh);
transition: transform 0.3s, visibility 0s 0.3s; transition: transform 0.3s, visibility 0s 0.3s;
@@ -208,8 +207,7 @@
} }
} }
.open& { .open& {
-webkit-transform: none; transform: none;
transform: none;
visibility: visible; visibility: visible;
transition-delay: 0s; transition-delay: 0s;
} }

View File

@@ -33,7 +33,6 @@
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: var(--zindex-modal); z-index: var(--zindex-modal);
-webkit-overflow-scrolling: touch;
// When fading in the modal, animate it to slide down // When fading in the modal, animate it to slide down
.Modal { .Modal {
@@ -143,8 +142,7 @@
transform: translate(0, 100vh); transform: translate(0, 100vh);
&.in { &.in {
-webkit-transform: none !important; transform: none !important;
transform: none !important;
} }
&:before { &:before {
content: " "; content: " ";
@@ -155,8 +153,7 @@
.Modal { .Modal {
max-width: 100%; max-width: 100%;
margin: 0; margin: 0;
-webkit-transform: none !important; transform: none !important;
transform: none !important;
} }
.Modal-content { .Modal-content {
border-radius: 0; border-radius: 0;
@@ -183,7 +180,6 @@
z-index: 1; z-index: 1;
} }
.Modal-content { .Modal-content {
border: 0; border: 0;
border-radius: var(--border-radius); border-radius: var(--border-radius);
box-shadow: 0 7px 15px var(--shadow-color); box-shadow: 0 7px 15px var(--shadow-color);

View File

@@ -6,7 +6,6 @@
display: inline-block; display: inline-block;
width: auto; width: auto;
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none;
padding-right: 30px; padding-right: 30px;
cursor: pointer; cursor: pointer;
line-height: 1; line-height: 1;

View File

@@ -65,11 +65,7 @@
// CSS3 Content Columns // CSS3 Content Columns
/** @deprecated */ /** @deprecated */
.content-columns(@column-count; @column-gap: @grid-gutter-width) { .content-columns(@column-count; @column-gap: @grid-gutter-width) {
// Safari
-webkit-column-count: @column-count;
column-count: @column-count; column-count: @column-count;
// Safari
-webkit-column-gap: @column-gap;
column-gap: @column-gap; column-gap: @column-gap;
} }
@@ -85,7 +81,6 @@
// Placeholder text // Placeholder text
.placeholder(@color) { .placeholder(@color) {
// Safari // Safari
&::-webkit-input-placeholder,
&::placeholder { &::placeholder {
color: @color; color: @color;
} }
@@ -176,7 +171,7 @@
// User select // User select
// For selecting text on the page // For selecting text on the page
.user-select(@select) { .user-select(@select) {
// Safari + MS Edge // Safari
-webkit-user-select: @select; -webkit-user-select: @select;
user-select: @select; user-select: @select;
} }

View File

@@ -80,7 +80,6 @@
padding: 15px 0; padding: 15px 0;
white-space: nowrap; white-space: nowrap;
overflow: auto; overflow: auto;
-webkit-overflow-scrolling: touch;
&:after { &:after {
content: " "; content: " ";

View File

@@ -107,25 +107,15 @@
} }
.animated { .animated {
-webkit-animation-fill-mode: both;
animation-fill-mode: both; animation-fill-mode: both;
-webkit-animation-duration: 0.5s;
animation-duration: 0.5s; animation-duration: 0.5s;
animation-delay: 1.7s; animation-delay: 1.7s;
-webkit-animation-delay: 1.7s;
}
@-webkit-keyframes fadeIn {
0% {opacity: 0}
100% {opacity: 1}
} }
@keyframes fadeIn { @keyframes fadeIn {
0% {opacity: 0} 0% {opacity: 0}
100% {opacity: 1} 100% {opacity: 1}
} }
.fadeIn { .fadeIn {
-webkit-animation-name: fadeIn;
animation-name: fadeIn; animation-name: fadeIn;
} }