Rev. 2.73

역시나, 기업 요구사항은 파일 교환보다는 문서 관리 쪽으로 많이 치우쳐져 있더군요. 그래서 문서관련 기능을 강화하기 위해 매시업할 수 있는 서비스들을 찾아보던 중 Scribd라는 서비스를 알게 되었습니다. 이 서비스는 업로드한 문서를 HTML5(또는 플래시)에서 출력할 수 있도록 컨버전해주는 서비스였습니다. 변환된 문서는 원본 문서와 다른 점을 찾아보기 어려울 정도로 품질이 좋더군요. 그리고 자신들이 가진 기술을 공유에 목적을 두고 마음껏 사용해 주기를 바라는 착한 회사였습니다.

Scribd의 API는 문서를 웹페이지에 임베드하기 위한 자바스크립트 API와 문서를 등록하거나 메타데이터를 조회하거나 검색 등을 할 수 있는 플랫폼 API로 나뉩니다. 플랫폼 API에는 Ruby, Java, PHP, Python 등의 라이브러리가 있었지만 아쉽게도 Node.JS용 라이브러리만 쏙 빠져있어서 래핑 모듈을 만들고 배포합니다. 아래는 GitHub에 공개한 scribd 모듈의 API 문서입니다.

Scribd Platform API binding for Node.JS

Installing:

$ npm install scribd

Usage:

var Scribd = require('scribd');

var key = "ENTER-YOUR-API-KEY-HERE"
  , secret = "ENTER-YOUR-API-SECRET-HERE"; 

var scribd = new Scribd(key, secret);

// or (>= 0.1.5)

var scribd = new Scribd({
    apikey: key
  , secret: secret
});


/**
 * docs.upload
 */

scribd.upload(function(err, res) {
  console.log("\n scribd.upload", err, res);
}, "./my.docx", "doc", "private");

// or (>= 0.1.5)

scribd.upload({
    file: "./my.docx"
  , docType: "doc"
  , access: "private"
}, function(err, res) {
  console.log("\n scribd.upload", err, res);
});

Methods:

/**
 * Docs Method
 */

// docs.upload (callback, file, [docType], [access], [revId])
scribd.upload(function(err, res) {
  console.log('\n scribd.upload', err, res);
}, './document.path');

// docs.uploadFromUrl (callback, url, [docType], [access], [revId])
scribd.uploadFromUrl(function(err, res) {
  console.log('\n scribd.uploadFromUrl', err, res);
}, 'url');

// docs.getList (callback)
scribd.getList(function(err, res) {
  console.log('\n scribd.getList', err, res);
});

// docs.getConversionStatus (callback, docId)
scribd.getConversionStatus(function(err, res) {
  console.log('\n scribd.getConversionStatus', err, res);
}, 'docId');

// docs.getSettings (callback, docId)
scribd.getSettings(function(err, res) {
  console.log('\n scribd.getSettings', err, res);
}, 'docId');

// docs.changeSettings (callback, docId, [title], [description], [access], [license], [showAds], [tags])
scribd.changeSettings(function(err, res) {
  console.log('\n scribd.changeSettings', err, res);
}, 'docId', 'title');

// docs.getDownloadUrl (callback, docId)
scribd.getDownloadUrl(function(err, res) {
  console.log('\n scribd.getDownloadUrl', err, res);
}, 'docId');

// docs.getStats (callback, docId)
scribd.getStats(function(err, res) {
  console.log('\n scribd.getStats', err, res);
}, 'docId');

// docs.delete (callback, docId)
scribd.delete(function(err, res) {
  console.log('\n scribd.remove', err, res);
}, 'docId');

// docs.search (callback, query, [numResults], [numStart], [scope])
scribd.search(function(err, res) {
  console.log('\n scribd.search', err, res);
}, 'Node.JS', 1);

// docs.getCategories (callback, docId)
scribd.getCategories(function(err, res) {
  console.log('\n scribd.getCategories', err, res);
}, 'docId');

