demo - Hearen/AlgorithmHackers Wiki

定制Stackedit

在写自己的博客系统的时候想要做一个markdown编辑器,参考了csdn的markdown编辑器和一些开源的实现(例如:suMarkdown)之后,感觉还是使用比较成熟的stackedit来定制比较方便。主要还是因为初学JS的缘故,要实现的功能其中好多都要现学,好麻烦……


准备

在开发之前首先有一些预安装的依赖参见stackedit的developer-guide。安装之后启动Nodejs服务器,能够看到和http://stackedit.io相同的页面,这样可以来到stackedit的编辑器。这样我们就可以修改源码并查看效果了。


结构

stackedit结构图

  • 加载流程: stackedit是通过nodejs的express框架来实现路由的,在server.js中我们发现服务器调用app模块来进行路由,编辑器的路径映射到views/editor.html,这之后通过requirejs的管理来加载相应的模块,细节详见代码。

core

我们从core开始分析: core的功能包括:

  • creating the UI Layout, the ACE editor and the PageDown editor,
  • loading/saving the settings,
  • running periodic tasks,
  • detecting the user activity,
  • checking the offline status.

###offline 在设置在线状态的时候,core中使用了setOnlinesetOffline函数,里面都调用了eventMgr.onOfflineChanged()函数,代码如下:

	core.setOffline = function() {
		offlineTime = utils.currentTime;
		if(isOffline === false) {
			isOffline = true;
			eventMgr.onOfflineChanged(true);
		}
	};
	function setOnline() {
		if(isOffline === true) {
			isOffline = false;
			eventMgr.onOfflineChanged(false);
		}
	}

阅读其他部分代码也可发现这个类型的函数有很多,通过阅读开发者指南我们发现这个类型的函数用来触发相应的事件,下面我们深入eventMgr来一探究竟。 和以上函数相关的语句:

addEventHook("onOfflineChanged");

function addEventHook(eventName) {
	eventMgr[eventName] = createEventHook(eventName);
}

function createEventHook(eventName) {
	eventListenerListMap[eventName] = getExtensionListenerList(eventName);
	return function() {
		logger.log(eventName, arguments);
		var eventArguments = arguments;
		_.each(eventListenerListMap[eventName], function(listener) {
			// Use try/catch in case userCustom listener contains error
			try {
				listener.apply(null, eventArguments);
			}
			catch(e) {
				console.error(_.isObject(e) ? e.stack : e);
			}
		});
	};
}

function getExtensionListenerList(eventName) {
	return _.chain(extensionList).map(function(extension) {
		return extension.enabled && extension[eventName];
	}).compact().value();
}

var extensionList = _.chain(arguments).map(function(argument) {
	return argument instanceof Extension && argument;
}).compact().value();

其中,_.chain()map()都是underscorejs中的:

_.chain(obj) 返回一个封装的对象. 在封装的对象上调用方法会返回封装的对象本身, 直道 value 方法调用为止.

_.map(list, iteratee, [context]) Alias: collect 通过转换函数(iteratee迭代器)映射列表中的每个值产生价值的新数组。iteratee传递三个参数:value,然后是迭代 index(或 key 愚人码头注:如果list是个JavaScript对象是,这个参数就是key),最后一个是引用指向整个list。

_.map([1, 2, 3], function(num){ return num * 3; }); => [3, 6, 9] _.map({one: 1, two: 2, three: 3}, function(num, key){ return num * 3; }); => [3, 6, 9] _.map([1, 2], 3, 4, .first); => [1, 3] **.each(list, iteratee, [context]) Alias: forEach** 遍历list中的所有元素,按顺序用遍历输出每个元素。如果传递了context参数,则把iteratee绑定到context对象上。每次调用iteratee都会传递三个参数:(element, index, list)。如果list是个JavaScript对象,iteratee的参数是 (value, key, list))。返回list以方便链式调用。(愚人码头注:如果存在原生的forEach方法,Underscore就使用它代替。)

_.each([1, 2, 3], alert); => alerts each number in turn... .each({one: 1, two: 2, three: 3}, alert); => alerts each number value in turn... **.compact(array)** 返回一个除去所有false值的 array副本。 在javascript中, false, null, 0, "", undefined 和 NaN 都是false值.

_.compact([0, 1, false, 2, '', 3]); => [1, 2, 3]

_.each(list, iteratee, [context]) Alias: forEach 遍历list中的所有元素,按顺序用遍历输出每个元素。如果传递了context参数,则把iteratee绑定到context对象上。每次调用iteratee都会传递三个参数:(element, index, list)。如果list是个JavaScript对象,iteratee的参数是 (value, key, list))。返回list以方便链式调用。(愚人码头注:如果存在原生的forEach方法,Underscore就使用它代替。)

_.each([1, 2, 3], alert); => alerts each number in turn... _.each({one: 1, two: 2, three: 3}, alert); => alerts each number value in turn...

这段代码主要的逻辑就是调用eventMgr.onMessage这类的函数会发送相应的消息,和extensions中的对应的事件的响应函数联系起来,发送消息后即会触发这些函数。

回到offline判断的话题,其实经过测试,由于有

var isOffline = false;

的存在,再加上checkOnline只有在离线状态才进行检查,如下:

function checkOnline() {
	// Try to reconnect if we are offline but we have some networks
	if(isOffline === true && navigator.onLine === true && offlineTime + constants.CHECK_ONLINE_PERIOD < utils.currentTime) {
		offlineTime = utils.currentTime;
		// Try to download anything to test the connection
		$.ajax({
			url: "//www.google.com/jsapi",
			timeout: constants.AJAX_TIMEOUT,
			dataType: "script"
		}).done(function() {
			setOnline();
		});
	}
}

所以一开始状态一直是在线状态。但是在进行同步的时候就可能改变状态,再加上检测在线的代码中是从google的网站上下载来判断是否在线,和国情不符,于是我们进行了一下修改:

function checkOnline() {
	// Try to reconnect if we are offline but we have some networks
	if(isOffline === true && navigator.onLine === true && offlineTime + constants.CHECK_ONLINE_PERIOD < utils.currentTime) {
		offlineTime = utils.currentTime;
		// Try to download anything to test the connection
		$.ajax({
			url: "//api.github.com/",
			timeout: constants.AJAX_TIMEOUT,
			dataType: "json"
		}).done(function() {
			setOnline();
		});
	}
}

这样就能正确的判断是否在线了。