1
0
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:
Kushagra Gour 2018-10-15 16:59:00 +05:30 committed by GitHub
commit affbd3541f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 28 deletions

View File

@ -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,

View File

@ -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)}

View File

@ -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 = (

View File

@ -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

View File

@ -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.

View File

@ -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' }];

View File

@ -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
});
}