Merge pull request #1269 from mattermost/plt-848-3
PLT-848 Add tutorial intro screens and popover tips
Этот коммит содержится в:
@@ -1,17 +1,47 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var CreatePost = require('../components/create_post.jsx');
|
const TutorialIntroScreens = require('./tutorial/tutorial_intro_screens.jsx');
|
||||||
var PostsViewContainer = require('../components/posts_view_container.jsx');
|
const CreatePost = require('./create_post.jsx');
|
||||||
var ChannelHeader = require('../components/channel_header.jsx');
|
const PostsViewContainer = require('./posts_view_container.jsx');
|
||||||
var Navbar = require('../components/navbar.jsx');
|
const ChannelHeader = require('./channel_header.jsx');
|
||||||
var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
|
const Navbar = require('./navbar.jsx');
|
||||||
|
const FileUploadOverlay = require('./file_upload_overlay.jsx');
|
||||||
|
|
||||||
|
const PreferenceStore = require('../stores/preference_store.jsx');
|
||||||
|
const UserStore = require('../stores/user_store.jsx');
|
||||||
|
|
||||||
|
const Constants = require('../utils/constants.jsx');
|
||||||
|
const TutorialSteps = Constants.TutorialSteps;
|
||||||
|
const Preferences = Constants.Preferences;
|
||||||
|
|
||||||
export default class CenterPanel extends React.Component {
|
export default class CenterPanel extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.onPreferenceChange = this.onPreferenceChange.bind(this);
|
||||||
|
|
||||||
|
const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
|
||||||
|
this.state = {showTutorialScreens: parseInt(tutorialPref.value, 10) === TutorialSteps.INTRO_SCREENS};
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
PreferenceStore.addChangeListener(this.onPreferenceChange);
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
PreferenceStore.removeChangeListener(this.onPreferenceChange);
|
||||||
|
}
|
||||||
|
onPreferenceChange() {
|
||||||
|
const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
|
||||||
|
this.setState({showTutorialScreens: parseInt(tutorialPref.value, 10) <= TutorialSteps.INTRO_SCREENS});
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
|
let postsContainer;
|
||||||
|
if (this.state.showTutorialScreens) {
|
||||||
|
postsContainer = <TutorialIntroScreens />;
|
||||||
|
} else {
|
||||||
|
postsContainer = <PostsViewContainer />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='inner__wrap channel__wrap'>
|
<div className='inner__wrap channel__wrap'>
|
||||||
<div className='row header'>
|
<div className='row header'>
|
||||||
@@ -32,7 +62,7 @@ export default class CenterPanel extends React.Component {
|
|||||||
<ChannelHeader />
|
<ChannelHeader />
|
||||||
</div>
|
</div>
|
||||||
<div id='post-list'>
|
<div id='post-list'>
|
||||||
<PostsViewContainer />
|
{postsContainer}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className='post-create__container'
|
className='post-create__container'
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var CenterPanel = require('../components/center_panel.jsx');
|
const CenterPanel = require('../components/center_panel.jsx');
|
||||||
var Sidebar = require('../components/sidebar.jsx');
|
const Sidebar = require('../components/sidebar.jsx');
|
||||||
var SidebarRight = require('../components/sidebar_right.jsx');
|
const SidebarRight = require('../components/sidebar_right.jsx');
|
||||||
var SidebarRightMenu = require('../components/sidebar_right_menu.jsx');
|
const SidebarRightMenu = require('../components/sidebar_right_menu.jsx');
|
||||||
|
|
||||||
export default class ChannelView extends React.Component {
|
export default class ChannelView extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
|
||||||
const Client = require('../utils/client.jsx');
|
|
||||||
const AsyncClient = require('../utils/async_client.jsx');
|
|
||||||
const ChannelStore = require('../stores/channel_store.jsx');
|
|
||||||
const PostStore = require('../stores/post_store.jsx');
|
|
||||||
const UserStore = require('../stores/user_store.jsx');
|
|
||||||
const SocketStore = require('../stores/socket_store.jsx');
|
|
||||||
const PreferenceStore = require('../stores/preference_store.jsx');
|
|
||||||
const MsgTyping = require('./msg_typing.jsx');
|
const MsgTyping = require('./msg_typing.jsx');
|
||||||
const Textbox = require('./textbox.jsx');
|
const Textbox = require('./textbox.jsx');
|
||||||
const FileUpload = require('./file_upload.jsx');
|
const FileUpload = require('./file_upload.jsx');
|
||||||
const FilePreview = require('./file_preview.jsx');
|
const FilePreview = require('./file_preview.jsx');
|
||||||
|
const TutorialTip = require('./tutorial/tutorial_tip.jsx');
|
||||||
|
|
||||||
|
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
||||||
|
const Client = require('../utils/client.jsx');
|
||||||
|
const AsyncClient = require('../utils/async_client.jsx');
|
||||||
const Utils = require('../utils/utils.jsx');
|
const Utils = require('../utils/utils.jsx');
|
||||||
|
|
||||||
|
const ChannelStore = require('../stores/channel_store.jsx');
|
||||||
|
const PostStore = require('../stores/post_store.jsx');
|
||||||
|
const UserStore = require('../stores/user_store.jsx');
|
||||||
|
const PreferenceStore = require('../stores/preference_store.jsx');
|
||||||
|
const SocketStore = require('../stores/socket_store.jsx');
|
||||||
|
|
||||||
const Constants = require('../utils/constants.jsx');
|
const Constants = require('../utils/constants.jsx');
|
||||||
|
const Preferences = Constants.Preferences;
|
||||||
|
const TutorialSteps = Constants.TutorialSteps;
|
||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
const KeyCodes = Constants.KeyCodes;
|
const KeyCodes = Constants.KeyCodes;
|
||||||
|
|
||||||
@@ -36,15 +41,16 @@ export default class CreatePost extends React.Component {
|
|||||||
this.handleTextDrop = this.handleTextDrop.bind(this);
|
this.handleTextDrop = this.handleTextDrop.bind(this);
|
||||||
this.removePreview = this.removePreview.bind(this);
|
this.removePreview = this.removePreview.bind(this);
|
||||||
this.onChange = this.onChange.bind(this);
|
this.onChange = this.onChange.bind(this);
|
||||||
|
this.onPreferenceChange = this.onPreferenceChange.bind(this);
|
||||||
this.getFileCount = this.getFileCount.bind(this);
|
this.getFileCount = this.getFileCount.bind(this);
|
||||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||||
this.handleResize = this.handleResize.bind(this);
|
this.handleResize = this.handleResize.bind(this);
|
||||||
this.sendMessage = this.sendMessage.bind(this);
|
this.sendMessage = this.sendMessage.bind(this);
|
||||||
this.onPreferenceChange = this.onPreferenceChange.bind(this);
|
|
||||||
|
|
||||||
PostStore.clearDraftUploads();
|
PostStore.clearDraftUploads();
|
||||||
|
|
||||||
const draft = this.getCurrentDraft();
|
const draft = this.getCurrentDraft();
|
||||||
|
const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
channelId: ChannelStore.getCurrentId(),
|
channelId: ChannelStore.getCurrentId(),
|
||||||
@@ -55,16 +61,12 @@ export default class CreatePost extends React.Component {
|
|||||||
initialText: draft.messageText,
|
initialText: draft.messageText,
|
||||||
windowWidth: Utils.windowWidth(),
|
windowWidth: Utils.windowWidth(),
|
||||||
windowHeight: Utils.windowHeight(),
|
windowHeight: Utils.windowHeight(),
|
||||||
ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value
|
ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value,
|
||||||
|
showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.POST_POPOVER
|
||||||
};
|
};
|
||||||
|
|
||||||
PreferenceStore.addChangeListener(this.onPreferenceChange);
|
PreferenceStore.addChangeListener(this.onPreferenceChange);
|
||||||
}
|
}
|
||||||
onPreferenceChange() {
|
|
||||||
this.setState({
|
|
||||||
ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
handleResize() {
|
handleResize() {
|
||||||
this.setState({
|
this.setState({
|
||||||
windowWidth: Utils.windowWidth(),
|
windowWidth: Utils.windowWidth(),
|
||||||
@@ -318,11 +320,13 @@ export default class CreatePost extends React.Component {
|
|||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
ChannelStore.addChangeListener(this.onChange);
|
ChannelStore.addChangeListener(this.onChange);
|
||||||
|
PreferenceStore.addChangeListener(this.onPreferenceChange);
|
||||||
this.resizePostHolder();
|
this.resizePostHolder();
|
||||||
window.addEventListener('resize', this.handleResize);
|
window.addEventListener('resize', this.handleResize);
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ChannelStore.removeChangeListener(this.onChange);
|
ChannelStore.removeChangeListener(this.onChange);
|
||||||
|
PreferenceStore.removeChangeListener(this.onPreferenceChange);
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
}
|
}
|
||||||
onChange() {
|
onChange() {
|
||||||
@@ -333,6 +337,13 @@ export default class CreatePost extends React.Component {
|
|||||||
this.setState({channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress});
|
this.setState({channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onPreferenceChange() {
|
||||||
|
const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
|
||||||
|
this.setState({
|
||||||
|
showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.POST_POPOVER,
|
||||||
|
ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value
|
||||||
|
});
|
||||||
|
}
|
||||||
getFileCount(channelId) {
|
getFileCount(channelId) {
|
||||||
if (channelId === this.state.channelId) {
|
if (channelId === this.state.channelId) {
|
||||||
return this.state.previews.length + this.state.uploadsInProgress.length;
|
return this.state.previews.length + this.state.uploadsInProgress.length;
|
||||||
@@ -367,6 +378,25 @@ export default class CreatePost extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
createTutorialTip() {
|
||||||
|
const screens = [];
|
||||||
|
|
||||||
|
screens.push(
|
||||||
|
<div>
|
||||||
|
<h4>{'Sending Messages'}</h4>
|
||||||
|
<p>{'Type here to write a message.'}</p>
|
||||||
|
<p>{'Click the attachment button to upload an image or a file.'}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TutorialTip
|
||||||
|
placement='top'
|
||||||
|
screens={screens}
|
||||||
|
overlayClass='tip-overlay--chat'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
render() {
|
render() {
|
||||||
let serverError = null;
|
let serverError = null;
|
||||||
if (this.state.serverError) {
|
if (this.state.serverError) {
|
||||||
@@ -398,6 +428,11 @@ export default class CreatePost extends React.Component {
|
|||||||
postFooterClassName += ' has-error';
|
postFooterClassName += ' has-error';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tutorialTip = null;
|
||||||
|
if (this.state.showTutorialTip) {
|
||||||
|
tutorialTip = this.createTutorialTip();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
id='create_post'
|
id='create_post'
|
||||||
@@ -436,6 +471,7 @@ export default class CreatePost extends React.Component {
|
|||||||
>
|
>
|
||||||
<i className='fa fa-paper-plane' />
|
<i className='fa fa-paper-plane' />
|
||||||
</a>
|
</a>
|
||||||
|
{tutorialTip}
|
||||||
</div>
|
</div>
|
||||||
<div className={postFooterClassName}>
|
<div className={postFooterClassName}>
|
||||||
{postError}
|
{postError}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export default class NavbarDropdown extends React.Component {
|
|||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.props.teamType === 'O') {
|
if (this.props.teamType === Constants.OPEN_TEAM) {
|
||||||
teamLink = (
|
teamLink = (
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -2,15 +2,18 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
const PostsView = require('./posts_view.jsx');
|
const PostsView = require('./posts_view.jsx');
|
||||||
|
const LoadingScreen = require('./loading_screen.jsx');
|
||||||
|
|
||||||
const ChannelStore = require('../stores/channel_store.jsx');
|
const ChannelStore = require('../stores/channel_store.jsx');
|
||||||
const PostStore = require('../stores/post_store.jsx');
|
const PostStore = require('../stores/post_store.jsx');
|
||||||
const Constants = require('../utils/constants.jsx');
|
|
||||||
const ActionTypes = Constants.ActionTypes;
|
|
||||||
const Utils = require('../utils/utils.jsx');
|
const Utils = require('../utils/utils.jsx');
|
||||||
const Client = require('../utils/client.jsx');
|
const Client = require('../utils/client.jsx');
|
||||||
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
||||||
const AsyncClient = require('../utils/async_client.jsx');
|
const AsyncClient = require('../utils/async_client.jsx');
|
||||||
const LoadingScreen = require('./loading_screen.jsx');
|
|
||||||
|
const Constants = require('../utils/constants.jsx');
|
||||||
|
const ActionTypes = Constants.ActionTypes;
|
||||||
|
|
||||||
import {createChannelIntroMessage} from '../utils/channel_intro_mssages.jsx';
|
import {createChannelIntroMessage} from '../utils/channel_intro_mssages.jsx';
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
const AsyncClient = require('../utils/async_client.jsx');
|
|
||||||
const ChannelStore = require('../stores/channel_store.jsx');
|
|
||||||
const Client = require('../utils/client.jsx');
|
|
||||||
const Constants = require('../utils/constants.jsx');
|
|
||||||
const PreferenceStore = require('../stores/preference_store.jsx');
|
|
||||||
const NewChannelFlow = require('./new_channel_flow.jsx');
|
const NewChannelFlow = require('./new_channel_flow.jsx');
|
||||||
const MoreDirectChannels = require('./more_direct_channels.jsx');
|
const MoreDirectChannels = require('./more_direct_channels.jsx');
|
||||||
const SearchBox = require('./search_bar.jsx');
|
const SearchBox = require('./search_bar.jsx');
|
||||||
const SidebarHeader = require('./sidebar_header.jsx');
|
const SidebarHeader = require('./sidebar_header.jsx');
|
||||||
const TeamStore = require('../stores/team_store.jsx');
|
|
||||||
const UnreadChannelIndicator = require('./unread_channel_indicator.jsx');
|
const UnreadChannelIndicator = require('./unread_channel_indicator.jsx');
|
||||||
|
const TutorialTip = require('./tutorial/tutorial_tip.jsx');
|
||||||
|
|
||||||
|
const ChannelStore = require('../stores/channel_store.jsx');
|
||||||
const UserStore = require('../stores/user_store.jsx');
|
const UserStore = require('../stores/user_store.jsx');
|
||||||
|
const TeamStore = require('../stores/team_store.jsx');
|
||||||
|
const PreferenceStore = require('../stores/preference_store.jsx');
|
||||||
|
|
||||||
|
const AsyncClient = require('../utils/async_client.jsx');
|
||||||
|
const Client = require('../utils/client.jsx');
|
||||||
const Utils = require('../utils/utils.jsx');
|
const Utils = require('../utils/utils.jsx');
|
||||||
|
|
||||||
|
const Constants = require('../utils/constants.jsx');
|
||||||
|
const Preferences = Constants.Preferences;
|
||||||
|
const TutorialSteps = Constants.TutorialSteps;
|
||||||
|
|
||||||
const Tooltip = ReactBootstrap.Tooltip;
|
const Tooltip = ReactBootstrap.Tooltip;
|
||||||
const OverlayTrigger = ReactBootstrap.OverlayTrigger;
|
const OverlayTrigger = ReactBootstrap.OverlayTrigger;
|
||||||
|
|
||||||
@@ -155,12 +162,15 @@ export default class Sidebar extends React.Component {
|
|||||||
|
|
||||||
visibleDirectChannels.sort(this.sortChannelsByDisplayName);
|
visibleDirectChannels.sort(this.sortChannelsByDisplayName);
|
||||||
|
|
||||||
|
const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activeId: currentId,
|
activeId: currentId,
|
||||||
channels: ChannelStore.getAll(),
|
channels: ChannelStore.getAll(),
|
||||||
members,
|
members,
|
||||||
visibleDirectChannels,
|
visibleDirectChannels,
|
||||||
hiddenDirectChannelCount
|
hiddenDirectChannelCount,
|
||||||
|
showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.CHANNEL_POPOVER
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,6 +318,51 @@ export default class Sidebar extends React.Component {
|
|||||||
this.setState({showDirectChannelsModal: false});
|
this.setState({showDirectChannelsModal: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createTutorialTip() {
|
||||||
|
const screens = [];
|
||||||
|
|
||||||
|
screens.push(
|
||||||
|
<div>
|
||||||
|
<h4>{'Channels'}</h4>
|
||||||
|
<p><strong>{'Channels'}</strong>{' organize conversations across different topics. They’re open to everyone on your team. To send private communications use '}<strong>{'Direct Messages'}</strong>{' for a single person or '}<strong>{'Private Groups'}</strong>{' for multiple people.'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
screens.push(
|
||||||
|
<div>
|
||||||
|
<h4>{'"Town Square" and "Off-Topic" channels'}</h4>
|
||||||
|
<p>{'Here are two public channels to start:'}</p>
|
||||||
|
<p>
|
||||||
|
<strong>{'Town Square'}</strong>{' is a place for team-wide communication. Everyone in your team is a member of this channel.'}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>{'Off-Topic'}</strong>{' is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
screens.push(
|
||||||
|
<div>
|
||||||
|
<h4>{'Creating and Joining Channels'}</h4>
|
||||||
|
<p>
|
||||||
|
{'Click '}<strong>{'"More..."'}</strong>{' to create a new channel or join an existing one.'}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{'You can also create a new channel or private group by clicking the '}<strong>{'"+" symbol'}</strong>{' next to the channel or private group header.'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TutorialTip
|
||||||
|
placement='right'
|
||||||
|
screens={screens}
|
||||||
|
overlayClass='tip-overlay--sidebar'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
createChannelElement(channel, index, arr, handleClose) {
|
createChannelElement(channel, index, arr, handleClose) {
|
||||||
var members = this.state.members;
|
var members = this.state.members;
|
||||||
var activeId = this.state.activeId;
|
var activeId = this.state.activeId;
|
||||||
@@ -444,6 +499,11 @@ export default class Sidebar extends React.Component {
|
|||||||
rowClass += ' has-close';
|
rowClass += ' has-close';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tutorialTip = null;
|
||||||
|
if (this.state.showTutorialTip && channel.name === Constants.DEFAULT_CHANNEL) {
|
||||||
|
tutorialTip = this.createTutorialTip();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={channel.name}
|
key={channel.name}
|
||||||
@@ -460,6 +520,7 @@ export default class Sidebar extends React.Component {
|
|||||||
{badge}
|
{badge}
|
||||||
{closeButton}
|
{closeButton}
|
||||||
</a>
|
</a>
|
||||||
|
{tutorialTip}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var NavbarDropdown = require('./navbar_dropdown.jsx');
|
const NavbarDropdown = require('./navbar_dropdown.jsx');
|
||||||
var UserStore = require('../stores/user_store.jsx');
|
const TutorialTip = require('./tutorial/tutorial_tip.jsx');
|
||||||
|
|
||||||
|
const UserStore = require('../stores/user_store.jsx');
|
||||||
|
const PreferenceStore = require('../stores/preference_store.jsx');
|
||||||
|
|
||||||
const Utils = require('../utils/utils.jsx');
|
const Utils = require('../utils/utils.jsx');
|
||||||
|
const Constants = require('../utils/constants.jsx');
|
||||||
|
const Preferences = Constants.Preferences;
|
||||||
|
const TutorialSteps = Constants.TutorialSteps;
|
||||||
|
|
||||||
const Tooltip = ReactBootstrap.Tooltip;
|
const Tooltip = ReactBootstrap.Tooltip;
|
||||||
const OverlayTrigger = ReactBootstrap.OverlayTrigger;
|
const OverlayTrigger = ReactBootstrap.OverlayTrigger;
|
||||||
@@ -13,8 +20,23 @@ export default class SidebarHeader extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.toggleDropdown = this.toggleDropdown.bind(this);
|
this.toggleDropdown = this.toggleDropdown.bind(this);
|
||||||
|
this.onPreferenceChange = this.onPreferenceChange.bind(this);
|
||||||
|
|
||||||
this.state = {};
|
this.state = this.getStateFromStores();
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
PreferenceStore.addChangeListener(this.onPreferenceChange);
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
PreferenceStore.removeChangeListener(this.onPreferenceChange);
|
||||||
|
}
|
||||||
|
getStateFromStores() {
|
||||||
|
const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
|
||||||
|
|
||||||
|
return {showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.MENU_POPOVER};
|
||||||
|
}
|
||||||
|
onPreferenceChange() {
|
||||||
|
this.setState(this.getStateFromStores());
|
||||||
}
|
}
|
||||||
toggleDropdown(e) {
|
toggleDropdown(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -24,6 +46,38 @@ export default class SidebarHeader extends React.Component {
|
|||||||
}
|
}
|
||||||
$('.team__header').find('.dropdown-toggle').dropdown('toggle');
|
$('.team__header').find('.dropdown-toggle').dropdown('toggle');
|
||||||
}
|
}
|
||||||
|
createTutorialTip() {
|
||||||
|
const screens = [];
|
||||||
|
|
||||||
|
screens.push(
|
||||||
|
<div>
|
||||||
|
<h4>{'Main Menu'}</h4>
|
||||||
|
<p>
|
||||||
|
{'The '}<strong>{'Main Menu'}</strong>{' is where you can '}
|
||||||
|
<strong>{'Invite New Members'}</strong>
|
||||||
|
{', access your '}
|
||||||
|
<strong>{'Account Settings'}</strong>
|
||||||
|
{' and set your '}<strong>{'Theme Color'}</strong>{'.'}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{'Team administrators can also access their '}<strong>{'Team Settings'}</strong>{' from this menu.'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={this.toggleDropdown}
|
||||||
|
>
|
||||||
|
<TutorialTip
|
||||||
|
ref='tip'
|
||||||
|
placement='right'
|
||||||
|
screens={screens}
|
||||||
|
overlayClass='tip-overlay--header'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
render() {
|
render() {
|
||||||
var me = UserStore.getCurrentUser();
|
var me = UserStore.getCurrentUser();
|
||||||
var profilePicture = null;
|
var profilePicture = null;
|
||||||
@@ -41,8 +95,14 @@ export default class SidebarHeader extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tutorialTip = null;
|
||||||
|
if (this.state.showTutorialTip) {
|
||||||
|
tutorialTip = this.createTutorialTip();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='team__header theme'>
|
<div className='team__header theme'>
|
||||||
|
{tutorialTip}
|
||||||
<a
|
<a
|
||||||
href='#'
|
href='#'
|
||||||
onClick={this.toggleDropdown}
|
onClick={this.toggleDropdown}
|
||||||
|
|||||||
152
web/react/components/tutorial/tutorial_intro_screens.jsx
Обычный файл
152
web/react/components/tutorial/tutorial_intro_screens.jsx
Обычный файл
@@ -0,0 +1,152 @@
|
|||||||
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
const UserStore = require('../../stores/user_store.jsx');
|
||||||
|
const ChannelStore = require('../../stores/channel_store.jsx');
|
||||||
|
const TeamStore = require('../../stores/team_store.jsx');
|
||||||
|
const PreferenceStore = require('../../stores/preference_store.jsx');
|
||||||
|
const Utils = require('../../utils/utils.jsx');
|
||||||
|
const AsyncClient = require('../../utils/async_client.jsx');
|
||||||
|
|
||||||
|
const Constants = require('../../utils/constants.jsx');
|
||||||
|
const Preferences = Constants.Preferences;
|
||||||
|
|
||||||
|
export default class TutorialIntroScreens extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleNext = this.handleNext.bind(this);
|
||||||
|
this.createScreen = this.createScreen.bind(this);
|
||||||
|
|
||||||
|
this.state = {currentScreen: 0};
|
||||||
|
}
|
||||||
|
handleNext() {
|
||||||
|
if (this.state.currentScreen < 2) {
|
||||||
|
this.setState({currentScreen: this.state.currentScreen + 1});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils.switchChannel(ChannelStore.getByName(Constants.DEFAULT_CHANNEL));
|
||||||
|
|
||||||
|
let preference = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
|
||||||
|
|
||||||
|
const newValue = (parseInt(preference.value, 10) + 1).toString();
|
||||||
|
|
||||||
|
preference = PreferenceStore.setPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), newValue);
|
||||||
|
AsyncClient.savePreferences([preference]);
|
||||||
|
}
|
||||||
|
createScreen() {
|
||||||
|
switch (this.state.currentScreen) {
|
||||||
|
case 0:
|
||||||
|
return this.createScreenOne();
|
||||||
|
case 1:
|
||||||
|
return this.createScreenTwo();
|
||||||
|
case 2:
|
||||||
|
return this.createScreenThree();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createScreenOne() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>{'Welcome to:'}</h3>
|
||||||
|
<h1>{'Mattermost'}</h1>
|
||||||
|
<p>{'Your team communications all in one place, instantly searchable and available anywhere.'}</p>
|
||||||
|
<p>{'Keep your team connected to help them achieve what matters most.'}</p>
|
||||||
|
<div className='tutorial__circles'>
|
||||||
|
<div className='circle active'/>
|
||||||
|
<div className='circle'/>
|
||||||
|
<div className='circle'/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
createScreenTwo() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>{'How Mattermost works:'}</h3>
|
||||||
|
<p>{'Communication happens in public discussion channels, private groups and direct messages.'}</p>
|
||||||
|
<p>{'Everything is archived and searchable from any web-enabled laptop, tablet or phone.'}</p>
|
||||||
|
<div className='tutorial__circles'>
|
||||||
|
<div className='circle'/>
|
||||||
|
<div className='circle active'/>
|
||||||
|
<div className='circle'/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
createScreenThree() {
|
||||||
|
const team = TeamStore.getCurrent();
|
||||||
|
let inviteModalLink;
|
||||||
|
if (team.type === Constants.INVITE_TEAM) {
|
||||||
|
inviteModalLink = (
|
||||||
|
<a
|
||||||
|
className='intro-links'
|
||||||
|
href='#'
|
||||||
|
data-toggle='modal'
|
||||||
|
data-target='#invite_member'
|
||||||
|
>
|
||||||
|
{'Invite teammates'}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
inviteModalLink = (
|
||||||
|
<a
|
||||||
|
className='intro-links'
|
||||||
|
href='#'
|
||||||
|
data-toggle='modal'
|
||||||
|
data-target='#get_link'
|
||||||
|
data-title='Team Invite'
|
||||||
|
data-value={Utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + team.id}
|
||||||
|
>
|
||||||
|
{'Invite teammates'}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>{'You’re all set'}</h3>
|
||||||
|
<p>
|
||||||
|
{inviteModalLink}
|
||||||
|
{' when you’re ready.'}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{'Need anything, just email us at '}
|
||||||
|
<a
|
||||||
|
href='mailto:feedback@mattermost.com'
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
{'feedback@mattermost.com'}
|
||||||
|
</a>
|
||||||
|
{'.'}
|
||||||
|
</p>
|
||||||
|
{'Click “Next” to enter Town Square. This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.'}
|
||||||
|
<div className='tutorial__circles'>
|
||||||
|
<div className='circle'/>
|
||||||
|
<div className='circle'/>
|
||||||
|
<div className='circle active'/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const screen = this.createScreen();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='tutorial-steps__container'>
|
||||||
|
<div className='tutorial__content'>
|
||||||
|
<div className='tutorial__steps'>
|
||||||
|
{screen}
|
||||||
|
<button
|
||||||
|
className='btn btn-primary'
|
||||||
|
tabIndex='1'
|
||||||
|
onClick={this.handleNext}
|
||||||
|
>
|
||||||
|
{'Next'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
131
web/react/components/tutorial/tutorial_tip.jsx
Обычный файл
131
web/react/components/tutorial/tutorial_tip.jsx
Обычный файл
@@ -0,0 +1,131 @@
|
|||||||
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
const UserStore = require('../../stores/user_store.jsx');
|
||||||
|
const PreferenceStore = require('../../stores/preference_store.jsx');
|
||||||
|
const AsyncClient = require('../../utils/async_client.jsx');
|
||||||
|
|
||||||
|
const Constants = require('../../utils/constants.jsx');
|
||||||
|
const Preferences = Constants.Preferences;
|
||||||
|
|
||||||
|
const Overlay = ReactBootstrap.Overlay;
|
||||||
|
|
||||||
|
export default class TutorialTip extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleNext = this.handleNext.bind(this);
|
||||||
|
this.toggle = this.toggle.bind(this);
|
||||||
|
|
||||||
|
this.state = {currentScreen: 0, show: false};
|
||||||
|
}
|
||||||
|
toggle() {
|
||||||
|
const show = !this.state.show;
|
||||||
|
this.setState({show});
|
||||||
|
|
||||||
|
if (!show && this.state.currentScreen >= this.props.screens.length - 1) {
|
||||||
|
let preference = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
|
||||||
|
|
||||||
|
const newValue = (parseInt(preference.value, 10) + 1).toString();
|
||||||
|
|
||||||
|
preference = PreferenceStore.setPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), newValue);
|
||||||
|
AsyncClient.savePreferences([preference]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleNext() {
|
||||||
|
if (this.state.currentScreen < this.props.screens.length - 1) {
|
||||||
|
this.setState({currentScreen: this.state.currentScreen + 1});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
skipTutorial(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const preference = PreferenceStore.setPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), '999');
|
||||||
|
AsyncClient.savePreferences([preference]);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const buttonText = this.state.currentScreen === this.props.screens.length - 1 ? 'Okay' : 'Next';
|
||||||
|
|
||||||
|
const dots = [];
|
||||||
|
if (this.props.screens.length > 1) {
|
||||||
|
for (let i = 0; i < this.props.screens.length; i++) {
|
||||||
|
if (i === this.state.currentScreen) {
|
||||||
|
dots.push(
|
||||||
|
<div
|
||||||
|
className='circle active'
|
||||||
|
key={'dotactive' + i}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
dots.push(
|
||||||
|
<div
|
||||||
|
className='circle'
|
||||||
|
key={'dotinactive' + i}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'tip-div ' + this.props.overlayClass}>
|
||||||
|
<img
|
||||||
|
className='tip-button'
|
||||||
|
src='/static/images/tutorialTip.gif'
|
||||||
|
width='35'
|
||||||
|
onClick={this.toggle}
|
||||||
|
ref='target'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Overlay
|
||||||
|
show={this.state.show}
|
||||||
|
>
|
||||||
|
<div className='tip-backdrop'/>
|
||||||
|
</Overlay>
|
||||||
|
|
||||||
|
<Overlay
|
||||||
|
placement={this.props.placement}
|
||||||
|
show={this.state.show}
|
||||||
|
rootClose={true}
|
||||||
|
onHide={this.toggle}
|
||||||
|
target={() => this.refs.target}
|
||||||
|
>
|
||||||
|
<div className={'tip-overlay ' + this.props.overlayClass}>
|
||||||
|
<div className='arrow'></div>
|
||||||
|
{this.props.screens[this.state.currentScreen]}
|
||||||
|
<div className='tutorial__circles'>{dots}</div>
|
||||||
|
<div className='text-right'>
|
||||||
|
<button
|
||||||
|
className='btn btn-default'
|
||||||
|
onClick={this.handleNext}
|
||||||
|
>
|
||||||
|
{buttonText}
|
||||||
|
</button>
|
||||||
|
<div className='tip-opt'>
|
||||||
|
{'Seen this before? '}
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
onClick={this.skipTutorial}
|
||||||
|
>
|
||||||
|
{'Opt out of these tips.'}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Overlay>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TutorialTip.defaultProps = {
|
||||||
|
overlayClass: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
TutorialTip.propTypes = {
|
||||||
|
screens: React.PropTypes.array.isRequired,
|
||||||
|
placement: React.PropTypes.string.isRequired,
|
||||||
|
overlayClass: React.PropTypes.string
|
||||||
|
};
|
||||||
@@ -314,7 +314,14 @@ module.exports = {
|
|||||||
Preferences: {
|
Preferences: {
|
||||||
CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show',
|
CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show',
|
||||||
CATEGORY_DISPLAY_SETTINGS: 'display_settings',
|
CATEGORY_DISPLAY_SETTINGS: 'display_settings',
|
||||||
CATEGORY_ADVANCED_SETTINGS: 'advanced_settings'
|
CATEGORY_ADVANCED_SETTINGS: 'advanced_settings',
|
||||||
|
TUTORIAL_STEP: 'tutorial_step'
|
||||||
|
},
|
||||||
|
TutorialSteps: {
|
||||||
|
INTRO_SCREENS: 0,
|
||||||
|
POST_POPOVER: 1,
|
||||||
|
CHANNEL_POPOVER: 2,
|
||||||
|
MENU_POPOVER: 3
|
||||||
},
|
},
|
||||||
KeyCodes: {
|
KeyCodes: {
|
||||||
UP: 38,
|
UP: 38,
|
||||||
|
|||||||
@@ -179,6 +179,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1140px) {
|
@media screen and (max-width: 1140px) {
|
||||||
|
.tip-overlay {
|
||||||
|
&.tip-overlay--chat {
|
||||||
|
margin: -10px 0 0 -10px;
|
||||||
|
.arrow {
|
||||||
|
right: 15px;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.inner__wrap {
|
.inner__wrap {
|
||||||
&.move--left {
|
&.move--left {
|
||||||
.file-overlay {
|
.file-overlay {
|
||||||
@@ -266,6 +275,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
|
.tip-div {
|
||||||
|
left: 15px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
.tip-overlay {
|
||||||
|
&.tip-overlay--chat {
|
||||||
|
margin-left: 10px;
|
||||||
|
.arrow {
|
||||||
|
left: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.file-details__container {
|
.file-details__container {
|
||||||
display: block;
|
display: block;
|
||||||
.file-details__preview {
|
.file-details__preview {
|
||||||
@@ -482,7 +503,6 @@
|
|||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
display: table;
|
display: table;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
table-layout: fixed;
|
|
||||||
.post-body__cell {
|
.post-body__cell {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
padding-left: 45px;
|
padding-left: 45px;
|
||||||
|
|||||||
188
web/sass-files/sass/partials/_tutorial.scss
Обычный файл
188
web/sass-files/sass/partials/_tutorial.scss
Обычный файл
@@ -0,0 +1,188 @@
|
|||||||
|
.tip-backdrop {
|
||||||
|
background: rgba(black, 0.5);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-overlay {
|
||||||
|
width: 350px;
|
||||||
|
max-width: 90%;
|
||||||
|
position:absolute;
|
||||||
|
background-color: #fff;
|
||||||
|
@include border-radius(3px);
|
||||||
|
padding: 20px;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
border-width: 10px;
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-color: transparent;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tip-overlay--sidebar {
|
||||||
|
max-width: 75%;
|
||||||
|
margin: 50px 0 0 10px;
|
||||||
|
.arrow {
|
||||||
|
top: 80px;
|
||||||
|
left: -10px;
|
||||||
|
margin-top: -10px;
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tip-overlay--header {
|
||||||
|
margin: 10px 0 0 10px;
|
||||||
|
.arrow {
|
||||||
|
top: 15px;
|
||||||
|
left: -10px;
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tip-overlay--chat {
|
||||||
|
margin-top: -10px;
|
||||||
|
.arrow {
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -10px;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
border-top-color: #fff;
|
||||||
|
bottom: -10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: em(16px);
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 5px 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background: #cccccc;
|
||||||
|
color: #fff;
|
||||||
|
@include border-radius(3px);
|
||||||
|
border: none;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
&:hover, &:active, &:focus {
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
background: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-opt {
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
@include opacity(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tutorial__circles {
|
||||||
|
margin: 1.5em 0px -1.7em -4px;
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
margin: 0 4px;
|
||||||
|
&.active {
|
||||||
|
background: #000;
|
||||||
|
@include opacity(0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-button {
|
||||||
|
z-index: 998;
|
||||||
|
right: -10px;
|
||||||
|
top: -10px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-div {
|
||||||
|
position:absolute;
|
||||||
|
top:0px;
|
||||||
|
right:0px;
|
||||||
|
|
||||||
|
&.tip-overlay--header {
|
||||||
|
top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tip-overlay--sidebar {
|
||||||
|
left: 0;
|
||||||
|
top: -9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tutorial-steps__container {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
display: table;
|
||||||
|
.tutorial__content {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
padding-bottom: 100px;
|
||||||
|
padding: 0 40px;
|
||||||
|
.tutorial__steps {
|
||||||
|
max-width: 310px;
|
||||||
|
min-height: 420px;
|
||||||
|
text-align: left;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: em(40px);
|
||||||
|
margin: -20px 0 30px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: em(24px);
|
||||||
|
margin-bottom: 30px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tutorial__circles {
|
||||||
|
margin: 2em 0;
|
||||||
|
.circle {
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
@include border-radius(9px);
|
||||||
|
@include opacity(0.1);
|
||||||
|
background: #000;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 5px;
|
||||||
|
&.active {
|
||||||
|
background: $primary-color;
|
||||||
|
@include opacity(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
@import "partials/loading";
|
@import "partials/loading";
|
||||||
@import "partials/get-link";
|
@import "partials/get-link";
|
||||||
@import "partials/markdown";
|
@import "partials/markdown";
|
||||||
|
@import "partials/tutorial";
|
||||||
@import "partials/statistics";
|
@import "partials/statistics";
|
||||||
|
|
||||||
// Responsive Css
|
// Responsive Css
|
||||||
|
|||||||
Двоичные данные
web/static/images/tutorialTip.gif
Обычный файл
Двоичные данные
web/static/images/tutorialTip.gif
Обычный файл
Двоичный файл не отображается.
|
После Ширина: | Высота: | Размер: 18 KiB |
Ссылка в новой задаче
Block a user