1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-08 10:22:46 +00:00
This commit is contained in:
Michael An
2019-03-14 18:14:11 +08:00
committed by Daniel Pan
parent ff453cb3fb
commit 2050d9f1b9
7 changed files with 181 additions and 183 deletions

View File

@@ -154,7 +154,6 @@ const CommentItempropTypes = {
editorUtilities: PropTypes.object.isRequired, editorUtilities: PropTypes.object.isRequired,
item: PropTypes.object, item: PropTypes.object,
time: PropTypes.string, time: PropTypes.string,
key: PropTypes.number,
editComment: PropTypes.func, editComment: PropTypes.func,
showResolvedComment: PropTypes.bool, showResolvedComment: PropTypes.bool,
deleteComment: PropTypes.func, deleteComment: PropTypes.func,
@@ -301,6 +300,6 @@ class CommentItem extends React.Component {
} }
} }
CommentsList.propTypes = CommentItempropTypes; CommentItem.propTypes = CommentItempropTypes;
export default CommentsList; export default CommentsList;

View File

@@ -20,7 +20,7 @@ class HistoryList extends React.Component {
this.perPage = 25; this.perPage = 25;
this.state = { this.state = {
historyList: [], historyList: [],
activeItem: -1, activeItem: 0,
currentPage: 1, currentPage: 1,
totalReversionCount: 0, totalReversionCount: 0,
loading: false loading: false
@@ -114,7 +114,7 @@ class HistoryList extends React.Component {
index={index} index={index}
key={index} key={index}
preItem={arr[preItemIndex]} preItem={arr[preItemIndex]}
/> />
); );
}) : <Loading/> }) : <Loading/>
} }
@@ -132,12 +132,13 @@ HistoryList.propTypes = propTypes;
const HistoryItempropTypes = { const HistoryItempropTypes = {
ctime: PropTypes.number, ctime: PropTypes.string,
onClick: PropTypes.func, onClick: PropTypes.func,
index: PropTypes.number, index: PropTypes.number,
preItem: PropTypes.string, preItem: PropTypes.object,
currewntItem: PropTypes.string, currewntItem: PropTypes.object,
name: PropTypes.string, name: PropTypes.string,
className: PropTypes.string,
}; };
class HistoryItem extends React.Component { class HistoryItem extends React.Component {
@@ -152,7 +153,7 @@ class HistoryItem extends React.Component {
} }
} }
HistoryList.propTypes = HistoryItempropTypes; HistoryItem.propTypes = HistoryItempropTypes;
export default HistoryList; export default HistoryList;

View File