// docs.featured (callback, [limit], [offset], [scope])
scribd.featured(function(err, res) {
  console.log('\n scribd.featured', err, res);
});

// docs.browse (callback, [limit], [offset], [categoryId], [sort])
scribd.browse(function(err, res) {
  console.log('\n scribd.browse', err, res);
});

// docs.uploadThumb (callback, file, docId)
scribd.uploadThumb(function(err, res) {
  console.log('\n scribd.uploadThumb', err, res);
}, 'thumbnail.path', 'docId');


/**
 * Thumbnail Method
 */

// thumbnail.get (callback, docId, [width], [height])
scribd.getThumbnail(function(err, res) {
  console.log('\n scribd.getThumbnail', err, res);
}, 'docId', 256);


/**
 * User Method
 */

// user.login (callback, username, password)
scribd.login(function(err, res) {
  console.log('\n scribd.login', err, res);
}, 'username', 'password');

// user.signup (callback, username, password, email, [name])
scribd.signup(function(err, res) {
  console.log('\n scribd.signup', err, res);
}, 'username', 'password', 'email', 'name');

// user.getAutoSigninUrl (callback, [nextUrl])
scribd.getAutoSigninUrl(function(err, res) {
  console.log('\n scribd.getAutoSigninUrl', err, res);
}, '/');

Have fun!

License

MIT <3

Comments

CodeMirror는 웹 브라우저에서 코드 편집 기능을 제공할 수 있게 하는 자바스크립트 컴포넌트입니다. 자바스크립트, CSS, HTML은 물론 C, Java, PHP, Python, Ruby 등 50여 종의 언어 모드(Mode)를 지원하며, 문법 강조, 단축키, 자동포맷, 코드 폴딩, 테마, 검색 및 바꾸기 등의 유틸리티를 제공합니다. JSBin, Google Apps Script, Zen Coding등의 사이트에서 CodeMirror를 이용하여 코드 편집 기능을 제공하고 있습니다. JSBin을 포크 해서 고쳐 사용 중인 FireJSBin(코드네임)의 라이브러리를 업데이트하려다가 CodeMirror 3의 정식 버전이 최근에 릴리즈되었다는 사실을 알게 되었습니다. 그래서 버전 2.x에 비해 어떤 점들이 달라지고 어떻게 업그레이드하는지 살펴보도록 하겠습니다.

CodeMirror 버전 3은 2.x의 API에서 크게 벗어나지 않았습니다. 단순한 방법으로 CodeMirror를 이용한 사이트는 별다른 문제 없이 업그레이드할 수 있습니다. 애석하게도 버전 3부터는 인터넷 익스플로러 7을 완전히 지원하는 것을 포기합니다. IE7을 지원하는 코드들이 효율성을 떨어트리기 때문일까요? 아무튼, 그렇습니다.

DOM structure

편집기 내부에 사용되는 새로운 스크롤링 모델을 구현하기 위해 꽤 많은 DOM 구조가 변경되었습니다. 그래서 codemirror.css를 변경해서 사용하는 경우에는 문제를 일으킬 가능성이 큽니다. 편집기의 높이를 처리하는 요소가 스크롤러 요소(CSS class CodeMirror-scroll)에서 래퍼(Wrapper) 요소(CSS class CodeMirror)로 변경되었습니다. 그 외 다른 요소들이 이동되거나 제거되었고 추가되었습니다. 만약 편집기 내부의 DOM 요소를 제어하는 코드를 작성했다면 버전 3에서는 그 코드들을 다시 테스트해야 할 것입니다. CodeMirror 메뉴얼의 스타일링 섹션에서 더 많은 정보를 얻을 수 있습니다.

Gutter model

CodeMirror 2.x에서는 싱글 거터(Gutter, 라인 넘버가 출력되는 영역)만 제공되어 라인을 마킹하거나 할 때 좁은 공간에 줄 번호를 비롯한 추가적인 정보가 공존해야 했었지만, 버전 3부터는 거터를 클래스네임의 배열로 지정함으로써 복수로 사용할 수 있게 되었습니다. setGutterMarker, clearGutter 등의 추가적인 메서드를 이용하여 거터단위 마커를 추가 및 제거할 수 있고, HTML 스니펫이 아닌 DOM 노드로 지정됩니다.

