Merge branch 'master' into share-links
This commit is contained in:
@ -4,7 +4,7 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<i>An open, extensible, wiki for your team built using React and Node.js.<br/>Try out Outline using our hosted version at <a href="https://www.getoutline.com">www.getoutline.com</a>.</i>
|
<i>An open, extensible, wiki for your team built using React and Node.js.<br/>Try out Outline using our hosted version at <a href="https://www.getoutline.com">www.getoutline.com</a>.</i>
|
||||||
<br/>
|
<br/>
|
||||||
<img src="https://user-images.githubusercontent.com/31465/34456332-51e41eb0-ed9c-11e7-9fa9-20e7fa946494.jpg" alt="Outline" width="800" height="500">
|
<img src="https://user-images.githubusercontent.com/31465/34456332-51e41eb0-ed9c-11e7-9fa9-20e7fa946494.jpg" alt="Outline" width="800" />
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://circleci.com/gh/outline/outline.svg?style=shield&circle-token=c0c4c2f39990e277385d5c1ae96169c409eb887a" alt="" data-canonical-src="" style="max-width:100%;">
|
<img src="https://circleci.com/gh/outline/outline.svg?style=shield&circle-token=c0c4c2f39990e277385d5c1ae96169c409eb887a" alt="" data-canonical-src="" style="max-width:100%;">
|
||||||
@ -63,12 +63,13 @@ Outline is composed of separate backend and frontend application which are both
|
|||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
Outline's frontend is a React application compiled with [Webpack](https://webpack.js.org/). It uses [Mobx](https://mobx.js.org/) for state management and [Styled Components](https://www.styled-components.com/) for component styles. Unless global, state logic and styles are always co-located with React components together with their subcomponents to make the component tree easier to manage. The editor is driven by [Slate](https://github.com/ianstormtaylor/slate) with several plugins.
|
Outline's frontend is a React application compiled with [Webpack](https://webpack.js.org/). It uses [Mobx](https://mobx.js.org/) for state management and [Styled Components](https://www.styled-components.com/) for component styles. Unless global, state logic and styles are always co-located with React components together with their subcomponents to make the component tree easier to manage.
|
||||||
|
|
||||||
|
The editor itself is built ontop of [Slate](https://github.com/ianstormtaylor/slate) and hosted in a separate repository to encourage reuse: [rich-markdown-editor](https://github.com/outline/rich-markdown-editor)
|
||||||
|
|
||||||
- `app/` - Frontend React application
|
- `app/` - Frontend React application
|
||||||
- `app/scenes` - Full page views
|
- `app/scenes` - Full page views
|
||||||
- `app/components` - Reusable React components
|
- `app/components` - Reusable React components
|
||||||
- `app/components/Editor` - Text editor and its plugins
|
|
||||||
- `app/stores` - Global state stores
|
- `app/stores` - Global state stores
|
||||||
- `app/models` - State models
|
- `app/models` - State models
|
||||||
- `app/types` - Flow types for non-models
|
- `app/types` - Flow types for non-models
|
||||||
|
@ -29,7 +29,7 @@ class SettingsSidebar extends React.Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
<HeaderBlock
|
<HeaderBlock
|
||||||
subheading="◄ Return to Dashboard"
|
subheading="◄ Return to App"
|
||||||
teamName={team.name}
|
teamName={team.name}
|
||||||
logoUrl={team.avatarUrl}
|
logoUrl={team.avatarUrl}
|
||||||
onClick={this.returnToDashboard}
|
onClick={this.returnToDashboard}
|
||||||
|
@ -35,9 +35,9 @@ function HeaderBlock({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const StyledExpandedIcon = styled(ExpandedIcon)`
|
const StyledExpandedIcon = styled(ExpandedIcon)`
|
||||||
position: relative;
|
position: absolute;
|
||||||
top: 6px;
|
right: 0;
|
||||||
left: -4px;
|
top: 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Subheading = styled.div`
|
const Subheading = styled.div`
|
||||||
@ -49,8 +49,9 @@ const Subheading = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const TeamName = styled.div`
|
const TeamName = styled.div`
|
||||||
|
position: relative;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
margin-top: ${props => (props.showDisclosure ? '-8px' : '0')};
|
padding-right: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: ${color.text};
|
color: ${color.text};
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
12
package.json
12
package.json
@ -155,21 +155,11 @@
|
|||||||
"react-waypoint": "^7.3.1",
|
"react-waypoint": "^7.3.1",
|
||||||
"redis": "^2.6.2",
|
"redis": "^2.6.2",
|
||||||
"redis-lock": "^0.1.0",
|
"redis-lock": "^0.1.0",
|
||||||
"rich-markdown-editor": "^1.0.0",
|
"rich-markdown-editor": "^1.1.0",
|
||||||
"safestart": "1.1.0",
|
"safestart": "1.1.0",
|
||||||
"sequelize": "4.28.6",
|
"sequelize": "4.28.6",
|
||||||
"sequelize-cli": "^2.7.0",
|
"sequelize-cli": "^2.7.0",
|
||||||
"sequelize-encrypted": "0.1.0",
|
"sequelize-encrypted": "0.1.0",
|
||||||
"slate": "^0.32.5",
|
|
||||||
"slate-collapse-on-escape": "^0.6.0",
|
|
||||||
"slate-edit-code": "^0.14.0",
|
|
||||||
"slate-edit-list": "^0.11.2",
|
|
||||||
"slate-md-serializer": "3.0.3",
|
|
||||||
"slate-paste-linkify": "^0.5.0",
|
|
||||||
"slate-plain-serializer": "0.5.4",
|
|
||||||
"slate-prism": "^0.5.0",
|
|
||||||
"slate-react": "^0.12.3",
|
|
||||||
"slate-trailing-block": "^0.5.0",
|
|
||||||
"slug": "0.9.1",
|
"slug": "0.9.1",
|
||||||
"string-hash": "^1.1.0",
|
"string-hash": "^1.1.0",
|
||||||
"string-replace-to-array": "^1.0.3",
|
"string-replace-to-array": "^1.0.3",
|
||||||
|
@ -8,6 +8,15 @@ Object {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`#documents.delete should require authentication 1`] = `
|
||||||
|
Object {
|
||||||
|
"error": "authentication_required",
|
||||||
|
"message": "Authentication required",
|
||||||
|
"ok": false,
|
||||||
|
"status": 401,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`#documents.list should require authentication 1`] = `
|
exports[`#documents.list should require authentication 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"error": "authentication_required",
|
"error": "authentication_required",
|
||||||
|
@ -428,7 +428,7 @@ router.post('documents.delete', auth(), async ctx => {
|
|||||||
authorize(ctx.state.user, 'delete', document);
|
authorize(ctx.state.user, 'delete', document);
|
||||||
|
|
||||||
const collection = document.collection;
|
const collection = document.collection;
|
||||||
if (collection.type === 'atlas') {
|
if (collection && collection.type === 'atlas') {
|
||||||
// Delete document and all of its children
|
// Delete document and all of its children
|
||||||
await collection.removeDocument(document);
|
await collection.removeDocument(document);
|
||||||
}
|
}
|
||||||
|
@ -618,3 +618,41 @@ describe('#documents.update', async () => {
|
|||||||
expect(res.status).toEqual(403);
|
expect(res.status).toEqual(403);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#documents.delete', async () => {
|
||||||
|
it('should allow deleting document', async () => {
|
||||||
|
const { user, document } = await seed();
|
||||||
|
const res = await server.post('/api/documents.delete', {
|
||||||
|
body: { token: user.getJwtToken(), id: document.id },
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200);
|
||||||
|
expect(body.success).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow deleting document without collection', async () => {
|
||||||
|
const { user, document, collection } = await seed();
|
||||||
|
|
||||||
|
// delete collection without hooks to trigger document deletion
|
||||||
|
await collection.destroy({ hooks: false });
|
||||||
|
const res = await server.post('/api/documents.delete', {
|
||||||
|
body: { token: user.getJwtToken(), id: document.id },
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200);
|
||||||
|
expect(body.success).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { document } = await seed();
|
||||||
|
const res = await server.post('/api/documents.delete', {
|
||||||
|
body: { id: document.id },
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(401);
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import naturalSort from 'natural-sort';
|
import naturalSort from 'natural-sort';
|
||||||
|
|
||||||
export default (sortableArray: Object[], key: string) => {
|
export default (sortableArray: Object[] = [], key: string) => {
|
||||||
let keys = sortableArray.map(object => object[key]);
|
let keys = sortableArray.map(object => object[key]);
|
||||||
keys.sort(naturalSort());
|
keys.sort(naturalSort());
|
||||||
return _.sortBy(sortableArray, object => keys.indexOf(object[key]));
|
return _.sortBy(sortableArray, object => keys.indexOf(object[key]));
|
||||||
|
36
yarn.lock
36
yarn.lock
@ -8762,9 +8762,9 @@ retry-as-promised@^2.3.1:
|
|||||||
bluebird "^3.4.6"
|
bluebird "^3.4.6"
|
||||||
debug "^2.6.9"
|
debug "^2.6.9"
|
||||||
|
|
||||||
rich-markdown-editor@^1.0.0:
|
rich-markdown-editor@^1.1.0:
|
||||||
version "1.0.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-1.0.0.tgz#ae540474e8e4e86b41d339d20cee0572c9794d2b"
|
resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-1.1.0.tgz#178307255bba4777c5b0f991202b522e5e0850a1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@tommoor/slate-drop-or-paste-images" "^0.8.1"
|
"@tommoor/slate-drop-or-paste-images" "^0.8.1"
|
||||||
boundless-arrow-key-navigation "^1.1.0"
|
boundless-arrow-key-navigation "^1.1.0"
|
||||||
@ -8778,11 +8778,11 @@ rich-markdown-editor@^1.0.0:
|
|||||||
react-keydown "^1.9.7"
|
react-keydown "^1.9.7"
|
||||||
react-medium-image-zoom "^3.0.10"
|
react-medium-image-zoom "^3.0.10"
|
||||||
react-portal "^4.1.4"
|
react-portal "^4.1.4"
|
||||||
slate "^0.32.5"
|
slate "^0.33.6"
|
||||||
slate-collapse-on-escape "^0.6.0"
|
slate-collapse-on-escape "^0.6.0"
|
||||||
slate-edit-code "^0.14.0"
|
slate-edit-code "^0.14.0"
|
||||||
slate-edit-list "^0.11.2"
|
slate-edit-list "^0.11.2"
|
||||||
slate-md-serializer "3.0.4"
|
slate-md-serializer "3.1.0"
|
||||||
slate-paste-linkify "^0.5.0"
|
slate-paste-linkify "^0.5.0"
|
||||||
slate-plain-serializer "0.5.4"
|
slate-plain-serializer "0.5.4"
|
||||||
slate-prism "^0.5.0"
|
slate-prism "^0.5.0"
|
||||||
@ -9143,13 +9143,9 @@ slate-edit-list@^0.11.2:
|
|||||||
version "0.11.2"
|
version "0.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/slate-edit-list/-/slate-edit-list-0.11.2.tgz#c67b961d98435f9f7747d20b870cbc51d25af7a8"
|
resolved "https://registry.yarnpkg.com/slate-edit-list/-/slate-edit-list-0.11.2.tgz#c67b961d98435f9f7747d20b870cbc51d25af7a8"
|
||||||
|
|
||||||
slate-md-serializer@3.0.3:
|
slate-md-serializer@3.1.0:
|
||||||
version "3.0.3"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/slate-md-serializer/-/slate-md-serializer-3.0.3.tgz#194aaf74b8c5158cdd45f8d2394a1a1574acaf83"
|
resolved "https://registry.yarnpkg.com/slate-md-serializer/-/slate-md-serializer-3.1.0.tgz#8e82899c45a607615b19e03d42cbd38cc7f64acf"
|
||||||
|
|
||||||
slate-md-serializer@3.0.4:
|
|
||||||
version "3.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/slate-md-serializer/-/slate-md-serializer-3.0.4.tgz#0ee04c0c2e44954b82f9fab1410b658bb6191aad"
|
|
||||||
|
|
||||||
slate-paste-linkify@^0.5.0:
|
slate-paste-linkify@^0.5.0:
|
||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
@ -9202,26 +9198,26 @@ slate-react@^0.12.3:
|
|||||||
slate-plain-serializer "^0.5.9"
|
slate-plain-serializer "^0.5.9"
|
||||||
slate-prop-types "^0.4.26"
|
slate-prop-types "^0.4.26"
|
||||||
|
|
||||||
slate-schema-violations@^0.1.3:
|
slate-schema-violations@^0.1.10:
|
||||||
version "0.1.7"
|
version "0.1.10"
|
||||||
resolved "https://registry.yarnpkg.com/slate-schema-violations/-/slate-schema-violations-0.1.7.tgz#cf2c6156eaf545f4d1985d3d1b94c50d6d273a08"
|
resolved "https://registry.yarnpkg.com/slate-schema-violations/-/slate-schema-violations-0.1.10.tgz#165227c230ea6c1027e523b7171a73e860e73646"
|
||||||
|
|
||||||
slate-trailing-block@^0.5.0:
|
slate-trailing-block@^0.5.0:
|
||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/slate-trailing-block/-/slate-trailing-block-0.5.0.tgz#cedb4f2975f1167e0fb9d259ce1252b82f4d74ff"
|
resolved "https://registry.yarnpkg.com/slate-trailing-block/-/slate-trailing-block-0.5.0.tgz#cedb4f2975f1167e0fb9d259ce1252b82f4d74ff"
|
||||||
|
|
||||||
slate@^0.32.5:
|
slate@^0.33.6:
|
||||||
version "0.32.5"
|
version "0.33.6"
|
||||||
resolved "https://registry.yarnpkg.com/slate/-/slate-0.32.5.tgz#5386c75dec1e5b87648189c9fbb4294cec623ff0"
|
resolved "https://registry.yarnpkg.com/slate/-/slate-0.33.6.tgz#0c7cb193cc5adeecec5c81e2ec0c86ab23dd6755"
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^2.3.2"
|
debug "^3.1.0"
|
||||||
direction "^0.1.5"
|
direction "^0.1.5"
|
||||||
esrever "^0.2.0"
|
esrever "^0.2.0"
|
||||||
is-empty "^1.0.0"
|
is-empty "^1.0.0"
|
||||||
is-plain-object "^2.0.4"
|
is-plain-object "^2.0.4"
|
||||||
lodash "^4.17.4"
|
lodash "^4.17.4"
|
||||||
slate-dev-logger "^0.1.39"
|
slate-dev-logger "^0.1.39"
|
||||||
slate-schema-violations "^0.1.3"
|
slate-schema-violations "^0.1.10"
|
||||||
type-of "^2.0.1"
|
type-of "^2.0.1"
|
||||||
|
|
||||||
slice-ansi@0.0.4:
|
slice-ansi@0.0.4:
|
||||||
|
Reference in New Issue
Block a user