mirror of
https://github.com/chinchang/web-maker.git
synced 2025-05-06 10:35:30 +02:00
Merge pull request #330 from chinchang/prettier
add prettier support for formatting files
This commit is contained in:
commit
affbd3541f
@ -48,7 +48,6 @@
|
|||||||
"merge-stream": "^1.0.1",
|
"merge-stream": "^1.0.1",
|
||||||
"preact-cli": "^2.1.0",
|
"preact-cli": "^2.1.0",
|
||||||
"preact-render-spy": "^1.2.1",
|
"preact-render-spy": "^1.2.1",
|
||||||
"prettier": "^1.10.2",
|
|
||||||
"run-sequence": "^2.2.1",
|
"run-sequence": "^2.2.1",
|
||||||
"sw-precache": "^5.2.0"
|
"sw-precache": "^5.2.0"
|
||||||
},
|
},
|
||||||
@ -66,7 +65,8 @@
|
|||||||
"preact-compat": "^3.17.0",
|
"preact-compat": "^3.17.0",
|
||||||
"preact-portal": "^1.1.3",
|
"preact-portal": "^1.1.3",
|
||||||
"preact-router": "^2.5.7",
|
"preact-router": "^2.5.7",
|
||||||
"split.js": "1.3.4"
|
"split.js": "1.3.4",
|
||||||
|
"prettier": "^1.10.2"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"verbose": true,
|
"verbose": true,
|
||||||
|
@ -3,11 +3,17 @@ import UserCodeMirror from './UserCodeMirror';
|
|||||||
import { modes, HtmlModes, CssModes, JsModes } from '../codeModes';
|
import { modes, HtmlModes, CssModes, JsModes } from '../codeModes';
|
||||||
import { log, loadJS } from '../utils';
|
import { log, loadJS } from '../utils';
|
||||||
|
|
||||||
import { linearizeFiles, assignFilePaths } from '../fileUtils';
|
import {
|
||||||
|
linearizeFiles,
|
||||||
|
assignFilePaths,
|
||||||
|
getFileFromPath,
|
||||||
|
getExtensionFromFileName
|
||||||
|
} from '../fileUtils';
|
||||||
|
|
||||||
import { SplitPane } from './SplitPane';
|
import { SplitPane } from './SplitPane';
|
||||||
import { trackEvent } from '../analytics';
|
import { trackEvent } from '../analytics';
|
||||||
import CodeMirror from '../CodeMirror';
|
import CodeMirror from '../CodeMirror';
|
||||||
|
import 'codemirror/mode/meta';
|
||||||
import { deferred } from '../deferred';
|
import { deferred } from '../deferred';
|
||||||
import { SidePane } from './SidePane';
|
import { SidePane } from './SidePane';
|
||||||
import { Console } from './Console';
|
import { Console } from './Console';
|
||||||
@ -51,6 +57,7 @@ export default class ContentWrapFiles extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
componentWillUpdate(nextProps) {
|
componentWillUpdate(nextProps) {
|
||||||
|
// If we get a new Item, clear file buffers and currently selected file.
|
||||||
if (
|
if (
|
||||||
this.props.currentItem.createdOn !== nextProps.currentItem.createdOn ||
|
this.props.currentItem.createdOn !== nextProps.currentItem.createdOn ||
|
||||||
this.props.currentItem.id !== nextProps.currentItem.id
|
this.props.currentItem.id !== nextProps.currentItem.id
|
||||||
@ -58,6 +65,21 @@ export default class ContentWrapFiles extends Component {
|
|||||||
this.fileBuffers = {};
|
this.fileBuffers = {};
|
||||||
this.state.selectedFile = null;
|
this.state.selectedFile = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the files have changed and we have a selected file (even after previous condition),
|
||||||
|
// update the buffer with new file content (may be it got prettified?)
|
||||||
|
if (
|
||||||
|
nextProps.currentItem.files !== this.props.currentItem.files &&
|
||||||
|
this.state.selectedFile &&
|
||||||
|
this.fileBuffers[this.state.selectedFile.path]
|
||||||
|
) {
|
||||||
|
this.fileBuffers[this.state.selectedFile.path].setValue(
|
||||||
|
getFileFromPath(
|
||||||
|
nextProps.currentItem.files,
|
||||||
|
this.state.selectedFile.path
|
||||||
|
).file.content
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const { currentItem } = this.props;
|
const { currentItem } = this.props;
|
||||||
@ -72,6 +94,7 @@ export default class ContentWrapFiles extends Component {
|
|||||||
) {
|
) {
|
||||||
this.fileSelectHandler(linearFiles[0]);
|
this.fileSelectHandler(linearFiles[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: becuase its a DOM manipulation
|
// HACK: becuase its a DOM manipulation
|
||||||
// window.logCountEl.textContent = this.logCount;
|
// window.logCountEl.textContent = this.logCount;
|
||||||
// log('🚀', 'didupdate', this.props.currentItem);
|
// log('🚀', 'didupdate', this.props.currentItem);
|
||||||
@ -108,26 +131,22 @@ export default class ContentWrapFiles extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createEditorDoc(file) {
|
createEditorDoc(file) {
|
||||||
let mode;
|
const detectedMode = CodeMirror.findModeByExtension(
|
||||||
if (file.name.match(/\.css$/)) {
|
getExtensionFromFileName(file.name)
|
||||||
mode = modes[CssModes.CSS];
|
);
|
||||||
} else if (file.name.match(/\.js$/)) {
|
let mode, mime;
|
||||||
mode = modes[JsModes.JS];
|
if (detectedMode) {
|
||||||
} else if (file.name.match(/\.html$/)) {
|
mode = detectedMode.mode;
|
||||||
mode = modes[HtmlModes.HTML];
|
mime = detectedMode.mimes ? detectedMode.mimes[0] : detectedMode.mime;
|
||||||
} else if (file.name.match(/\.md$/) || file.name.match(/\.markdown$/)) {
|
|
||||||
mode = modes[HtmlModes.MARKDOWN];
|
CodeMirror.autoLoadMode(this.cm, mode);
|
||||||
} else if (file.name.match(/\.sass$/)) {
|
|
||||||
mode = modes[CssModes.SASS];
|
|
||||||
} else if (file.name.match(/\.scss$/)) {
|
|
||||||
mode = modes[CssModes.SCSS];
|
|
||||||
}
|
}
|
||||||
|
if (mime === 'application/json') {
|
||||||
CodeMirror.autoLoadMode(this.cm, mode.cmPath || mode.cmMode);
|
mime = 'application/ld+json';
|
||||||
|
}
|
||||||
this.fileBuffers[file.name] = CodeMirror.Doc(
|
this.fileBuffers[file.path] = CodeMirror.Doc(
|
||||||
file.content || '',
|
file.content || '',
|
||||||
mode.cmMode
|
detectedMode ? mime : 'text/plain'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,7 +278,6 @@ export default class ContentWrapFiles extends Component {
|
|||||||
return !!item.title;
|
return !!item.title;
|
||||||
}
|
}
|
||||||
refreshEditor() {
|
refreshEditor() {
|
||||||
this.cmCodes.html = this.props.currentItem.html;
|
|
||||||
if (this.state.selectedFile) {
|
if (this.state.selectedFile) {
|
||||||
this.cm.setValue(this.state.selectedFile.content);
|
this.cm.setValue(this.state.selectedFile.content);
|
||||||
}
|
}
|
||||||
@ -458,10 +476,10 @@ export default class ContentWrapFiles extends Component {
|
|||||||
editorOptions: this.getEditorOptions(file.name),
|
editorOptions: this.getEditorOptions(file.name),
|
||||||
selectedFile: file
|
selectedFile: file
|
||||||
});
|
});
|
||||||
if (!this.fileBuffers[file.name]) {
|
if (!this.fileBuffers[file.path]) {
|
||||||
this.createEditorDoc(file);
|
this.createEditorDoc(file);
|
||||||
}
|
}
|
||||||
this.cm.swapDoc(this.fileBuffers[file.name]);
|
this.cm.swapDoc(this.fileBuffers[file.path]);
|
||||||
|
|
||||||
// var cmMode = 'html';
|
// var cmMode = 'html';
|
||||||
// if (file.name.match(/\.css$/)) {
|
// if (file.name.match(/\.css$/)) {
|
||||||
@ -561,6 +579,9 @@ export default class ContentWrapFiles extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prettifyBtnClickHandler() {
|
||||||
|
this.props.onPrettifyBtnClick(this.state.selectedFile);
|
||||||
|
}
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<SplitPane
|
<SplitPane
|
||||||
@ -600,6 +621,12 @@ export default class ContentWrapFiles extends Component {
|
|||||||
{this.state.selectedFile ? this.state.selectedFile.name : ''}
|
{this.state.selectedFile ? this.state.selectedFile.name : ''}
|
||||||
</label>
|
</label>
|
||||||
<div class="code-wrap__header-right-options">
|
<div class="code-wrap__header-right-options">
|
||||||
|
<button
|
||||||
|
class="btn btn--dark"
|
||||||
|
onClick={this.prettifyBtnClickHandler.bind(this)}
|
||||||
|
>
|
||||||
|
Prettify
|
||||||
|
</button>
|
||||||
<a
|
<a
|
||||||
class="js-code-collapse-btn code-wrap__header-btn code-wrap__collapse-btn"
|
class="js-code-collapse-btn code-wrap__header-btn code-wrap__collapse-btn"
|
||||||
title="Toggle code pane"
|
title="Toggle code pane"
|
||||||
@ -607,6 +634,9 @@ export default class ContentWrapFiles extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UserCodeMirror
|
<UserCodeMirror
|
||||||
|
value={
|
||||||
|
this.state.selectedFile ? this.state.selectedFile.content : ''
|
||||||
|
}
|
||||||
options={this.state.editorOptions}
|
options={this.state.editorOptions}
|
||||||
prefs={this.props.prefs}
|
prefs={this.props.prefs}
|
||||||
onChange={this.onHtmlCodeChange.bind(this)}
|
onChange={this.onHtmlCodeChange.bind(this)}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
|
import { getExtensionFromFileName } from '../fileUtils';
|
||||||
|
|
||||||
export function FileIcon({ file }) {
|
export function FileIcon({ file }) {
|
||||||
let path;
|
let path;
|
||||||
@ -24,7 +25,7 @@ export function FileIcon({ file }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const type = file.name.match(/.(\w+)$/)[1];
|
const type = getExtensionFromFileName(file.name);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'html':
|
case 'html':
|
||||||
path = (
|
path = (
|
||||||
|
@ -20,7 +20,8 @@ import {
|
|||||||
handleDownloadsPermission,
|
handleDownloadsPermission,
|
||||||
downloadFile,
|
downloadFile,
|
||||||
getCompleteHtml,
|
getCompleteHtml,
|
||||||
getFilenameFromUrl
|
getFilenameFromUrl,
|
||||||
|
prettify
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import {
|
import {
|
||||||
linearizeFiles,
|
linearizeFiles,
|
||||||
@ -29,6 +30,7 @@ import {
|
|||||||
removeFileAtPath,
|
removeFileAtPath,
|
||||||
doesFileExistInFolder
|
doesFileExistInFolder
|
||||||
} from '../fileUtils';
|
} from '../fileUtils';
|
||||||
|
|
||||||
import { itemService } from '../itemService';
|
import { itemService } from '../itemService';
|
||||||
import '../db';
|
import '../db';
|
||||||
import { Notifications } from './Notifications';
|
import { Notifications } from './Notifications';
|
||||||
@ -740,7 +742,7 @@ export default class App extends Component {
|
|||||||
onCodeChange(type, code, isUserChange) {
|
onCodeChange(type, code, isUserChange) {
|
||||||
if (this.state.currentItem.files) {
|
if (this.state.currentItem.files) {
|
||||||
linearizeFiles(this.state.currentItem.files).map(file => {
|
linearizeFiles(this.state.currentItem.files).map(file => {
|
||||||
if (file.name === type.name) {
|
if (file.path === type.path) {
|
||||||
file.content = code;
|
file.content = code;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1291,6 +1293,19 @@ export default class App extends Component {
|
|||||||
return classes.join(' ');
|
return classes.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prettify(selectedFile) {
|
||||||
|
const currentItem = {
|
||||||
|
...this.state.currentItem,
|
||||||
|
files: [...this.state.currentItem.files]
|
||||||
|
};
|
||||||
|
const formattedContent = prettify(selectedFile);
|
||||||
|
if (formattedContent !== selectedFile.content) {
|
||||||
|
selectedFile.content = formattedContent;
|
||||||
|
this.incrementUnsavedChanges();
|
||||||
|
this.setState({ currentItem });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div class={this.getRootClasses()}>
|
<div class={this.getRootClasses()}>
|
||||||
@ -1327,6 +1342,7 @@ export default class App extends Component {
|
|||||||
onRenameFile={this.renameFileHandler.bind(this)}
|
onRenameFile={this.renameFileHandler.bind(this)}
|
||||||
onFileDrop={this.fileDropHandler.bind(this)}
|
onFileDrop={this.fileDropHandler.bind(this)}
|
||||||
onFolderSelect={this.folderSelectHandler.bind(this)}
|
onFolderSelect={this.folderSelectHandler.bind(this)}
|
||||||
|
onPrettifyBtnClick={this.prettify.bind(this)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ContentWrap
|
<ContentWrap
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
import { deferred } from './deferred';
|
import { deferred } from './deferred';
|
||||||
const esprima = require('esprima');
|
const esprima = require('esprima');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the extension from the file name.
|
||||||
|
* @param {dtring} fileName File name
|
||||||
|
*/
|
||||||
|
export function getExtensionFromFileName(fileName) {
|
||||||
|
const type = fileName.match(/\.(\w+)$/);
|
||||||
|
return type ? type[1] : '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a linear file list from a nested file strcuture.
|
* Returns a linear file list from a nested file strcuture.
|
||||||
* It excludes the folders from the returned list.
|
* It excludes the folders from the returned list.
|
||||||
|
@ -4,7 +4,8 @@ import {
|
|||||||
assignFilePaths,
|
assignFilePaths,
|
||||||
getFileFromPath,
|
getFileFromPath,
|
||||||
removeFileAtPath,
|
removeFileAtPath,
|
||||||
getParentPath
|
getParentPath,
|
||||||
|
getExtensionFromFileName
|
||||||
} from '../fileUtils';
|
} from '../fileUtils';
|
||||||
|
|
||||||
function getNestedFiles() {
|
function getNestedFiles() {
|
||||||
@ -18,6 +19,17 @@ function getNestedFiles() {
|
|||||||
{ name: 'script.js' }
|
{ name: 'script.js' }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
describe('getExtensionFromFileName', () => {
|
||||||
|
test('should return correct extension', () => {
|
||||||
|
expect(getExtensionFromFileName('test.js')).toBe('js');
|
||||||
|
expect(getExtensionFromFileName('test.css')).toBe('css');
|
||||||
|
expect(getExtensionFromFileName('test.main.css')).toBe('css');
|
||||||
|
});
|
||||||
|
test('should return empty string when no extension is found', () => {
|
||||||
|
expect(getExtensionFromFileName('hello')).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('assignFilePaths', () => {
|
describe('assignFilePaths', () => {
|
||||||
test('should assign path on linear file system', () => {
|
test('should assign path on linear file system', () => {
|
||||||
const files = [{ name: 'index.html' }, { name: 'main.css' }];
|
const files = [{ name: 'index.html' }, { name: 'main.css' }];
|
||||||
|
39
src/utils.js
39
src/utils.js
@ -3,6 +3,7 @@ import { trackEvent } from './analytics';
|
|||||||
import { computeHtml, computeCss, computeJs } from './computes';
|
import { computeHtml, computeCss, computeJs } from './computes';
|
||||||
import { JsModes } from './codeModes';
|
import { JsModes } from './codeModes';
|
||||||
import { deferred } from './deferred';
|
import { deferred } from './deferred';
|
||||||
|
import { getExtensionFromFileName } from './fileUtils';
|
||||||
const esprima = require('esprima');
|
const esprima = require('esprima');
|
||||||
|
|
||||||
window.DEBUG = document.cookie.indexOf('wmdebug') > -1;
|
window.DEBUG = document.cookie.indexOf('wmdebug') > -1;
|
||||||
@ -465,3 +466,41 @@ if (window.IS_EXTENSION) {
|
|||||||
} else {
|
} else {
|
||||||
document.body.classList.add('is-app');
|
document.body.classList.add('is-app');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function prettify(file) {
|
||||||
|
const prettier = require('prettier/standalone');
|
||||||
|
const fileExtension = getExtensionFromFileName(file.name);
|
||||||
|
|
||||||
|
let plugins, parser;
|
||||||
|
switch (fileExtension) {
|
||||||
|
case 'js':
|
||||||
|
parser = 'babylon';
|
||||||
|
plugins = [require('prettier/parser-babylon')];
|
||||||
|
break;
|
||||||
|
case 'json':
|
||||||
|
parser = 'json';
|
||||||
|
plugins = [require('prettier/parser-babylon')];
|
||||||
|
break;
|
||||||
|
case 'css':
|
||||||
|
case 'scss':
|
||||||
|
case 'sass':
|
||||||
|
case 'less':
|
||||||
|
parser = 'css';
|
||||||
|
plugins = [require('prettier/parser-postcss')];
|
||||||
|
break;
|
||||||
|
case 'md':
|
||||||
|
case 'markdown':
|
||||||
|
parser = 'markdown';
|
||||||
|
plugins = [require('prettier/parser-markdown')];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parser) {
|
||||||
|
return file.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prettier.format(file.content, {
|
||||||
|
parser,
|
||||||
|
plugins
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user