수평으로 스크롤할 때 거터가 들쭉날쭉하는 문제가 사라졌으며, fixedGutter 옵션은 삭제되었습니다.

<style>
  /* Define a gutter style */
  .note-gutter { width: 3em; background: cyan; }
</style>
<script>
  // Create an instance with two gutters -- line numbers and notes
  var cm = new CodeMirror(document.body, {
    gutters: ["note-gutter", "CodeMirror-linenumbers"],
    lineNumbers: true
  });
  // Add a note to line 0
  cm.setGutterMarker(0, "note-gutter", document.createTextNode("hi"));
</script>

Event handling

onXYZ 옵션이 대부분 삭제되었습니다. 같은 효과로 이벤트 유형을 식별하는 문자열에서 입수할 수 있습니다. 여러 개의 핸들러를 하나의 이벤트에 할당합니다. 이러한 방식으로 라인 핸들러는 더 쉽게 이벤트를 받습니다. onKeyEventonDragEvent 옵션은 이벤트 핸들러를 후크하는 방식으로 작동하며 이전과 마찬가지로 사용할 수 있습니다.

cm.on("change", function(cm, change) {
  console.log("something changed! (" + change.origin + ")");
});

markText method arguments

markText 메서드는 이제 CSS 클래스명을 인자로 받지 않습니다. 이것은 선택 옵션이며, 옵션 개체를 통하여 전달할 수 있습니다.

// Style first ten lines, and forbid the cursor from entering them
cm.markText({line: 0, ch: 0}, {line: 10, ch: 0}, {
  className: "magic-text",
  inclusiveLeft: true,
  atomic: true
});

Line folding

라인을 숨기는 인터페이스가 제거되었습니다. markText를 이용하여 지금보다 더 유연하고 강력한 방법으로 동일한 작업을 수행하는 데 사용할 수 있습니다. 폴딩 스크립트는 새로운 인터페이스를 사용되도록 견고하게 업데이트되었습니다.

// Fold a range, replacing it with the text "??"
var range = cm.markText({line: 4, ch: 2}, {line: 8, ch: 1}, {
  replacedWith: document.createTextNode("??"),
  // Auto-unfold when cursor moves into the range
  clearOnEnter: true
});
// Get notified when auto-unfolding
CodeMirror.on(range, "clear", function() {
  console.log("boom");
});

Line CSS classes

setLineClass 메서드는 addLineClassremoveLineClass으로 대체되었습니다.

var marked = cm.addLineClass(10, "background", "highlighted-line");
setTimeout(function() {
  cm.removeLineClass(marked, "background", "highlighted-line");
});

Position Properties

모든 포지션 메서드는 화면에 표시하는 위치 개체를 반환합니다. {left, top, bottom, right} 속성을 항상 사용할 수 있으며, 버전 2.x에서 사용하던 {x, y, yBot} 속성을 사용하고 있다면 수정해야 합니다. 포지션 속성을 반환하는 메서드는 cursorCoords, charCoords, coordsChar 그리고 getScrollInfo 입니다.

Bracket matching no longer in core

matchBrackets 옵션은 이제 코어 에디터에 정의되지 않으며 lib/util/matchbrackets.js를 로드합니다.

Mode management

정의된 모드(Mode)의 목록을 반환하던 CodeMirror.listModesCodeMirror.listMIMEs 함수는 이제 없어졌습니다. 대신 CodeMirror.modesCodeMirror.mimeModes로 간단하게 확인할 수 있습니다.

New features

버전 3으로 업그레이드하면 다음과 같은 이점이 있습니다.

  • 양 방향(Bi-directional) 텍스트 지원, 아랍어 또는 히브리어 텍스트를 편집할 수 있습니다.
  • 임의의 행 높이 처리, 에디터 내부에서 높이가 서로다른 글꼴이 사용되는 경우 이제 우아하게 처리합니다.
  • 인라인-위젯 지원, 이 데모문서를 참고하세요.
  • CodeMirror.defineOption으로 사용자 정의 옵션을 지정할 수 있습니다.