@@ -2,11 +2,11 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants'; import { gettext } from '../../utils/constants';
const propTypes = {
const OutlineItempropTypes = {
scrollToNode: PropTypes.func.isRequired, scrollToNode: PropTypes.func.isRequired,
isViewer: PropTypes.bool.isRequired, node: PropTypes.object.isRequired,
document: PropTypes.object.isRequired, active: PropTypes.string.isRequired,
editor: PropTypes.object.isRequired,
}; };
class OutlineItem extends React.PureComponent { class OutlineItem extends React.PureComponent {
@@ -35,6 +35,17 @@ class OutlineItem extends React.PureComponent {
} }
} }
OutlineItem.propTypes = OutlineItempropTypes;
const propTypes = {
scrollToNode: PropTypes.func.isRequired,
isViewer: PropTypes.bool.isRequired,
document: PropTypes.object.isRequired,
editor: PropTypes.object.isRequired,
activeTitleIndex: PropTypes.number,
value: PropTypes.object,
};
class OutlineView extends React.PureComponent { class OutlineView extends React.PureComponent {
render() { render() {
@@ -46,19 +57,19 @@ class OutlineView extends React.PureComponent {
return ( return (
<div className="seafile-editor-outline"> <div className="seafile-editor-outline">
{headerList.size > 0 ? {headerList.size > 0 ?
headerList.map((node, index) => { headerList.map((node, index) => {
let active = (index === this.props.activeTitleIndex) ? ' active' : ''; let active = (index === this.props.activeTitleIndex) ? ' active' : '';
return ( return (
<OutlineItem <OutlineItem
key={node.key} key={node.key}
editor={this.props.editor} editor={this.props.editor}
value={this.props.value} value={this.props.value}
node={node} node={node}
active={active} active={active}
scrollToNode={this.props.scrollToNode} scrollToNode={this.props.scrollToNode}
/> />
); );
}) : <div className={'size-panel-no-content'}>{gettext('No out line.')}</div>} }) : <div className={'size-panel-no-content'}>{gettext('No outline')}</div>}
</div> </div>
); );
} }

View File

@@ -1,5 +1,4 @@
.seafile-comment { .seafile-comment {
border-left: 1px solid #e6e6dd;
background-color: #fff; background-color: #fff;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -12,11 +11,14 @@
border-bottom: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5;
min-height: 2em; min-height: 2em;
line-height: 2em; line-height: 2em;
padding: 0 1em; padding: 2px 1em;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
background-color: #fafaf9; background-color: #fafaf9;
position: absolute;
z-index: 1;
width: 30%;
} }
.seafile-comment-title .seafile-comment-title-close { .seafile-comment-title .seafile-comment-title-close {
color: #b9b9b9; color: #b9b9b9;
@@ -27,7 +29,10 @@
.seafile-comment-list { .seafile-comment-list {
height: calc(100% - 40px); height: calc(100% - 40px);
overflow-y: auto; overflow-y: auto;
margin-bottom: 120px; margin: 30px 0 120px;
}
.seafile-comment::-webkit-scrollbar {
display: none;
} }
.seafile-comment-list .comment-vacant { .seafile-comment-list .comment-vacant {
padding: 1em; padding: 1em;
@@ -95,7 +100,7 @@
position: absolute; position: absolute;
bottom: 0; bottom: 0;
padding: 0; padding: 0;
width: inherit; width: 30%;
} }
.seafile-comment-footer .seafile-add-comment { .seafile-comment-footer .seafile-add-comment {
margin: 10px 20px 5px 15px; margin: 10px 20px 5px 15px;
@@ -113,7 +118,8 @@
} }
.seafile-add-comment .add-comment-input { .seafile-add-comment .add-comment-input {
height: calc(100% - 50px); height: calc(100% - 50px);
width: calc(100% - 40px); width: 92%;
max-height: 300px;
} }
.seafile-comment-footer .seafile-add-comment .submit-comment { .seafile-comment-footer .seafile-add-comment .submit-comment {
margin-top: 5px; margin-top: 5px;

View File

@@ -1,6 +1,5 @@
.seafile-history-side-panel { .seafile-history-side-panel {
user-select: none; user-select: none;
border-left: 1px solid #e5e5e5;
background-color: #fff; background-color: #fff;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -1,6 +1,8 @@
.seafile-md-viewer { .seafile-md-viewer {
height: 100%; height: 100%;
flex-direction: row; flex-direction: row;
position: relative;
float: none;
} }
.sf-md-viewer-topbar-first { .sf-md-viewer-topbar-first {
padding: 4px 10px; padding: 4px 10px;
@@ -13,7 +15,16 @@
.seafile-md-viewer-container { .seafile-md-viewer-container {
width: 70%; width: 70%;
background-color: #fafaf9; background-color: #fafaf9;
overflow-y: auto; overflow: hidden;
height: 100%;
}
.seafile-md-viewer-container:hover {
overflow: auto;
}
.seafile-md-viewer-slate {
flex: auto;
position: relative;
margin: 20px 40px;
} }
.seafile-md-viewer-main { .seafile-md-viewer-main {
flex:auto; flex:auto;
@@ -21,41 +32,11 @@
background:#fafaf9; background:#fafaf9;
width: 70%; width: 70%;
} }
.seafile-editor-outline .active {
.seafile-md-viewer-outline-heading2,
.seafile-md-viewer-outline-heading3 {
margin-left: .75rem;
line-height: 2.5;
color:#666;
white-space: nowrap;
overflow:hidden;
text-overflow:ellipsis;
cursor:pointer;
}
.seafile-md-viewer-outline-heading3 {
margin-left: 2rem;
}
.seafile-md-viewer-outline-heading2:hover,
.seafile-md-viewer-outline-heading3:hover {
color: #eb8205;
}
.seafile-markdown-outline {
position: fixed;
padding-right: 1rem;
top: 97px;
right: 0;
width: 200px;
overflow: scroll;
height: 80%;
}
.seafile-editor-outline {
border-left: 1px solid #ddd;
}
.seafile-markdown-outline .active {
color: #eb8205; color: #eb8205;
border-left: 1px solid #eb8205; border-left: 1px solid #eb8205;
} }
.seafile-markdown-outline .outline-h2, .seafile-markdown-outline .outline-h3 { .seafile-editor-outline .outline-h2, .seafile-editor-outline .outline-h3 {
height: 30px; height: 30px;
margin-left: 0; margin-left: 0;
white-space: nowrap; white-space: nowrap;
@@ -63,24 +44,24 @@
text-overflow: ellipsis; text-overflow: ellipsis;
font-size: 14px; font-size: 14px;
} }
.seafile-markdown-outline .outline-h2 { .seafile-editor-outline .outline-h2 {
padding-left: 20px; padding-left: 20px;
} }
.seafile-markdown-outline .outline-h3 { .seafile-editor-outline .outline-h3 {
padding-left: 40px; padding-left: 40px;
} }
/* side-panel */ /* side-panel */
.seafile-md-viewer-side-panel { .seafile-md-viewer-side-panel {
height: 100%; height: 100%;
overflow:hidden; overflow:hidden;
user-select: none; user-select: none;
width: 30%;
} }
.seafile-md-viewer-side-panel .seafile-editor-outline { .seafile-md-viewer-side-panel .seafile-editor-outline {
border-left: 0; border-left: 0;
} }
.seafile-md-viewer-side-panel:hover { .seafile-md-viewer-side-panel:hover {
overflow:auto; overflow: auto;
} }
.seafile-md-viewer-side-panel-heading { .seafile-md-viewer-side-panel-heading {
padding:7px 0; padding:7px 0;
@@ -100,7 +81,11 @@
} }
.seafile-md-viewer-side-panel .tab-content { .seafile-md-viewer-side-panel .tab-content {
height: calc(100% - 39px); height: calc(100% - 39px);
overflow-y: scroll; overflow-y: auto;
overflow-x: hidden;
}
.seafile-md-viewer-side-panel .tab-content .outline {
padding: 20px;
} }
.seafile-md-viewer-side-panel .md-side-panel-nav { .seafile-md-viewer-side-panel .md-side-panel-nav {
margin: 0; margin: 0;
@@ -146,16 +131,12 @@
cursor: pointer; cursor: pointer;
background-color: #eee; background-color: #eee;
} }
.seafile-md-viewer-slate {
flex: auto;
position: relative;
margin: 20px 40px;
}
@media (max-width:991.8px) { @media (max-width:991.8px) {
.seafile-md-viewer-side-panel { .seafile-md-viewer-side-panel {
display:none; display:none;
} }
.seafile-markdown-outline { .seafile-editor-outline {
display: none; display: none;
} }
.seafile-md-viewer-slate { .seafile-md-viewer-slate {

View File

@@ -5,6 +5,8 @@ import { Value, Document, Block } from 'slate';
import { seafileAPI } from './utils/seafile-api'; import { seafileAPI } from './utils/seafile-api';
import { Utils } from './utils/utils'; import { Utils } from './utils/utils';
import { gettext } from './utils/constants'; import { gettext } from './utils/constants';
import io from 'socket.io-client';
import toaster from './components/toast';
import ModalPortal from './components/modal-portal'; import ModalPortal from './components/modal-portal';
import EditFileTagDialog from './components/dialog/edit-filetag-dialog'; import EditFileTagDialog from './components/dialog/edit-filetag-dialog';
import ListRelatedFileDialog from './components/dialog/list-related-file-dialog'; import ListRelatedFileDialog from './components/dialog/list-related-file-dialog';
@@ -12,20 +14,18 @@ import AddRelatedFileDialog from './components/dialog/add-related-file-dialog';
import ShareDialog from './components/dialog/share-dialog'; import ShareDialog from './components/dialog/share-dialog';
import CommentDialog from './components/markdown-view/comment-dialog'; import CommentDialog from './components/markdown-view/comment-dialog';
import MarkdownViewerSlate from '@seafile/seafile-editor/dist/viewer/markdown-viewer-slate'; import MarkdownViewerSlate from '@seafile/seafile-editor/dist/viewer/markdown-viewer-slate';
import io from "socket.io-client"; import { serialize, deserialize } from '@seafile/seafile-editor/dist/utils/slate2markdown';
import toaster from "./components/toast"; import LocalDraftDialog from '@seafile/seafile-editor/dist/components/local-draft-dialog';
import { serialize, deserialize } from "@seafile/seafile-editor/dist/utils/slate2markdown";
import LocalDraftDialog from "@seafile/seafile-editor/dist/components/local-draft-dialog";
import DiffViewer from '@seafile/seafile-editor/dist/viewer/diff-viewer'; import DiffViewer from '@seafile/seafile-editor/dist/viewer/diff-viewer';
import MarkdownViewerToolbar from './components/toolbar/markdown-viewer-toolbar'; import MarkdownViewerToolbar from './components/toolbar/markdown-viewer-toolbar';
import MarkdownViewerSidePanel from './components/markdown-view/markdown-viewer-side-panel'; import MarkdownViewerSidePanel from './components/markdown-view/markdown-viewer-side-panel';
import Loading from './components/loading'; import Loading from './components/loading';
import { Editor, findRange } from '@seafile/slate-react'; import { findRange } from '@seafile/slate-react';
import './css/markdown-viewer/markdown-editor.css'; import './css/markdown-viewer/markdown-editor.css';
const CryptoJS = require('crypto-js'); const CryptoJS = require('crypto-js');
const { repoID, repoName, filePath, fileName, mode, draftID, draftFilePath, draftOriginFilePath, isDraft, hasDraft, shareLinkExpireDaysMin, shareLinkExpireDaysMax } = window.app.pageOptions; const { repoID, repoName, filePath, fileName, mode, draftID, isDraft, hasDraft } = window.app.pageOptions;
const { siteRoot, serviceUrl, seafileCollabServer } = window.app.config; const { siteRoot, serviceUrl, seafileCollabServer } = window.app.config;
const userInfo = window.app.userInfo; const userInfo = window.app.userInfo;
const userName = userInfo.username; const userName = userInfo.username;
@@ -321,9 +321,9 @@ class MarkdownEditor extends React.Component {
const { repoID, path } = this.state.fileInfo; const { repoID, path } = this.state.fileInfo;
this.socket.emit('presence', { this.socket.emit('presence', {
request: 'editing', request: 'editing',
doc_id: CryptoJS.MD5(repoID+path).toString(), doc_id: CryptoJS.MD5(repoID+path).toString(),
user: userInfo, user: userInfo,
is_editing, is_editing,
}); });
} }
} }
@@ -395,12 +395,12 @@ class MarkdownEditor extends React.Component {
setEditorMode = (type) => { setEditorMode = (type) => {
this.setState({ this.setState({
editorMode: type editorMode: type
}) });
} }
setDraftValue = (type, value) => { setDraftValue = (type, value) => {
if (type === 'rich') { if (type === 'rich') {
this.draftRichValue = value this.draftRichValue = value;
} else { } else {
this.draftPlainValue = value; this.draftPlainValue = value;
} }
@@ -511,12 +511,12 @@ class MarkdownEditor extends React.Component {
componentWillUnmount() { componentWillUnmount() {
this.socket.emit('repo_update', { this.socket.emit('repo_update', {
request: 'unwatch_update', request: 'unwatch_update',
repo_id: this.props.editorUtilities.repoID, repo_id: this.props.c.repoID,
user: { user: {
name: this.props.editorUtilities.name, name: editorUtilities.name,
username: this.props.editorUtilities.username, username: editorUtilities.username,
contact_email: this.props.editorUtilities.contact_email, contact_email: editorUtilities.contact_email,
}, },
}); });
document.removeEventListener('selectionchange', this.setBtnPosition); document.removeEventListener('selectionchange', this.setBtnPosition);
} }
@@ -565,18 +565,18 @@ class MarkdownEditor extends React.Component {
const { repoID, path } = this.state.fileInfo; const { repoID, path } = this.state.fileInfo;
this.socket.emit('presence', { this.socket.emit('presence', {
request: 'join_room', request: 'join_room',
doc_id: CryptoJS.MD5(repoID+path).toString(), doc_id: CryptoJS.MD5(repoID+path).toString(),
user: userInfo user: userInfo
}); });
this.socket.emit('repo_update', { this.socket.emit('repo_update', {
request: 'watch_update', request: 'watch_update',
repo_id: editorUtilities.repoID, repo_id: editorUtilities.repoID,
user: { user: {
name: editorUtilities.name, name: editorUtilities.name,
username: editorUtilities.username, username: editorUtilities.username,
contact_email: editorUtilities.contact_email, contact_email: editorUtilities.contact_email,
}, },
}); });
} }
this.checkDraft(); this.checkDraft();
@@ -648,7 +648,8 @@ class MarkdownEditor extends React.Component {
let that = this; let that = this;
if (that.timer) { if (that.timer) {
return; return;
} else { }
else {
that.timer = setTimeout(() => { that.timer = setTimeout(() => {
let str = ''; let str = '';
if (this.state.editorMode == 'rich') { if (this.state.editorMode == 'rich') {
@@ -661,16 +662,16 @@ class MarkdownEditor extends React.Component {
let draftKey = editorUtilities.getDraftKey(); let draftKey = editorUtilities.getDraftKey();
localStorage.setItem(draftKey, str); localStorage.setItem(draftKey, str);
that.setState({ that.setState({
showDraftSaved: true showDraftSaved: true
}); });
setTimeout(() => { setTimeout(() => {
that.setState({ that.setState({
showDraftSaved: false showDraftSaved: false
}); });
}, 3000); }, 3000);
that.timer = null; that.timer = null;
}, 60000); }, 60000);
} }
} }
backToParentDirectory = () => { backToParentDirectory = () => {
@@ -864,39 +865,39 @@ class MarkdownEditor extends React.Component {
</div> </div>
); );
} else if (this.state.mode === 'editor') { } else if (this.state.mode === 'editor') {
if (this.state.editorMode === 'viewer') { if (this.state.editorMode === 'viewer') {
component = ( component = (
<div className="seafile-md-viewer d-flex flex-column"> <div className="seafile-md-viewer d-flex flex-column">
<MarkdownViewerToolbar <MarkdownViewerToolbar
hasDraft={hasDraft} hasDraft={hasDraft}
isDraft={isDraft} isDraft={isDraft}
editorUtilities={editorUtilities} editorUtilities={editorUtilities}
collabUsers={this.state.collabUsers} collabUsers={this.state.collabUsers}
fileInfo={this.state.fileInfo} fileInfo={this.state.fileInfo}
toggleStar={this.toggleStar} toggleStar={this.toggleStar}
backToParentDirectory={this.backToParentDirectory} backToParentDirectory={this.backToParentDirectory}
openDialogs={this.openDialogs} openDialogs={this.openDialogs}
fileTagList={this.state.fileTagList} fileTagList={this.state.fileTagList}
relatedFiles={this.state.relatedFiles} relatedFiles={this.state.relatedFiles}
toggleShareLinkDialog={this.toggleShareLinkDialog} toggleShareLinkDialog={this.toggleShareLinkDialog}
onEdit={this.onEdit} onEdit={this.onEdit}
toggleNewDraft={editorUtilities.createDraftFile} toggleNewDraft={editorUtilities.createDraftFile}
/> />
<div className="seafile-md-viewer d-flex"> <div className="seafile-md-viewer d-flex">
<div className="seafile-md-viewer-container d-flex" ref="markdownContainer"> <div className="seafile-md-viewer-container" ref="markdownContainer">
{ {
this.state.activeTab === "history" ? this.state.activeTab === 'history' ?
<div className="diff-container"> <div className="diff-container">
<div className="diff-wrapper article"> <div className="diff-wrapper article">
{ this.state.loadingDiff ? { this.state.loadingDiff ?
<Loading/> : <Loading/> :
<DiffViewer <DiffViewer
newMarkdownContent={this.state.markdownContent} newMarkdownContent={this.state.markdownContent}
oldMarkdownContent={this.state.oldMarkdownContent} oldMarkdownContent={this.state.oldMarkdownContent}
/> />
} }
</div>
</div> </div>
</div>
: :
<div className='seafile-md-viewer-slate'> <div className='seafile-md-viewer-slate'>
<MarkdownViewerSlate <MarkdownViewerSlate
@@ -907,52 +908,52 @@ class MarkdownEditor extends React.Component {
{isShowComments && {isShowComments &&
<i className="fa fa-plus-square seafile-viewer-comment-btn" ref="commentbtn" onMouseDown={this.addComment}></i>} <i className="fa fa-plus-square seafile-viewer-comment-btn" ref="commentbtn" onMouseDown={this.addComment}></i>}
</div> </div>
} }
</div>
<MarkdownViewerSidePanel
viewer={this}
value={this.state.value}
markdownContent={this.state.markdownContent}
editorUtilities={editorUtilities}
commentsNumber={this.state.commentsNumber}
getCommentsNumber={this.getCommentsNumber}
showDiffViewer={this.showDiffViewer}
setDiffViewerContent={this.setDiffViewerContent}
reloadDiffContent={this.reloadDiffContent}
activeTab={this.state.activeTab}
tabItemClick={this.tabItemClick}
/>
</div> </div>
<MarkdownViewerSidePanel
viewer={this}
value={this.state.value}
markdownContent={this.state.markdownContent}
editorUtilities={editorUtilities}
commentsNumber={this.state.commentsNumber}
getCommentsNumber={this.getCommentsNumber}
showDiffViewer={this.showDiffViewer}
setDiffViewerContent={this.setDiffViewerContent}
reloadDiffContent={this.reloadDiffContent}
activeTab={this.state.activeTab}
tabItemClick={this.tabItemClick}
/>
</div> </div>
) </div>
} else { );
component = <SeafileEditor } else {
fileInfo={this.state.fileInfo} component = <SeafileEditor
markdownContent={this.state.markdownContent} fileInfo={this.state.fileInfo}
editorUtilities={editorUtilities} markdownContent={this.state.markdownContent}
collabUsers={this.state.collabUsers} editorUtilities={editorUtilities}
setFileInfoMtime={this.setFileInfoMtime} collabUsers={this.state.collabUsers}
toggleStar={this.toggleStar} setFileInfoMtime={this.setFileInfoMtime}
showFileHistory={true} toggleStar={this.toggleStar}
setEditorMode={this.setEditorMode} showFileHistory={true}
setContent={this.setContent} setEditorMode={this.setEditorMode}
draftID={draftID} setContent={this.setContent}
isDraft={isDraft} draftID={draftID}
mode={this.state.mode} isDraft={isDraft}
emitSwitchEditor={this.emitSwitchEditor} mode={this.state.mode}
hasDraft={hasDraft} emitSwitchEditor={this.emitSwitchEditor}
editorMode={this.state.editorMode} hasDraft={hasDraft}
relatedFiles={this.state.relatedFiles} editorMode={this.state.editorMode}
siteRoot={siteRoot} relatedFiles={this.state.relatedFiles}
autoSaveDraft={this.autoSaveDraft} siteRoot={siteRoot}
setDraftValue={this.setDraftValue} autoSaveDraft={this.autoSaveDraft}
clearTimer={this.clearTimer} setDraftValue={this.setDraftValue}
openDialogs={this.openDialogs} clearTimer={this.clearTimer}
fileTagList={this.state.fileTagList} openDialogs={this.openDialogs}
deleteDraft={this.deleteDraft} fileTagList={this.state.fileTagList}
showDraftSaved={this.state.showDraftSaved} deleteDraft={this.deleteDraft}
/> showDraftSaved={this.state.showDraftSaved}
} />;
}
return ( return (
<React.Fragment> <React.Fragment>
@@ -961,7 +962,7 @@ class MarkdownEditor extends React.Component {
localDraftDialog={this.state.localDraftDialog} localDraftDialog={this.state.localDraftDialog}
deleteDraft={this.deleteDraft} deleteDraft={this.deleteDraft}
useDraft={this.useDraft}/>: useDraft={this.useDraft}/>:
null} null}
{component} {component}
{this.state.showMarkdownEditorDialog && ( {this.state.showMarkdownEditorDialog && (
<React.Fragment> <React.Fragment>