Merge pull request #429 from hackmdio/refactor-part-3

Refactor frontend part 3
This commit is contained in:
Max Wu 2017-05-13 19:54:43 +08:00 committed by GitHub
commit f85d1d8801
9 changed files with 224 additions and 221 deletions

View file

@ -162,8 +162,9 @@
"less-loader": "^2.2.3", "less-loader": "^2.2.3",
"optimize-css-assets-webpack-plugin": "^1.3.0", "optimize-css-assets-webpack-plugin": "^1.3.0",
"script-loader": "^0.7.0", "script-loader": "^0.7.0",
"style-loader": "^0.13.1",
"standard": "^9.0.1", "standard": "^9.0.1",
"string-loader": "^0.0.1",
"style-loader": "^0.13.1",
"url-loader": "^0.5.7", "url-loader": "^0.5.7",
"webpack": "^1.14.0", "webpack": "^1.14.0",
"webpack-parallel-uglify-plugin": "^0.2.0" "webpack-parallel-uglify-plugin": "^0.2.0"

View file

@ -65,7 +65,7 @@ import {
setupSyncAreas, setupSyncAreas,
syncScrollToEdit, syncScrollToEdit,
syncScrollToView syncScrollToView
} from './syncscroll' } from './lib/syncscroll'
import { import {
writeHistory, writeHistory,
@ -78,9 +78,10 @@ import {
import { preventXSS } from './render' import { preventXSS } from './render'
import Editor from './lib/editor' import Editor from './lib/editor'
import EditorConfig from './lib/editor/config'
import getUIElements from './lib/editor/ui-elements' import getUIElements from './lib/editor/ui-elements'
import modeType from './lib/modeType'
import appState from './lib/appState'
var defaultTextHeight = 20 var defaultTextHeight = 20
var viewportMargin = 20 var viewportMargin = 20
@ -124,7 +125,7 @@ var supportHeaders = [
search: '###### tags:' search: '###### tags:'
} }
] ]
var supportReferrals = [ const supportReferrals = [
{ {
text: '[reference link]', text: '[reference link]',
search: '[]' search: '[]'
@ -170,7 +171,7 @@ var supportReferrals = [
search: '[]' search: '[]'
} }
] ]
var supportExternals = [ const supportExternals = [
{ {
text: '{%youtube youtubeid %}', text: '{%youtube youtubeid %}',
search: 'youtube' search: 'youtube'
@ -196,12 +197,12 @@ var supportExternals = [
search: 'pdf' search: 'pdf'
} }
] ]
var supportExtraTags = [ const supportExtraTags = [
{ {
text: '[name tag]', text: '[name tag]',
search: '[]', search: '[]',
command: function () { command: function () {
return '[name=' + window.personalInfo.name + ']' return '[name=' + personalInfo.name + ']'
} }
}, },
{ {
@ -215,7 +216,7 @@ var supportExtraTags = [
text: '[my color tag]', text: '[my color tag]',
search: '[]', search: '[]',
command: function () { command: function () {
return '[color=' + window.personalInfo.color + ']' return '[color=' + personalInfo.color + ']'
} }
}, },
{ {
@ -227,18 +228,7 @@ var supportExtraTags = [
} }
} }
] ]
window.modeType = { const statusType = {
edit: {
name: 'edit'
},
view: {
name: 'view'
},
both: {
name: 'both'
}
}
var statusType = {
connected: { connected: {
msg: 'CONNECTED', msg: 'CONNECTED',
label: 'label-warning', label: 'label-warning',
@ -255,21 +245,19 @@ var statusType = {
fa: 'fa-plug' fa: 'fa-plug'
} }
} }
var defaultMode = modeType.view
// global vars // global vars
window.loaded = false window.loaded = false
window.needRefresh = false let needRefresh = false
window.isDirty = false let isDirty = false
window.editShown = false let editShown = false
window.visibleXS = false let visibleXS = false
window.visibleSM = false let visibleSM = false
window.visibleMD = false let visibleMD = false
window.visibleLG = false let visibleLG = false
window.isTouchDevice = 'ontouchstart' in document.documentElement const isTouchDevice = 'ontouchstart' in document.documentElement
window.currentMode = defaultMode let currentStatus = statusType.offline
window.currentStatus = statusType.offline let lastInfo = {
window.lastInfo = {
needRestore: false, needRestore: false,
cursor: null, cursor: null,
scroll: null, scroll: null,
@ -292,9 +280,9 @@ window.lastInfo = {
}, },
history: null history: null
} }
window.personalInfo = {} let personalInfo = {}
window.onlineUsers = [] let onlineUsers = []
window.fileTypes = { const fileTypes = {
'pl': 'perl', 'pl': 'perl',
'cgi': 'perl', 'cgi': 'perl',
'js': 'javascript', 'js': 'javascript',
@ -306,7 +294,7 @@ window.fileTypes = {
} }
// editor settings // editor settings
var textit = document.getElementById('textit') const textit = document.getElementById('textit')
if (!textit) { if (!textit) {
throw new Error('There was no textit area!') throw new Error('There was no textit area!')
} }
@ -394,7 +382,7 @@ function setRefreshModal (status) {
} }
function setNeedRefresh () { function setNeedRefresh () {
window.needRefresh = true needRefresh = true
editor.setOption('readOnly', true) editor.setOption('readOnly', true)
socket.disconnect() socket.disconnect()
showStatus(statusType.offline) showStatus(statusType.offline)
@ -416,7 +404,7 @@ Visibility.change(function (e, state) {
} }
} else { } else {
if (wasFocus) { if (wasFocus) {
if (!window.visibleXS) { if (!visibleXS) {
editor.focus() editor.focus()
editor.refresh() editor.refresh()
} }
@ -433,7 +421,7 @@ $(document).ready(function () {
checkResponsive() checkResponsive()
// if in smaller screen, we don't need advanced scrollbar // if in smaller screen, we don't need advanced scrollbar
var scrollbarStyle var scrollbarStyle
if (window.visibleXS) { if (visibleXS) {
scrollbarStyle = 'native' scrollbarStyle = 'native'
} else { } else {
scrollbarStyle = 'overlay' scrollbarStyle = 'overlay'
@ -444,9 +432,9 @@ $(document).ready(function () {
} }
checkEditorStyle() checkEditorStyle()
/* we need this only on touch devices */ /* we need this only on touch devices */
if (window.isTouchDevice) { if (isTouchDevice) {
/* cache dom references */ /* cache dom references */
var $body = jQuery('body') var $body = $('body')
/* bind events */ /* bind events */
$(document) $(document)
@ -497,7 +485,7 @@ $(window).on('error', function () {
// setNeedRefresh(); // setNeedRefresh();
}) })
setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown) setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown, editor)
function autoSyncscroll () { function autoSyncscroll () {
if (editorHasFocus()) { if (editorHasFocus()) {
@ -533,8 +521,8 @@ function windowResizeInner (callback) {
autoSyncscroll() autoSyncscroll()
editor.setOption('viewportMargin', viewportMargin) editor.setOption('viewportMargin', viewportMargin)
// add or update user cursors // add or update user cursors
for (var i = 0; i < window.onlineUsers.length; i++) { for (var i = 0; i < onlineUsers.length; i++) {
if (window.onlineUsers[i].id !== window.personalInfo.id) { buildCursor(window.onlineUsers[i]) } if (onlineUsers[i].id !== personalInfo.id) { buildCursor(onlineUsers[i]) }
} }
updateScrollspy() updateScrollspy()
if (callback && typeof callback === 'function') { callback() } if (callback && typeof callback === 'function') { callback() }
@ -554,12 +542,12 @@ function editorHasFocus () {
// 768-792px have a gap // 768-792px have a gap
function checkResponsive () { function checkResponsive () {
window.visibleXS = $('.visible-xs').is(':visible') visibleXS = $('.visible-xs').is(':visible')
window.visibleSM = $('.visible-sm').is(':visible') visibleSM = $('.visible-sm').is(':visible')
window.visibleMD = $('.visible-md').is(':visible') visibleMD = $('.visible-md').is(':visible')
window.visibleLG = $('.visible-lg').is(':visible') visibleLG = $('.visible-lg').is(':visible')
if (window.visibleXS && window.currentMode === modeType.both) { if (visibleXS && appState.currentMode === modeType.both) {
if (editorHasFocus()) { changeMode(modeType.edit) } else { changeMode(modeType.view) } if (editorHasFocus()) { changeMode(modeType.edit) } else { changeMode(modeType.view) }
} }
@ -573,7 +561,7 @@ function checkEditorStyle () {
var desireHeight = editorInstance.statusBar ? (ui.area.edit.height() - editorInstance.statusBar.outerHeight()) : ui.area.edit.height() var desireHeight = editorInstance.statusBar ? (ui.area.edit.height() - editorInstance.statusBar.outerHeight()) : ui.area.edit.height()
// set editor height and min height based on scrollbar style and mode // set editor height and min height based on scrollbar style and mode
var scrollbarStyle = editor.getOption('scrollbarStyle') var scrollbarStyle = editor.getOption('scrollbarStyle')
if (scrollbarStyle === 'overlay' || window.currentMode === modeType.both) { if (scrollbarStyle === 'overlay' || appState.currentMode === modeType.both) {
ui.area.codemirrorScroll.css('height', desireHeight + 'px') ui.area.codemirrorScroll.css('height', desireHeight + 'px')
ui.area.codemirrorScroll.css('min-height', '') ui.area.codemirrorScroll.css('min-height', '')
checkEditorScrollbar() checkEditorScrollbar()
@ -629,7 +617,7 @@ function checkEditorStyle () {
previousFocusOnEditor = null previousFocusOnEditor = null
}) })
ui.area.resize.syncToggle.click(function () { ui.area.resize.syncToggle.click(function () {
window.syncscroll = !window.syncscroll appState.syncscroll = !appState.syncscroll
checkSyncToggle() checkSyncToggle()
}) })
ui.area.resize.handle.append(ui.area.resize.syncToggle) ui.area.resize.handle.append(ui.area.resize.syncToggle)
@ -643,7 +631,7 @@ function checkEditorStyle () {
} }
function checkSyncToggle () { function checkSyncToggle () {
if (window.syncscroll) { if (appState.syncscroll) {
if (previousFocusOnEditor) { if (previousFocusOnEditor) {
window.preventSyncScrollToView = false window.preventSyncScrollToView = false
syncScrollToView() syncScrollToView()
@ -690,10 +678,10 @@ function checkTocStyle () {
// toc scrollspy // toc scrollspy
ui.toc.toc.removeClass('scrollspy-body, scrollspy-view') ui.toc.toc.removeClass('scrollspy-body, scrollspy-view')
ui.toc.affix.removeClass('scrollspy-body, scrollspy-view') ui.toc.affix.removeClass('scrollspy-body, scrollspy-view')
if (window.currentMode === modeType.both) { if (appState.currentMode === modeType.both) {
ui.toc.toc.addClass('scrollspy-view') ui.toc.toc.addClass('scrollspy-view')
ui.toc.affix.addClass('scrollspy-view') ui.toc.affix.addClass('scrollspy-view')
} else if (window.currentMode !== modeType.both && !newbool) { } else if (appState.currentMode !== modeType.both && !newbool) {
ui.toc.toc.addClass('scrollspy-body') ui.toc.toc.addClass('scrollspy-body')
ui.toc.affix.addClass('scrollspy-body') ui.toc.affix.addClass('scrollspy-body')
} else { } else {
@ -707,7 +695,7 @@ function checkTocStyle () {
} }
function showStatus (type, num) { function showStatus (type, num) {
window.currentStatus = type currentStatus = type
var shortStatus = ui.toolbar.shortStatus var shortStatus = ui.toolbar.shortStatus
var status = ui.toolbar.status var status = ui.toolbar.status
var label = $('<span class="label"></span>') var label = $('<span class="label"></span>')
@ -718,7 +706,7 @@ function showStatus (type, num) {
shortStatus.html('') shortStatus.html('')
status.html('') status.html('')
switch (window.currentStatus) { switch (currentStatus) {
case statusType.connected: case statusType.connected:
label.addClass(statusType.connected.label) label.addClass(statusType.connected.label)
fa.addClass(statusType.connected.fa) fa.addClass(statusType.connected.fa)
@ -748,7 +736,7 @@ function showStatus (type, num) {
} }
function toggleMode () { function toggleMode () {
switch (window.currentMode) { switch (appState.currentMode) {
case modeType.edit: case modeType.edit:
changeMode(modeType.view) changeMode(modeType.view)
break break
@ -768,8 +756,8 @@ function changeMode (type) {
lockNavbar() lockNavbar()
saveInfo() saveInfo()
if (type) { if (type) {
lastMode = window.currentMode lastMode = appState.currentMode
window.currentMode = type appState.currentMode = type
} }
var responsiveClass = 'col-lg-6 col-md-6 col-sm-6' var responsiveClass = 'col-lg-6 col-md-6 col-sm-6'
var scrollClass = 'ui-scrollable' var scrollClass = 'ui-scrollable'
@ -777,13 +765,13 @@ function changeMode (type) {
ui.area.edit.removeClass(responsiveClass) ui.area.edit.removeClass(responsiveClass)
ui.area.view.removeClass(scrollClass) ui.area.view.removeClass(scrollClass)
ui.area.view.removeClass(responsiveClass) ui.area.view.removeClass(responsiveClass)
switch (window.currentMode) { switch (appState.currentMode) {
case modeType.edit: case modeType.edit:
ui.area.edit.show() ui.area.edit.show()
ui.area.view.hide() ui.area.view.hide()
if (!window.editShown) { if (!editShown) {
editor.refresh() editor.refresh()
window.editShown = true editShown = true
} }
break break
case modeType.view: case modeType.view:
@ -798,11 +786,11 @@ function changeMode (type) {
break break
} }
// save mode to url // save mode to url
if (history.replaceState && window.loaded) history.replaceState(null, '', serverurl + '/' + noteid + '?' + window.currentMode.name) if (history.replaceState && window.loaded) history.replaceState(null, '', serverurl + '/' + noteid + '?' + appState.currentMode.name)
if (window.currentMode === modeType.view) { if (appState.currentMode === modeType.view) {
editor.getInputField().blur() editor.getInputField().blur()
} }
if (window.currentMode === modeType.edit || window.currentMode === modeType.both) { if (appState.currentMode === modeType.edit || appState.currentMode === modeType.both) {
ui.toolbar.uploadImage.fadeIn() ui.toolbar.uploadImage.fadeIn()
// add and update status bar // add and update status bar
if (!editorInstance.statusBar) { if (!editorInstance.statusBar) {
@ -815,14 +803,14 @@ function changeMode (type) {
} else { } else {
ui.toolbar.uploadImage.fadeOut() ui.toolbar.uploadImage.fadeOut()
} }
if (window.currentMode !== modeType.edit) { if (appState.currentMode !== modeType.edit) {
$(document.body).css('background-color', 'white') $(document.body).css('background-color', 'white')
updateView() updateView()
} else { } else {
$(document.body).css('background-color', ui.area.codemirror.css('background-color')) $(document.body).css('background-color', ui.area.codemirror.css('background-color'))
} }
// check resizable editor style // check resizable editor style
if (window.currentMode === modeType.both) { if (appState.currentMode === modeType.both) {
if (lastEditorWidth > 0) { if (lastEditorWidth > 0) {
ui.area.edit.css('width', lastEditorWidth + 'px') ui.area.edit.css('width', lastEditorWidth + 'px')
} else { } else {
@ -838,22 +826,22 @@ function changeMode (type) {
restoreInfo() restoreInfo()
if (lastMode === modeType.view && window.currentMode === modeType.both) { if (lastMode === modeType.view && appState.currentMode === modeType.both) {
window.preventSyncScrollToView = 2 window.preventSyncScrollToView = 2
syncScrollToEdit(null, true) syncScrollToEdit(null, true)
} }
if (lastMode === modeType.edit && window.currentMode === modeType.both) { if (lastMode === modeType.edit && appState.currentMode === modeType.both) {
window.preventSyncScrollToEdit = 2 window.preventSyncScrollToEdit = 2
syncScrollToView(null, true) syncScrollToView(null, true)
} }
if (lastMode === modeType.both && window.currentMode !== modeType.both) { if (lastMode === modeType.both && appState.currentMode !== modeType.both) {
window.preventSyncScrollToView = false window.preventSyncScrollToView = false
window.preventSyncScrollToEdit = false window.preventSyncScrollToEdit = false
} }
if (lastMode !== modeType.edit && window.currentMode === modeType.edit) { if (lastMode !== modeType.edit && appState.currentMode === modeType.edit) {
editor.refresh() editor.refresh()
} }
@ -1371,7 +1359,7 @@ ui.modal.snippetImportSnippets.change(function () {
}) })
function scrollToTop () { function scrollToTop () {
if (window.currentMode === modeType.both) { if (appState.currentMode === modeType.both) {
if (editor.getScrollInfo().top !== 0) { editor.scrollTo(0, 0) } else { if (editor.getScrollInfo().top !== 0) { editor.scrollTo(0, 0) } else {
ui.area.view.animate({ ui.area.view.animate({
scrollTop: 0 scrollTop: 0
@ -1385,7 +1373,7 @@ function scrollToTop () {
} }
function scrollToBottom () { function scrollToBottom () {
if (window.currentMode === modeType.both) { if (appState.currentMode === modeType.both) {
var scrollInfo = editor.getScrollInfo() var scrollInfo = editor.getScrollInfo()
var scrollHeight = scrollInfo.height var scrollHeight = scrollInfo.height
if (scrollInfo.top !== scrollHeight) { editor.scrollTo(0, scrollHeight * 2) } else { if (scrollInfo.top !== scrollHeight) { editor.scrollTo(0, scrollHeight * 2) } else {
@ -1544,7 +1532,7 @@ $('#snippetImportModalConfirm').click(function () {
if (raw) { if (raw) {
content += '\n\n' content += '\n\n'
if (fileInfo[1] !== 'md') { if (fileInfo[1] !== 'md') {
content += '```' + window.fileTypes[fileInfo[1]] + '\n' content += '```' + fileTypes[fileInfo[1]] + '\n'
} }
content += raw content += raw
if (fileInfo[1] !== 'md') { if (fileInfo[1] !== 'md') {
@ -1717,7 +1705,7 @@ function updatePermission (newPermission) {
title = 'Only owner can view & edit' title = 'Only owner can view & edit'
break break
} }
if (window.personalInfo.userid && window.owner && window.personalInfo.userid === window.owner) { if (personalInfo.userid && window.owner && personalInfo.userid === window.owner) {
label += ' <i class="fa fa-caret-down"></i>' label += ' <i class="fa fa-caret-down"></i>'
ui.infobar.permission.label.removeClass('disabled') ui.infobar.permission.label.removeClass('disabled')
} else { } else {
@ -1734,7 +1722,7 @@ function havePermission () {
break break
case 'editable': case 'editable':
case 'limited': case 'limited':
if (!window.personalInfo.login) { if (!personalInfo.login) {
bool = false bool = false
} else { } else {
bool = true bool = true
@ -1743,7 +1731,7 @@ function havePermission () {
case 'locked': case 'locked':
case 'private': case 'private':
case 'protected': case 'protected':
if (!window.owner || window.personalInfo.userid !== window.owner) { if (!window.owner || personalInfo.userid !== window.owner) {
bool = false bool = false
} else { } else {
bool = true bool = true
@ -1765,11 +1753,11 @@ var socket = io.connect({
// overwrite original event for checking login state // overwrite original event for checking login state
var on = socket.on var on = socket.on
socket.on = function () { socket.on = function () {
if (!checkLoginStateChanged() && !window.needRefresh) { return on.apply(socket, arguments) } if (!checkLoginStateChanged() && !needRefresh) { return on.apply(socket, arguments) }
} }
var emit = socket.emit var emit = socket.emit
socket.emit = function () { socket.emit = function () {
if (!checkLoginStateChanged() && !window.needRefresh) { emit.apply(socket, arguments) } if (!checkLoginStateChanged() && !needRefresh) { emit.apply(socket, arguments) }
} }
socket.on('info', function (data) { socket.on('info', function (data) {
console.error(data) console.error(data)
@ -1790,7 +1778,7 @@ socket.on('error', function (data) {
if (data.message && data.message.indexOf('AUTH failed') === 0) { location.href = serverurl + '/403' } if (data.message && data.message.indexOf('AUTH failed') === 0) { location.href = serverurl + '/403' }
}) })
socket.on('delete', function () { socket.on('delete', function () {
if (window.personalInfo.login) { if (personalInfo.login) {
deleteServerHistory(noteid, function (err, data) { deleteServerHistory(noteid, function (err, data) {
if (!err) location.href = serverurl if (!err) location.href = serverurl
}) })
@ -1810,12 +1798,12 @@ socket.on('disconnect', function (data) {
showStatus(statusType.offline) showStatus(statusType.offline)
if (window.loaded) { if (window.loaded) {
saveInfo() saveInfo()
window.lastInfo.history = editor.getHistory() lastInfo.history = editor.getHistory()
} }
if (!editor.getOption('readOnly')) { editor.setOption('readOnly', true) } if (!editor.getOption('readOnly')) { editor.setOption('readOnly', true) }
if (!retryTimer) { if (!retryTimer) {
retryTimer = setInterval(function () { retryTimer = setInterval(function () {
if (!window.needRefresh) socket.connect() if (!needRefresh) socket.connect()
}, 1000) }, 1000)
} }
}) })
@ -1828,7 +1816,7 @@ socket.on('reconnect', function (data) {
socket.on('connect', function (data) { socket.on('connect', function (data) {
clearInterval(retryTimer) clearInterval(retryTimer)
retryTimer = null retryTimer = null
window.personalInfo['id'] = socket.id personalInfo['id'] = socket.id
showStatus(statusType.connected) showStatus(statusType.connected)
socket.emit('version') socket.emit('version')
}) })
@ -2082,23 +2070,23 @@ socket.on('permission', function (data) {
var permission = null var permission = null
socket.on('refresh', function (data) { socket.on('refresh', function (data) {
// console.log(data); // console.log(data);
EditorConfig.docmaxlength = data.docmaxlength editorInstance.config.docmaxlength = data.docmaxlength
editor.setOption('maxLength', EditorConfig.docmaxlength) editor.setOption('maxLength', editorInstance.config.docmaxlength)
updateInfo(data) updateInfo(data)
updatePermission(data.permission) updatePermission(data.permission)
if (!window.loaded) { if (!window.loaded) {
// auto change mode if no content detected // auto change mode if no content detected
var nocontent = editor.getValue().length <= 0 var nocontent = editor.getValue().length <= 0
if (nocontent) { if (nocontent) {
if (window.visibleXS) { window.currentMode = modeType.edit } else { window.currentMode = modeType.both } if (visibleXS) { appState.currentMode = modeType.edit } else { appState.currentMode = modeType.both }
} }
// parse mode from url // parse mode from url
if (window.location.search.length > 0) { if (window.location.search.length > 0) {
var urlMode = modeType[window.location.search.substr(1)] var urlMode = modeType[window.location.search.substr(1)]
if (urlMode) window.currentMode = urlMode if (urlMode) appState.currentMode = urlMode
} }
changeMode(window.currentMode) changeMode(appState.currentMode)
if (nocontent && !window.visibleXS) { if (nocontent && !visibleXS) {
editor.focus() editor.focus()
editor.refresh() editor.refresh()
} }
@ -2146,8 +2134,8 @@ socket.on('doc', function (obj) {
} else { } else {
// if current doc is equal to the doc before disconnect // if current doc is equal to the doc before disconnect
if (setDoc && bodyMismatch) editor.clearHistory() if (setDoc && bodyMismatch) editor.clearHistory()
else if (window.lastInfo.history) editor.setHistory(window.lastInfo.history) else if (lastInfo.history) editor.setHistory(lastInfo.history)
window.lastInfo.history = null lastInfo.history = null
} }
if (!cmClient) { if (!cmClient) {
@ -2170,7 +2158,7 @@ socket.on('doc', function (obj) {
} }
if (setDoc && bodyMismatch) { if (setDoc && bodyMismatch) {
window.isDirty = true isDirty = true
updateView() updateView()
} }
@ -2178,18 +2166,18 @@ socket.on('doc', function (obj) {
}) })
socket.on('ack', function () { socket.on('ack', function () {
window.isDirty = true isDirty = true
updateView() updateView()
}) })
socket.on('operation', function () { socket.on('operation', function () {
window.isDirty = true isDirty = true
updateView() updateView()
}) })
socket.on('online users', function (data) { socket.on('online users', function (data) {
if (debug) { console.debug(data) } if (debug) { console.debug(data) }
window.onlineUsers = data.users onlineUsers = data.users
updateOnlineStatus() updateOnlineStatus()
$('.CodeMirror-other-cursors').children().each(function (key, value) { $('.CodeMirror-other-cursors').children().each(function (key, value) {
var found = false var found = false
@ -2205,14 +2193,14 @@ socket.on('online users', function (data) {
}) })
for (var i = 0; i < data.users.length; i++) { for (var i = 0; i < data.users.length; i++) {
var user = data.users[i] var user = data.users[i]
if (user.id !== socket.id) { buildCursor(user) } else { window.personalInfo = user } if (user.id !== socket.id) { buildCursor(user) } else { personalInfo = user }
} }
}) })
socket.on('user status', function (data) { socket.on('user status', function (data) {
if (debug) { console.debug(data) } if (debug) { console.debug(data) }
for (var i = 0; i < window.onlineUsers.length; i++) { for (var i = 0; i < onlineUsers.length; i++) {
if (window.onlineUsers[i].id === data.id) { if (onlineUsers[i].id === data.id) {
window.onlineUsers[i] = data onlineUsers[i] = data
} }
} }
updateOnlineStatus() updateOnlineStatus()
@ -2220,9 +2208,9 @@ socket.on('user status', function (data) {
}) })
socket.on('cursor focus', function (data) { socket.on('cursor focus', function (data) {
if (debug) { console.debug(data) } if (debug) { console.debug(data) }
for (var i = 0; i < window.onlineUsers.length; i++) { for (var i = 0; i < onlineUsers.length; i++) {
if (window.onlineUsers[i].id === data.id) { if (onlineUsers[i].id === data.id) {
window.onlineUsers[i].cursor = data.cursor onlineUsers[i].cursor = data.cursor
} }
} }
if (data.id !== socket.id) { buildCursor(data) } if (data.id !== socket.id) { buildCursor(data) }
@ -2234,18 +2222,18 @@ socket.on('cursor focus', function (data) {
}) })
socket.on('cursor activity', function (data) { socket.on('cursor activity', function (data) {
if (debug) { console.debug(data) } if (debug) { console.debug(data) }
for (var i = 0; i < window.onlineUsers.length; i++) { for (var i = 0; i < onlineUsers.length; i++) {
if (window.onlineUsers[i].id === data.id) { if (onlineUsers[i].id === data.id) {
window.onlineUsers[i].cursor = data.cursor onlineUsers[i].cursor = data.cursor
} }
} }
if (data.id !== socket.id) { buildCursor(data) } if (data.id !== socket.id) { buildCursor(data) }
}) })
socket.on('cursor blur', function (data) { socket.on('cursor blur', function (data) {
if (debug) { console.debug(data) } if (debug) { console.debug(data) }
for (var i = 0; i < window.onlineUsers.length; i++) { for (var i = 0; i < onlineUsers.length; i++) {
if (window.onlineUsers[i].id === data.id) { if (onlineUsers[i].id === data.id) {
window.onlineUsers[i].cursor = null onlineUsers[i].cursor = null
} }
} }
if (data.id !== socket.id) { buildCursor(data) } if (data.id !== socket.id) { buildCursor(data) }
@ -2270,7 +2258,7 @@ var shortOnlineUserList = new List('short-online-user-list', options)
function updateOnlineStatus () { function updateOnlineStatus () {
if (!window.loaded || !socket.connected) return if (!window.loaded || !socket.connected) return
var _onlineUsers = deduplicateOnlineUsers(window.onlineUsers) var _onlineUsers = deduplicateOnlineUsers(onlineUsers)
showStatus(statusType.online, _onlineUsers.length) showStatus(statusType.online, _onlineUsers.length)
var items = onlineUserList.items var items = onlineUserList.items
// update or remove current list items // update or remove current list items
@ -2321,8 +2309,8 @@ function sortOnlineUserList (list) {
sortFunction: function (a, b) { sortFunction: function (a, b) {
var usera = a.values() var usera = a.values()
var userb = b.values() var userb = b.values()
var useraIsSelf = (usera.id === window.personalInfo.id || (usera.login && usera.userid === window.personalInfo.userid)) var useraIsSelf = (usera.id === personalInfo.id || (usera.login && usera.userid === personalInfo.userid))
var userbIsSelf = (userb.id === window.personalInfo.id || (userb.login && userb.userid === window.personalInfo.userid)) var userbIsSelf = (userb.id === personalInfo.id || (userb.login && userb.userid === personalInfo.userid))
if (useraIsSelf && !userbIsSelf) { if (useraIsSelf && !userbIsSelf) {
return -1 return -1
} else if (!useraIsSelf && userbIsSelf) { } else if (!useraIsSelf && userbIsSelf) {
@ -2373,7 +2361,7 @@ function deduplicateOnlineUsers (list) {
for (var j = 0; j < _onlineUsers.length; j++) { for (var j = 0; j < _onlineUsers.length; j++) {
if (_onlineUsers[j].userid === user.userid) { if (_onlineUsers[j].userid === user.userid) {
// keep self color when login // keep self color when login
if (user.id === window.personalInfo.id) { if (user.id === personalInfo.id) {
_onlineUsers[j].color = user.color _onlineUsers[j].color = user.color
} }
// keep idle state if any of self client not idle // keep idle state if any of self client not idle
@ -2396,14 +2384,14 @@ var userStatusCache = null
function emitUserStatus (force) { function emitUserStatus (force) {
if (!window.loaded) return if (!window.loaded) return
var type = null var type = null
if (window.visibleXS) { type = 'xs' } else if (window.visibleSM) { type = 'sm' } else if (window.visibleMD) { type = 'md' } else if (window.visibleLG) { type = 'lg' } if (visibleXS) { type = 'xs' } else if (visibleSM) { type = 'sm' } else if (visibleMD) { type = 'md' } else if (visibleLG) { type = 'lg' }
window.personalInfo['idle'] = idle.isAway personalInfo['idle'] = idle.isAway
window.personalInfo['type'] = type personalInfo['type'] = type
for (var i = 0; i < window.onlineUsers.length; i++) { for (var i = 0; i < onlineUsers.length; i++) {
if (window.onlineUsers[i].id === window.personalInfo.id) { if (onlineUsers[i].id === personalInfo.id) {
window.onlineUsers[i] = window.personalInfo onlineUsers[i] = personalInfo
} }
} }
@ -2457,7 +2445,7 @@ function checkCursorTag (coord, ele) {
} }
function buildCursor (user) { function buildCursor (user) {
if (window.currentMode === modeType.view) return if (appState.currentMode === modeType.view) return
if (!user.cursor) return if (!user.cursor) return
var coord = editor.charCoords(user.cursor, 'windows') var coord = editor.charCoords(user.cursor, 'windows')
coord.left = coord.left < 4 ? 4 : coord.left coord.left = coord.left < 4 ? 4 : coord.left
@ -2477,9 +2465,6 @@ function buildCursor (user) {
iconClass = 'fa-desktop' iconClass = 'fa-desktop'
break break
} }
if ($('.CodeMirror-other-cursors').length <= 0) {
$("<div class='CodeMirror-other-cursors'>").insertAfter('.CodeMirror-cursors')
}
if ($('div[data-clientid="' + user.id + '"]').length <= 0) { if ($('div[data-clientid="' + user.id + '"]').length <= 0) {
let cursor = $('<div data-clientid="' + user.id + '" class="CodeMirror-other-cursor" style="display:none;"></div>') let cursor = $('<div data-clientid="' + user.id + '" class="CodeMirror-other-cursor" style="display:none;"></div>')
cursor.attr('data-line', user.cursor.line) cursor.attr('data-line', user.cursor.line)
@ -2664,12 +2649,12 @@ editorInstance.on('changes', function (editor, changes) {
} }
}) })
editorInstance.on('focus', function (editor) { editorInstance.on('focus', function (editor) {
for (var i = 0; i < window.onlineUsers.length; i++) { for (var i = 0; i < onlineUsers.length; i++) {
if (window.onlineUsers[i].id === window.personalInfo.id) { if (onlineUsers[i].id === personalInfo.id) {
window.onlineUsers[i].cursor = editor.getCursor() onlineUsers[i].cursor = editor.getCursor()
} }
} }
window.personalInfo['cursor'] = editor.getCursor() personalInfo['cursor'] = editor.getCursor()
socket.emit('cursor focus', editor.getCursor()) socket.emit('cursor focus', editor.getCursor())
}) })
@ -2677,12 +2662,12 @@ const cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce)
function cursorActivityInner (editor) { function cursorActivityInner (editor) {
if (editorHasFocus() && !Visibility.hidden()) { if (editorHasFocus() && !Visibility.hidden()) {
for (var i = 0; i < window.onlineUsers.length; i++) { for (var i = 0; i < onlineUsers.length; i++) {
if (window.onlineUsers[i].id === window.personalInfo.id) { if (onlineUsers[i].id === personalInfo.id) {
window.onlineUsers[i].cursor = editor.getCursor() onlineUsers[i].cursor = editor.getCursor()
} }
} }
window.personalInfo['cursor'] = editor.getCursor() personalInfo['cursor'] = editor.getCursor()
socket.emit('cursor activity', editor.getCursor()) socket.emit('cursor activity', editor.getCursor())
} }
} }
@ -2724,12 +2709,12 @@ editorInstance.on('beforeSelectionChange', function (doc, selections) {
}) })
editorInstance.on('blur', function (cm) { editorInstance.on('blur', function (cm) {
for (var i = 0; i < window.onlineUsers.length; i++) { for (var i = 0; i < onlineUsers.length; i++) {
if (window.onlineUsers[i].id === window.personalInfo.id) { if (onlineUsers[i].id === personalInfo.id) {
window.onlineUsers[i].cursor = null onlineUsers[i].cursor = null
} }
} }
window.personalInfo['cursor'] = null personalInfo['cursor'] = null
socket.emit('cursor blur') socket.emit('cursor blur')
}) })
@ -2737,71 +2722,71 @@ function saveInfo () {
var scrollbarStyle = editor.getOption('scrollbarStyle') var scrollbarStyle = editor.getOption('scrollbarStyle')
var left = $(window).scrollLeft() var left = $(window).scrollLeft()
var top = $(window).scrollTop() var top = $(window).scrollTop()
switch (window.currentMode) { switch (appState.currentMode) {
case modeType.edit: case modeType.edit:
if (scrollbarStyle === 'native') { if (scrollbarStyle === 'native') {
window.lastInfo.edit.scroll.left = left lastInfo.edit.scroll.left = left
window.lastInfo.edit.scroll.top = top lastInfo.edit.scroll.top = top
} else { } else {
window.lastInfo.edit.scroll = editor.getScrollInfo() lastInfo.edit.scroll = editor.getScrollInfo()
} }
break break
case modeType.view: case modeType.view:
window.lastInfo.view.scroll.left = left lastInfo.view.scroll.left = left
window.lastInfo.view.scroll.top = top lastInfo.view.scroll.top = top
break break
case modeType.both: case modeType.both:
window.lastInfo.edit.scroll = editor.getScrollInfo() lastInfo.edit.scroll = editor.getScrollInfo()
window.lastInfo.view.scroll.left = ui.area.view.scrollLeft() lastInfo.view.scroll.left = ui.area.view.scrollLeft()
window.lastInfo.view.scroll.top = ui.area.view.scrollTop() lastInfo.view.scroll.top = ui.area.view.scrollTop()
break break
} }
window.lastInfo.edit.cursor = editor.getCursor() lastInfo.edit.cursor = editor.getCursor()
window.lastInfo.edit.selections = editor.listSelections() lastInfo.edit.selections = editor.listSelections()
window.lastInfo.needRestore = true lastInfo.needRestore = true
} }
function restoreInfo () { function restoreInfo () {
var scrollbarStyle = editor.getOption('scrollbarStyle') var scrollbarStyle = editor.getOption('scrollbarStyle')
if (window.lastInfo.needRestore) { if (lastInfo.needRestore) {
var line = window.lastInfo.edit.cursor.line var line = lastInfo.edit.cursor.line
var ch = window.lastInfo.edit.cursor.ch var ch = lastInfo.edit.cursor.ch
editor.setCursor(line, ch) editor.setCursor(line, ch)
editor.setSelections(window.lastInfo.edit.selections) editor.setSelections(lastInfo.edit.selections)
switch (window.currentMode) { switch (appState.currentMode) {
case modeType.edit: case modeType.edit:
if (scrollbarStyle === 'native') { if (scrollbarStyle === 'native') {
$(window).scrollLeft(window.lastInfo.edit.scroll.left) $(window).scrollLeft(lastInfo.edit.scroll.left)
$(window).scrollTop(window.lastInfo.edit.scroll.top) $(window).scrollTop(lastInfo.edit.scroll.top)
} else { } else {
let left = window.lastInfo.edit.scroll.left let left = lastInfo.edit.scroll.left
let top = window.lastInfo.edit.scroll.top let top = lastInfo.edit.scroll.top
editor.scrollIntoView() editor.scrollIntoView()
editor.scrollTo(left, top) editor.scrollTo(left, top)
} }
break break
case modeType.view: case modeType.view:
$(window).scrollLeft(window.lastInfo.view.scroll.left) $(window).scrollLeft(lastInfo.view.scroll.left)
$(window).scrollTop(window.lastInfo.view.scroll.top) $(window).scrollTop(lastInfo.view.scroll.top)
break break
case modeType.both: case modeType.both:
let left = window.lastInfo.edit.scroll.left let left = lastInfo.edit.scroll.left
let top = window.lastInfo.edit.scroll.top let top = lastInfo.edit.scroll.top
editor.scrollIntoView() editor.scrollIntoView()
editor.scrollTo(left, top) editor.scrollTo(left, top)
ui.area.view.scrollLeft(window.lastInfo.view.scroll.left) ui.area.view.scrollLeft(lastInfo.view.scroll.left)
ui.area.view.scrollTop(window.lastInfo.view.scroll.top) ui.area.view.scrollTop(lastInfo.view.scroll.top)
break break
} }
window.lastInfo.needRestore = false lastInfo.needRestore = false
} }
} }
// view actions // view actions
function refreshView () { function refreshView () {
ui.area.markdown.html('') ui.area.markdown.html('')
window.isDirty = true isDirty = true
updateViewInner() updateViewInner()
} }
@ -2813,7 +2798,7 @@ var lastResult = null
var postUpdateEvent = null var postUpdateEvent = null
function updateViewInner () { function updateViewInner () {
if (window.currentMode === modeType.edit || !window.isDirty) return if (appState.currentMode === modeType.edit || !isDirty) return
var value = editor.getValue() var value = editor.getValue()
var lastMeta = md.meta var lastMeta = md.meta
md.meta = {} md.meta = {}
@ -2830,13 +2815,13 @@ function updateViewInner () {
// prevent XSS // prevent XSS
ui.area.markdown.html(preventXSS(ui.area.markdown.html())) ui.area.markdown.html(preventXSS(ui.area.markdown.html()))
ui.area.markdown.addClass('slides') ui.area.markdown.addClass('slides')
window.syncscroll = false appState.syncscroll = false
checkSyncToggle() checkSyncToggle()
} else { } else {
if (lastMeta.type && lastMeta.type === 'slide') { if (lastMeta.type && lastMeta.type === 'slide') {
refreshView() refreshView()
ui.area.markdown.removeClass('slides') ui.area.markdown.removeClass('slides')
window.syncscroll = true appState.syncscroll = true
checkSyncToggle() checkSyncToggle()
} }
// only render again when meta changed // only render again when meta changed
@ -2861,7 +2846,7 @@ function updateViewInner () {
generateScrollspy() generateScrollspy()
updateScrollspy() updateScrollspy()
smoothHashScroll() smoothHashScroll()
window.isDirty = false isDirty = false
clearMap() clearMap()
// buildMap(); // buildMap();
updateTitleReminder() updateTitleReminder()

View file

@ -0,0 +1,8 @@
import modeType from './modeType'
let state = {
syncscroll: true,
currentMode: modeType.view
}
export default state

View file

@ -1,5 +1,6 @@
import * as utils from './utils' import * as utils from './utils'
import config from './config' import config from './config'
import statusBarTemplate from './statusbar.html'
/* config section */ /* config section */
const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault
@ -118,6 +119,7 @@ export default class Editor {
} }
} }
this.eventListeners = {} this.eventListeners = {}
this.config = config
} }
on (event, cb) { on (event, cb) {
@ -132,20 +134,8 @@ export default class Editor {
}) })
} }
getStatusBarTemplate () {
return new Promise((resolve, reject) => {
$.get(window.serverurl + '/views/statusbar.html').done(template => {
this.statusBarTemplate = template
resolve()
}).fail(reject)
})
}
addStatusBar () { addStatusBar () {
if (!this.statusBarTemplate) { this.statusBar = $(statusBarTemplate)
this.getStatusBarTemplate.then(this.addStatusBar)
} else {
this.statusBar = $(this.statusBarTemplate)
this.statusCursor = this.statusBar.find('.status-cursor > .status-line-column') this.statusCursor = this.statusBar.find('.status-cursor > .status-line-column')
this.statusSelection = this.statusBar.find('.status-cursor > .status-selection') this.statusSelection = this.statusBar.find('.status-cursor > .status-selection')
this.statusFile = this.statusBar.find('.status-file') this.statusFile = this.statusBar.find('.status-file')
@ -166,7 +156,6 @@ export default class Editor {
this.setSpellcheck() this.setSpellcheck()
this.setPreferences() this.setPreferences()
} }
}
updateStatusBar () { updateStatusBar () {
if (!this.statusBar) return if (!this.statusBar) return
@ -508,8 +497,6 @@ export default class Editor {
placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)" placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)"
}) })
this.getStatusBarTemplate()
return this.editor return this.editor
} }

11
public/js/lib/modeType.js Normal file
View file

@ -0,0 +1,11 @@
export default {
edit: {
name: 'edit'
},
view: {
name: 'view'
},
both: {
name: 'both'
}
}

View file

@ -4,7 +4,9 @@
import markdownitContainer from 'markdown-it-container' import markdownitContainer from 'markdown-it-container'
import { md } from './extra' import { md } from '../extra'
import modeType from './modeType'
import appState from './appState'
function addPart (tokens, idx) { function addPart (tokens, idx) {
if (tokens[idx].map && tokens[idx].level === 0) { if (tokens[idx].map && tokens[idx].level === 0) {
@ -109,9 +111,6 @@ md.use(markdownitContainer, 'info', { render: renderContainer })
md.use(markdownitContainer, 'warning', { render: renderContainer }) md.use(markdownitContainer, 'warning', { render: renderContainer })
md.use(markdownitContainer, 'danger', { render: renderContainer }) md.use(markdownitContainer, 'danger', { render: renderContainer })
// FIXME: expose syncscroll to window
window.syncscroll = true
window.preventSyncScrollToEdit = false window.preventSyncScrollToEdit = false
window.preventSyncScrollToView = false window.preventSyncScrollToView = false
@ -126,10 +125,15 @@ let editArea = null
let viewArea = null let viewArea = null
let markdownArea = null let markdownArea = null
export function setupSyncAreas (edit, view, markdown) { let editor
export function setupSyncAreas (edit, view, markdown, _editor) {
editArea = edit editArea = edit
viewArea = view viewArea = view
markdownArea = markdown markdownArea = markdown
editor = _editor
editArea.on('scroll', _.throttle(syncScrollToView, editScrollThrottle)) editArea.on('scroll', _.throttle(syncScrollToView, editScrollThrottle))
viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle)) viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle))
} }
@ -161,8 +165,8 @@ function buildMapInner (callback) {
viewBottom = viewArea[0].scrollHeight - viewArea.height() viewBottom = viewArea[0].scrollHeight - viewArea.height()
acc = 0 acc = 0
const lines = window.editor.getValue().split('\n') const lines = editor.getValue().split('\n')
const lineHeight = window.editor.defaultTextHeight() const lineHeight = editor.defaultTextHeight()
for (i = 0; i < lines.length; i++) { for (i = 0; i < lines.length; i++) {
const str = lines[i] const str = lines[i]
@ -173,7 +177,7 @@ function buildMapInner (callback) {
continue continue
} }
const h = window.editor.heightAtLine(i + 1) - window.editor.heightAtLine(i) const h = editor.heightAtLine(i + 1) - editor.heightAtLine(i)
acc += Math.round(h / lineHeight) acc += Math.round(h / lineHeight)
} }
_lineHeightMap.push(acc) _lineHeightMap.push(acc)
@ -228,7 +232,7 @@ function buildMapInner (callback) {
let viewScrollingTimer = null let viewScrollingTimer = null
export function syncScrollToEdit (event, preventAnimate) { export function syncScrollToEdit (event, preventAnimate) {
if (window.currentMode !== window.modeType.both || !window.syncscroll || !editArea) return if (appState.currentMode !== modeType.both || !appState.syncscroll || !editArea) return
if (window.preventSyncScrollToEdit) { if (window.preventSyncScrollToEdit) {
if (typeof window.preventSyncScrollToEdit === 'number') { if (typeof window.preventSyncScrollToEdit === 'number') {
window.preventSyncScrollToEdit-- window.preventSyncScrollToEdit--
@ -268,8 +272,8 @@ export function syncScrollToEdit (event, preventAnimate) {
let posTo = 0 let posTo = 0
let topDiffPercent = 0 let topDiffPercent = 0
let posToNextDiff = 0 let posToNextDiff = 0
const scrollInfo = window.editor.getScrollInfo() const scrollInfo = editor.getScrollInfo()
const textHeight = window.editor.defaultTextHeight() const textHeight = editor.defaultTextHeight()
const preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight const preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight
const preLastLineNo = Math.round(preLastLineHeight / textHeight) const preLastLineNo = Math.round(preLastLineHeight / textHeight)
const preLastLinePos = scrollMap[preLastLineNo] const preLastLinePos = scrollMap[preLastLineNo]
@ -310,7 +314,7 @@ function viewScrollingTimeoutInner () {
let editScrollingTimer = null let editScrollingTimer = null
export function syncScrollToView (event, preventAnimate) { export function syncScrollToView (event, preventAnimate) {
if (window.currentMode !== window.modeType.both || !window.syncscroll || !viewArea) return if (appState.currentMode !== modeType.both || !appState.syncscroll || !viewArea) return
if (window.preventSyncScrollToView) { if (window.preventSyncScrollToView) {
if (typeof preventSyncScrollToView === 'number') { if (typeof preventSyncScrollToView === 'number') {
window.preventSyncScrollToView-- window.preventSyncScrollToView--
@ -329,8 +333,8 @@ export function syncScrollToView (event, preventAnimate) {
let lineNo, posTo let lineNo, posTo
let topDiffPercent, posToNextDiff let topDiffPercent, posToNextDiff
const scrollInfo = window.editor.getScrollInfo() const scrollInfo = editor.getScrollInfo()
const textHeight = window.editor.defaultTextHeight() const textHeight = editor.defaultTextHeight()
lineNo = Math.floor(scrollInfo.top / textHeight) lineNo = Math.floor(scrollInfo.top / textHeight)
// if reach the last line, will start lerp to the bottom // if reach the last line, will start lerp to the bottom
const diffToBottom = (scrollInfo.top + scrollInfo.clientHeight) - (scrollInfo.height - textHeight) const diffToBottom = (scrollInfo.top + scrollInfo.clientHeight) - (scrollInfo.height - textHeight)

View file

@ -412,6 +412,9 @@ module.exports = {
}, { }, {
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
loader: 'file' loader: 'file'
}, {
test: /\.html$/,
loader: 'string'
}, { }, {
test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url?prefix=font/&limit=5000' loader: 'url?prefix=font/&limit=5000'

View file

@ -6529,6 +6529,10 @@ strict-uri-encode@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
string-loader@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/string-loader/-/string-loader-0.0.1.tgz#496f3cccc990213e0dd5411499f9ac6a6a6f2ff8"
string-natural-compare@^2.0.2: string-natural-compare@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-2.0.2.tgz#c5ce4e278ab5d1265ae6fc55435aeb7b76fcb001" resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-2.0.2.tgz#c5ce4e278ab5d1265ae6fc55435aeb7b76fcb001"