이상으로 CodeMirror 버전 3으로의 업그래이드 방법에 대하여 알아보았습니다. 스크롤이 수평으로 이동할때 거터가 들락날락하는 현상이 수정된 거 빼고는 그리 업그레이드의 필요성을 못느끼겠네요. 아랍어를 다룰것도 아니고; 이 기존 2.x 버전은 버그를 수정하는 수준으로 업데이트가 이루어 진다고 하니, 너무 성급하게 3으로 갈아탈 필요는 없어 보입니다.

Comments

Google Analytics에서 제공하는 기본 트래킹 코드는 정적인 페이지에서 사용하기 적합하며 Ajax나 history. pushState를 이용하여 컨텐츠를 갱신하는 동적인 사이트는 필요한 경우 추가적인 작업을 해야 합니다. Analytics는 페이지의 이동 경로와 이벤트 등을 추적할 수 있는 자바스크립트로 작성된 오픈 API를 제공합니다. API를 이용하는 방법은 크게 두 가지로 구분할 수 있는데 _gaq.push로 호출할 메서드와 값을 전달하는 방법과 _gat._getTracker에서 반환하는 인스턴트의 _trackPageview 메서드를 호출하는 방법입니다. 이 API를 Ajax에 응용한 예제는 다음과 같습니다.

// _gaq.push
$.ajax({
  url: "/some.html",
  context: document.body,
  success: function(){
    _gaq.push(['_trackPageview', '/some.html']);
  }
});

// or

// _gat._getTracker
var pageTracker = _gat._getTracker('UA-XXXXX-X');
$.ajax({
  url: "/some.html",
  context: document.body,
  success: function(){
    pageTracker._trackPageview('/some.html');
  }
});

두 방법 모두 같은 결과를 보입니다. 자바스크립트를 이용하여 트래킹하는 경우에 주의해야 할 점 역시 두가지 입니다. _trackPageview는 자동으로 호스트 네임을 걸러주지 않기 때문에 인자로 입력되는 주소는 호스트 정보가 빠진 경로만을 입력해야 합니다. 그리고 페이지 타이틀을 갱신해야 합니다. 그렇지 않으면 Analytics의 페이지 제목 분석기능을 제대로 활용할 수 없게 됩니다. 예를 들어 첫 페이지의 타이틀이 'firejune.com'이면 비동기로 갱신되어 Analytics에 기록된 페이지들이 모두 'firejune.com'이란 타이틀을 가지게 되기 때문이죠. '_trackPageview' 함수는 단순히 document.title의 값을 기록하죠. 다음은 간단한 실용 예를 작성한 것입니다.

var pageTracker = _gat._getTracker('UA-XXXXX-X');
function trackPageview(url, title) {
    url = url.replace(location.protocol + '//' + location.host, '');
    document.title = document.title.split(' : ')[0] + (title && ' : ' + title || '');
    pageTracker._trackPageview(url);
}

function updatePage(el) {
  $.ajax({
    url: el.href,
    context: document.body,
    success: function(){
      trackPageview(el.href, el.innerHTML);
    }
  });
  return false;
}
<a href="/some.html" onclick="return updatePage(this);">Some HTML</a>

최근 작업 중인 벨록스 프로젝트는 애플리케이션 레이아웃이어서 패널과 다이얼로그를 이용하여 컨텐츠를 갱신하기 때문에 다른 방식으로 사용자의 행동을 추적해야 할 필요가 있었습니다. 그래서 속성 편집 패널을 열면, 기존 document.title에 추가로 컨트롤러/액션(' : /files/edit') 이름을 더하여 Analytics에 기록한 후 원래대로 돌리는 방식으로 데이터를 쌓음으로써 사용자의 이동 경로를 조금 더 세밀하게 파악할 수 있었습니다.

Comments