Merge pull request #1416 from florianorben/PLT-1125
PLT-1125: Add "Preview pre-release features" option
Этот коммит содержится в:
@@ -12,6 +12,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW = "direct_channel_show"
|
PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW = "direct_channel_show"
|
||||||
PREFERENCE_CATEGORY_TUTORIAL_STEPS = "tutorial_step"
|
PREFERENCE_CATEGORY_TUTORIAL_STEPS = "tutorial_step"
|
||||||
|
PREFERENCE_CATEGORY_ADVANCED_SETTINGS = "advanced_settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Preference struct {
|
type Preference struct {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
l4g "code.google.com/p/log4go"
|
||||||
"github.com/go-gorp/gorp"
|
"github.com/go-gorp/gorp"
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
"github.com/mattermost/platform/utils"
|
"github.com/mattermost/platform/utils"
|
||||||
@@ -13,6 +14,10 @@ type SqlPreferenceStore struct {
|
|||||||
*SqlStore
|
*SqlStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
FEATURE_TOGGLE_PREFIX = "feature_enabled_"
|
||||||
|
)
|
||||||
|
|
||||||
func NewSqlPreferenceStore(sqlStore *SqlStore) PreferenceStore {
|
func NewSqlPreferenceStore(sqlStore *SqlStore) PreferenceStore {
|
||||||
s := &SqlPreferenceStore{sqlStore}
|
s := &SqlPreferenceStore{sqlStore}
|
||||||
|
|
||||||
@@ -36,6 +41,23 @@ func (s SqlPreferenceStore) CreateIndexesIfNotExists() {
|
|||||||
s.CreateIndexIfNotExists("idx_preferences_name", "Preferences", "Name")
|
s.CreateIndexIfNotExists("idx_preferences_name", "Preferences", "Name")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s SqlPreferenceStore) DeleteUnusedFeatures() {
|
||||||
|
l4g.Debug("Deleting any unused pre-release features")
|
||||||
|
|
||||||
|
sql := `DELETE
|
||||||
|
FROM Preferences
|
||||||
|
WHERE
|
||||||
|
Category = :Category
|
||||||
|
AND Value = :Value
|
||||||
|
AND Name LIKE '` + FEATURE_TOGGLE_PREFIX + `%'`
|
||||||
|
|
||||||
|
queryParams := map[string]string{
|
||||||
|
"Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS,
|
||||||
|
"Value": "false",
|
||||||
|
}
|
||||||
|
s.GetMaster().Exec(sql, queryParams)
|
||||||
|
}
|
||||||
|
|
||||||
func (s SqlPreferenceStore) Save(preferences *model.Preferences) StoreChannel {
|
func (s SqlPreferenceStore) Save(preferences *model.Preferences) StoreChannel {
|
||||||
storeChannel := make(StoreChannel)
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
@@ -257,3 +279,28 @@ func (s SqlPreferenceStore) PermanentDeleteByUser(userId string) StoreChannel {
|
|||||||
|
|
||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s SqlPreferenceStore) IsFeatureEnabled(feature, userId string) StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
if value, err := s.GetReplica().SelectStr(`SELECT
|
||||||
|
value
|
||||||
|
FROM
|
||||||
|
Preferences
|
||||||
|
WHERE
|
||||||
|
UserId = :UserId
|
||||||
|
AND Category = :Category
|
||||||
|
AND Name = :Name`, map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "Name": FEATURE_TOGGLE_PREFIX + feature}); err != nil {
|
||||||
|
result.Err = model.NewAppError("SqlPreferenceStore.IsFeatureEnabled", "We encountered an error while finding a pre release feature preference", err.Error())
|
||||||
|
} else {
|
||||||
|
result.Data = value == "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|||||||
@@ -232,3 +232,138 @@ func TestPreferenceDelete(t *testing.T) {
|
|||||||
t.Fatal(result.Err)
|
t.Fatal(result.Err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsFeatureEnabled(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
feature1 := "testFeat1"
|
||||||
|
feature2 := "testFeat2"
|
||||||
|
feature3 := "testFeat3"
|
||||||
|
|
||||||
|
userId := model.NewId()
|
||||||
|
category := model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS
|
||||||
|
|
||||||
|
features := model.Preferences{
|
||||||
|
{
|
||||||
|
UserId: userId,
|
||||||
|
Category: category,
|
||||||
|
Name: FEATURE_TOGGLE_PREFIX + feature1,
|
||||||
|
Value: "true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UserId: userId,
|
||||||
|
Category: category,
|
||||||
|
Name: model.NewId(),
|
||||||
|
Value: "false",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UserId: userId,
|
||||||
|
Category: model.NewId(),
|
||||||
|
Name: FEATURE_TOGGLE_PREFIX + feature1,
|
||||||
|
Value: "false",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UserId: model.NewId(),
|
||||||
|
Category: category,
|
||||||
|
Name: FEATURE_TOGGLE_PREFIX + feature2,
|
||||||
|
Value: "false",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UserId: model.NewId(),
|
||||||
|
Category: category,
|
||||||
|
Name: FEATURE_TOGGLE_PREFIX + feature3,
|
||||||
|
Value: "foobar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Must(store.Preference().Save(&features))
|
||||||
|
|
||||||
|
if result := <-store.Preference().IsFeatureEnabled(feature1, userId); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else if data := result.Data.(bool); data != true {
|
||||||
|
t.Fatalf("got incorrect setting for feature1, %v=%v", true, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-store.Preference().IsFeatureEnabled(feature2, userId); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else if data := result.Data.(bool); data != false {
|
||||||
|
t.Fatalf("got incorrect setting for feature2, %v=%v", false, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we get false if something different than "true" or "false" has been saved to database
|
||||||
|
if result := <-store.Preference().IsFeatureEnabled(feature3, userId); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else if data := result.Data.(bool); data != false {
|
||||||
|
t.Fatalf("got incorrect setting for feature3, %v=%v", false, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure false is returned if a non-existent feature is queried
|
||||||
|
if result := <-store.Preference().IsFeatureEnabled("someOtherFeature", userId); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else if data := result.Data.(bool); data != false {
|
||||||
|
t.Fatalf("got incorrect setting for non-existent feature 'someOtherFeature', %v=%v", false, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteUnusedFeatures(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
userId1 := model.NewId()
|
||||||
|
userId2 := model.NewId()
|
||||||
|
category := model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS
|
||||||
|
feature1 := "feature1"
|
||||||
|
feature2 := "feature2"
|
||||||
|
|
||||||
|
features := model.Preferences{
|
||||||
|
{
|
||||||
|
UserId: userId1,
|
||||||
|
Category: category,
|
||||||
|
Name: FEATURE_TOGGLE_PREFIX + feature1,
|
||||||
|
Value: "true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UserId: userId2,
|
||||||
|
Category: category,
|
||||||
|
Name: FEATURE_TOGGLE_PREFIX + feature1,
|
||||||
|
Value: "false",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UserId: userId1,
|
||||||
|
Category: category,
|
||||||
|
Name: FEATURE_TOGGLE_PREFIX + feature2,
|
||||||
|
Value: "false",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UserId: userId2,
|
||||||
|
Category: category,
|
||||||
|
Name: FEATURE_TOGGLE_PREFIX + feature2,
|
||||||
|
Value: "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Must(store.Preference().Save(&features))
|
||||||
|
|
||||||
|
store.(*SqlStore).preference.(*SqlPreferenceStore).DeleteUnusedFeatures()
|
||||||
|
|
||||||
|
//make sure features with value "false" have actually been deleted from the database
|
||||||
|
if val, err := store.(*SqlStore).preference.(*SqlPreferenceStore).GetReplica().SelectInt(`SELECT COUNT(*)
|
||||||
|
FROM Preferences
|
||||||
|
WHERE Category = :Category
|
||||||
|
AND Value = :Val
|
||||||
|
AND Name LIKE '`+FEATURE_TOGGLE_PREFIX+`%'`, map[string]interface{}{"Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "Val": "false"}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if val != 0 {
|
||||||
|
t.Fatalf("Found %d features with value 'false', expected all to be deleted", val)
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// make sure features with value "true" remain saved
|
||||||
|
if val, err := store.(*SqlStore).preference.(*SqlPreferenceStore).GetReplica().SelectInt(`SELECT COUNT(*)
|
||||||
|
FROM Preferences
|
||||||
|
WHERE Category = :Category
|
||||||
|
AND Value = :Val
|
||||||
|
AND Name LIKE '`+FEATURE_TOGGLE_PREFIX+`%'`, map[string]interface{}{"Category": model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "Val": "true"}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if val == 0 {
|
||||||
|
t.Fatalf("Found %d features with value 'true', expected to find at least %d features", val, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -148,6 +148,8 @@ func NewSqlStore() Store {
|
|||||||
sqlStore.webhook.(*SqlWebhookStore).CreateIndexesIfNotExists()
|
sqlStore.webhook.(*SqlWebhookStore).CreateIndexesIfNotExists()
|
||||||
sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists()
|
sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists()
|
||||||
|
|
||||||
|
sqlStore.preference.(*SqlPreferenceStore).DeleteUnusedFeatures()
|
||||||
|
|
||||||
if model.IsPreviousVersion(schemaVersion) || isSchemaVersion07 || isSchemaVersion10 {
|
if model.IsPreviousVersion(schemaVersion) || isSchemaVersion07 || isSchemaVersion10 {
|
||||||
sqlStore.system.Update(&model.System{Name: "Version", Value: model.CurrentVersion})
|
sqlStore.system.Update(&model.System{Name: "Version", Value: model.CurrentVersion})
|
||||||
l4g.Warn("The database schema has been upgraded to version " + model.CurrentVersion)
|
l4g.Warn("The database schema has been upgraded to version " + model.CurrentVersion)
|
||||||
|
|||||||
@@ -186,4 +186,5 @@ type PreferenceStore interface {
|
|||||||
GetCategory(userId string, category string) StoreChannel
|
GetCategory(userId string, category string) StoreChannel
|
||||||
GetAll(userId string) StoreChannel
|
GetAll(userId string) StoreChannel
|
||||||
PermanentDeleteByUser(userId string) StoreChannel
|
PermanentDeleteByUser(userId string) StoreChannel
|
||||||
|
IsFeatureEnabled(feature, userId string) StoreChannel
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import * as Utils from '../utils/utils.jsx';
|
|||||||
import Constants from '../utils/constants.jsx';
|
import Constants from '../utils/constants.jsx';
|
||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
const KeyCodes = Constants.KeyCodes;
|
const KeyCodes = Constants.KeyCodes;
|
||||||
|
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
|
||||||
|
|
||||||
export default class Textbox extends React.Component {
|
export default class Textbox extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -303,7 +304,19 @@ export default class Textbox extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const previewLinkVisible = this.props.messageText.length > 0;
|
let previewLink = null;
|
||||||
|
if (Utils.isFeatureEnabled(PreReleaseFeatures.MARKDOWN_PREVIEW)) {
|
||||||
|
const previewLinkVisible = this.props.messageText.length > 0;
|
||||||
|
previewLink = (
|
||||||
|
<a
|
||||||
|
style={{visibility: previewLinkVisible ? 'visible' : 'hidden'}}
|
||||||
|
onClick={this.showPreview}
|
||||||
|
className='textbox-preview-link'
|
||||||
|
>
|
||||||
|
{this.state.preview ? 'Edit message' : 'Preview'}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -342,19 +355,13 @@ export default class Textbox extends React.Component {
|
|||||||
dangerouslySetInnerHTML={{__html: this.state.preview ? TextFormatting.formatText(this.props.messageText) : ''}}
|
dangerouslySetInnerHTML={{__html: this.state.preview ? TextFormatting.formatText(this.props.messageText) : ''}}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
{previewLink}
|
||||||
<a
|
<a
|
||||||
onClick={this.showHelp}
|
onClick={this.showHelp}
|
||||||
className='textbox-help-link'
|
className='textbox-help-link'
|
||||||
>
|
>
|
||||||
{'Help'}
|
{'Help'}
|
||||||
</a>
|
</a>
|
||||||
<a
|
|
||||||
style={{visibility: previewLinkVisible ? 'visible' : 'hidden'}}
|
|
||||||
onClick={this.showPreview}
|
|
||||||
className='textbox-preview-link'
|
|
||||||
>
|
|
||||||
{this.state.preview ? 'Edit' : 'Preview'}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import SettingItemMin from '../setting_item_min.jsx';
|
|||||||
import SettingItemMax from '../setting_item_max.jsx';
|
import SettingItemMax from '../setting_item_max.jsx';
|
||||||
import Constants from '../../utils/constants.jsx';
|
import Constants from '../../utils/constants.jsx';
|
||||||
import PreferenceStore from '../../stores/preference_store.jsx';
|
import PreferenceStore from '../../stores/preference_store.jsx';
|
||||||
|
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
|
||||||
|
|
||||||
export default class AdvancedSettingsDisplay extends React.Component {
|
export default class AdvancedSettingsDisplay extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -13,21 +14,33 @@ export default class AdvancedSettingsDisplay extends React.Component {
|
|||||||
|
|
||||||
this.updateSection = this.updateSection.bind(this);
|
this.updateSection = this.updateSection.bind(this);
|
||||||
this.updateSetting = this.updateSetting.bind(this);
|
this.updateSetting = this.updateSetting.bind(this);
|
||||||
this.setupInitialState = this.setupInitialState.bind(this);
|
this.toggleFeature = this.toggleFeature.bind(this);
|
||||||
|
this.saveEnabledFeatures = this.saveEnabledFeatures.bind(this);
|
||||||
|
|
||||||
this.state = this.setupInitialState();
|
const preReleaseFeaturesKeys = Object.keys(PreReleaseFeatures);
|
||||||
}
|
const advancedSettings = PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS);
|
||||||
|
const settings = {
|
||||||
setupInitialState() {
|
send_on_ctrl_enter: PreferenceStore.getPreference(
|
||||||
const sendOnCtrlEnter = PreferenceStore.getPreference(
|
Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
|
||||||
Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
|
'send_on_ctrl_enter',
|
||||||
'send_on_ctrl_enter',
|
{value: 'false'}
|
||||||
{value: 'false'}
|
).value
|
||||||
).value;
|
|
||||||
|
|
||||||
return {
|
|
||||||
settings: {send_on_ctrl_enter: sendOnCtrlEnter}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let enabledFeatures = 0;
|
||||||
|
advancedSettings.forEach((setting) => {
|
||||||
|
preReleaseFeaturesKeys.forEach((key) => {
|
||||||
|
const feature = PreReleaseFeatures[key];
|
||||||
|
if (setting.name === Constants.FeatureTogglePrefix + feature.label) {
|
||||||
|
settings[setting.name] = setting.value;
|
||||||
|
if (setting.value === 'true') {
|
||||||
|
enabledFeatures++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.state = {preReleaseFeatures: PreReleaseFeatures, settings, preReleaseFeaturesKeys, enabledFeatures};
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSetting(setting, value) {
|
updateSetting(setting, value) {
|
||||||
@@ -36,14 +49,45 @@ export default class AdvancedSettingsDisplay extends React.Component {
|
|||||||
this.setState(settings);
|
this.setState(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit(setting) {
|
toggleFeature(feature, checked) {
|
||||||
const preference = PreferenceStore.setPreference(
|
const settings = this.state.settings;
|
||||||
Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
|
settings[Constants.FeatureTogglePrefix + feature] = String(checked);
|
||||||
setting,
|
|
||||||
this.state.settings[setting]
|
|
||||||
);
|
|
||||||
|
|
||||||
Client.savePreferences([preference],
|
let enabledFeatures = 0;
|
||||||
|
Object.keys(this.state.settings).forEach((setting) => {
|
||||||
|
if (setting.lastIndexOf(Constants.FeatureTogglePrefix) === 0 && this.state.settings[setting] === 'true') {
|
||||||
|
enabledFeatures++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({settings, enabledFeatures});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveEnabledFeatures() {
|
||||||
|
const features = [];
|
||||||
|
Object.keys(this.state.settings).forEach((setting) => {
|
||||||
|
if (setting.lastIndexOf(Constants.FeatureTogglePrefix) === 0) {
|
||||||
|
features.push(setting);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.handleSubmit(features);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(settings) {
|
||||||
|
const preferences = [];
|
||||||
|
|
||||||
|
(Array.isArray(settings) ? settings : [settings]).forEach((setting) => {
|
||||||
|
preferences.push(
|
||||||
|
PreferenceStore.setPreference(
|
||||||
|
Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
|
||||||
|
setting,
|
||||||
|
String(this.state.settings[setting])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Client.savePreferences(preferences,
|
||||||
() => {
|
() => {
|
||||||
PreferenceStore.emitChange();
|
PreferenceStore.emitChange();
|
||||||
this.updateSection('');
|
this.updateSection('');
|
||||||
@@ -118,6 +162,66 @@ export default class AdvancedSettingsDisplay extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previewFeaturesSection;
|
||||||
|
let previewFeaturesSectionDivider;
|
||||||
|
if (this.state.preReleaseFeaturesKeys.length > 0) {
|
||||||
|
previewFeaturesSectionDivider = (
|
||||||
|
<div className='divider-light'/>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.props.activeSection === 'advancedPreviewFeatures') {
|
||||||
|
const inputs = [];
|
||||||
|
|
||||||
|
this.state.preReleaseFeaturesKeys.forEach((key) => {
|
||||||
|
const feature = this.state.preReleaseFeatures[key];
|
||||||
|
inputs.push(
|
||||||
|
<div key={'advancedPreviewFeatures_' + feature.label}>
|
||||||
|
<div className='checkbox'>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={this.state.settings[Constants.FeatureTogglePrefix + feature.label] === 'true'}
|
||||||
|
onChange={(e) => {
|
||||||
|
this.toggleFeature(feature.label, e.target.checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{feature.description}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
inputs.push(
|
||||||
|
<div key='advancedPreviewFeatures_helptext'>
|
||||||
|
<br/>
|
||||||
|
{'Check any pre-released features you\'d like to preview.'}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
previewFeaturesSection = (
|
||||||
|
<SettingItemMax
|
||||||
|
title='Preview pre-release features'
|
||||||
|
inputs={inputs}
|
||||||
|
submit={this.saveEnabledFeatures}
|
||||||
|
server_error={serverError}
|
||||||
|
updateSection={(e) => {
|
||||||
|
this.updateSection('');
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
previewFeaturesSection = (
|
||||||
|
<SettingItemMin
|
||||||
|
title='Preview pre-release features'
|
||||||
|
describe={this.state.enabledFeatures + (this.state.enabledFeatures === 1 ? ' Feature ' : ' Features ') + 'enabled'}
|
||||||
|
updateSection={() => this.props.updateSection('advancedPreviewFeatures')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='modal-header'>
|
<div className='modal-header'>
|
||||||
@@ -145,6 +249,8 @@ export default class AdvancedSettingsDisplay extends React.Component {
|
|||||||
<h3 className='tab-header'>{'Advanced Settings'}</h3>
|
<h3 className='tab-header'>{'Advanced Settings'}</h3>
|
||||||
<div className='divider-dark first'/>
|
<div className='divider-dark first'/>
|
||||||
{ctrlSendSection}
|
{ctrlSendSection}
|
||||||
|
{previewFeaturesSectionDivider}
|
||||||
|
{previewFeaturesSection}
|
||||||
<div className='divider-dark'/>
|
<div className='divider-dark'/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -398,5 +398,12 @@ export default {
|
|||||||
},
|
},
|
||||||
NotificationPrefs: {
|
NotificationPrefs: {
|
||||||
MENTION: 'mention'
|
MENTION: 'mention'
|
||||||
|
},
|
||||||
|
FeatureTogglePrefix: 'feature_enabled_',
|
||||||
|
PRE_RELEASE_FEATURES: {
|
||||||
|
MARKDOWN_PREVIEW: {
|
||||||
|
label: 'markdown_preview', // github issue: https://github.com/mattermost/platform/pull/1389
|
||||||
|
description: 'Show markdown preview option in message input box'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1221,3 +1221,7 @@ export function getPostTerm(post) {
|
|||||||
|
|
||||||
return postTerm;
|
return postTerm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isFeatureEnabled(feature) {
|
||||||
|
return PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, Constants.FeatureTogglePrefix + feature.label, {value: 'false'}).value === 'true';
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ body.ios {
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
white-space: normal;
|
||||||
}
|
}
|
||||||
.textbox-preview-link, .textbox-help-link {
|
.textbox-preview-link, .textbox-help-link {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -283,14 +284,14 @@ body.ios {
|
|||||||
.custom-textarea {
|
.custom-textarea {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-right: 28px;
|
padding-right: 28px;
|
||||||
max-height: 160px;
|
max-height: 162px !important;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
.textarea-div {
|
.textarea-div {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-right: 30px;
|
padding-right: 30px;
|
||||||
max-height: 160px;
|
max-height: 163px !important;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user