├── skypad-og-image.png
├── vendor
├── codeflask
│ ├── codeflask.css
│ └── codeflask.js
├── prism
│ ├── prism.css
│ └── prism.js
├── mui
│ ├── mui.min.js
│ └── mui.min.css
├── zepto
│ └── zepto.min.js
└── skygear
│ └── polyfill.min.js
├── app.css
├── README.md
├── index.html
├── app.js
└── LICENSE
/skypad-og-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skygear-demo/skypad/HEAD/skypad-og-image.png
--------------------------------------------------------------------------------
/vendor/codeflask/codeflask.css:
--------------------------------------------------------------------------------
1 | .CodeFlask{
2 | position:relative;
3 | overflow:hidden;
4 | }
5 |
6 | .CodeFlask__textarea,
7 | .CodeFlask__pre,
8 | code[class*="language-"],
9 | pre[class*="language-"]{
10 | box-sizing:border-box;
11 | position:absolute;
12 | top:0;
13 | left:0;
14 | width:100%;
15 | padding:1rem !important;
16 | border:none;
17 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
18 | font-size:13px;
19 | background:transparent;
20 | white-space:pre-wrap !important;
21 | line-height:1.5em;
22 | word-wrap: break-word;
23 | }
24 |
25 | .CodeFlask__textarea{
26 | border:none;
27 | background:transparent;
28 | outline:none;
29 | resize:none;
30 | opacity:0.4;
31 | color:#fff;
32 | margin:0;
33 | z-index:1;
34 | height:100%;
35 | -webkit-overflow-scrolling: touch;
36 | -webkit-text-fill-color: transparent;
37 | tab-size: 4;
38 | }
39 |
40 | .CodeFlask__pre{
41 | z-index:2;
42 | pointer-events:none;
43 | overflow-y:auto;
44 | margin:0;
45 | min-height:100%;
46 | margin:0 !important;
47 | background:transparent !important;
48 | }
49 |
50 | .CodeFlask__code{
51 | font-size:inherit;
52 | font-family:inherit;
53 | color:inherit;
54 | display:block;
55 | }
56 |
57 | .CodeFlask__is-code {
58 | white-space: pre;
59 | }
60 |
61 | .CodeFlask__textarea_line-numbers {
62 | width: calc(100% - 3.8em);
63 | margin-left: 3.8em;
64 | padding-left: 0px !important;
65 | }
66 |
67 | .CodeFlask__pre_line-numbers {
68 | padding-left: 3.8em !important;
69 | }
70 |
--------------------------------------------------------------------------------
/app.css:
--------------------------------------------------------------------------------
1 | html,body {
2 | height:100%;
3 | }
4 |
5 | body {
6 | background-color: #111;
7 | }
8 |
9 | div#skypad-title {
10 | position: fixed;
11 | }
12 |
13 | div#skypad-title input {
14 | position: fixed;
15 | border: 0;
16 | height: 30px;
17 | width: 100%;
18 | font-family: monospace;
19 | color: #ecf0f1;
20 | margin: 0;
21 | padding: 6px 10px;
22 | top: 0;
23 | line-height: 24px;
24 | font-weight: 600;
25 | background-color: #2c3e50;
26 | }
27 |
28 | div#skypad-title input:focus {
29 | background: #2980b9;
30 | }
31 |
32 | div#skypad-title input::-webkit-input-placeholder { color: #ccc; }
33 | div#skypad-title input::-moz-placeholder {color: #ccc; }
34 | div#skypad-title input:-ms-input-placeholder { color: #ccc; }
35 | div#skypad-title input:-o-input-placeholder { color: #ccc; }
36 |
37 |
38 | div#skypad-display {
39 | position: fixed;
40 | border: 0;
41 | padding: 20px;
42 | margin-top: 42px;
43 | width: 100%;
44 | top: 0;
45 | color: #fff;
46 | background-color: #111;
47 | font-family: monospace;
48 | height: calc(100% - 130px);
49 | }
50 |
51 | textarea,input:focus {
52 | outline: none;
53 | }
54 | .CodeFlask__pre {
55 | bottom: 0; /* Fix the scrolling problem*/
56 | }
57 |
58 | #credits {
59 | color: #999;
60 | }
61 |
62 | #credits a {
63 | color: #777;
64 | }
65 |
66 | #share-bar {
67 | border: 0;
68 | position: fixed;
69 | margin: 0;
70 | width:100%;
71 | bottom: 0;
72 | height: 40px;
73 | z-index: 20;
74 | color: #fff;
75 | background-color: #333;
76 | font-family: monospace;
77 | padding: 10px;
78 | display: none;
79 | }
80 |
81 | #side-bar {
82 | float;right;
83 | position: fixed;
84 | width: 200px;
85 | max-width: 30%;
86 | top: 42px;
87 | bottom: 0;
88 | color: #fff;
89 | right: 0;
90 | background: #2f3640;
91 | padding: 10px;
92 | border-left: 2px #456184 solid;
93 | transition: 1s;
94 | }
95 |
96 |
97 | #note-list {
98 | list-style-type: none;
99 | }
100 |
101 | #note-list li {
102 | padding-left: 1em;
103 | text-indent: -1em;
104 | text-overflow: ellipsis;
105 | white-space: nowrap;
106 | overflow: hidden;
107 | width: 120px;
108 | }
109 |
110 | #note-list li:before {
111 | content: "📝";
112 | padding-right: 5px;
113 | }
114 |
115 | @media (max-width: 500px ){
116 | #side-bar {
117 | right: -200px;
118 | }
119 | }
120 |
121 | .code-highlight-selector {
122 | float: right;
123 | display: none;
124 | }
125 |
126 | .code-highlight-selector button {
127 | font-size: 12px;
128 | }
129 |
130 | .code-highlight-selector ul {
131 | font-size: 12px;
132 | }
133 |
134 | .code-highlight-selector ul li a {
135 | cursor: pointer;
136 | }
137 | @media (max-height: 400px ){
138 | #share-bar {
139 | visibility: hidden;
140 | }
141 | div#skypad-display {
142 | height: 100%;
143 | }
144 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Skypad
2 |
3 | Simple, real time collaborative notepad on the cloud. First version quickly built in an hour with [Skygear](https://skygear.io)
4 |
5 | #### Try now:
6 | * Create a new note: https://skygear-demo.github.io/skypad
7 |
8 | 
9 |
10 | ## Non-CDN version
11 | * This version's libraries are not served from any CDN. Suitable for independent deployment.
12 |
13 | ---
14 |
15 | # Features
16 |
17 | * Handy - Create a pad instantly.
18 | * Simple UI - Neat, undistracting and responsive.
19 | * Easy Sharing - Share by URL, Twitter or FB.
20 | * Collaboration - Real time sync across all platforms.
21 | * Auto Save - Changes saved on the cloud automatically.
22 | * Syntax Highligting - Support JavaScript, C, HTML and CSS. More comming.
23 |
24 | [Try it here](https://skygear-demo.github.io/skypad)
25 |
26 | # Develop
27 |
28 | * Edit the `config` dict at `app.js`
29 |
30 | ```javascript
31 | const config = {
32 | baseURL: "https://yoursite.com/", // To help generate a correct sharing URL
33 | skygearAPIEndpoint: "https://skypad.skygeario.com/", // API Endpoint
34 | skygearAPIKey: "xxxxc613xxxx4227xxxx6114a401xxxx", // API Key
35 | writerUser: "username", // the default user for creating app
36 | writerPass: "password" // the default user password for creating app
37 | }
38 | ```
39 |
40 | * Sign up at [Skygear](https://portal.skygear.io/signup) to obtain the API Endpoint and API Key.
41 | * Use `signupWithUserName` to create your own writerUser at Skygear.
42 |
43 |
44 | # Deploy
45 |
46 | This app can be deployed on localhost, AWS s3, Skygear hosting, GitHub Page or other static hosts.
47 |
48 | * These files are required to deploy:
49 | * `index.html` - Main layout
50 | * `app.js` - Main app logic
51 | * `app.css` - CSS styling
52 | * `/vendor` - Required external library files
53 |
54 | # Versions
55 |
56 | You can view the previous version as tags, e.g. `v0.1`, `v0.2`
57 |
58 | * `mvp` Quick first usable version
59 | * `v0.1` Social sharing and UI fixed
60 | * `v0.2` Code highlighting
61 | * `v0.3` Create new pad and pad title
62 | * `v1.0` List of my notes (Automatically managed)
63 | * `v1.1` **(Upcoming)** Private notes
64 |
65 | # Feedback and Contribution
66 |
67 | Feel free to open any issue and PR. Contact at hello@skygear.io
68 |
69 | ### Credits & Thanks
70 |
71 | * Code highlighting powered by [CodeFlask](https://github.com/kazzkiq/CodeFlask.js) by [kazzkiq](https://twitter.com/kazzkiq). That's awesome.
72 |
73 | * CSS and base style: [MUI](https://www.muicss.com/) a lightweight material framework.
74 |
75 | * I didn't use complex JS framework but [zepto.js](http://zeptojs.com/) for quick hacks and keep it lightweight.
76 |
77 | ### Mentions
78 |
79 | * [Discussion on HN](https://news.ycombinator.com/item?id=14864089)
80 |
81 |
82 |
83 | ### About Skygear
84 |
85 | [Skygear](https://skygear.io) is a backend for building real-time and cloud-based web/mobile app. Skypad is a perfect simple usecase.
86 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Skypad - powered by Skygear
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | codeflask
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
50 |
51 |
52 |
56 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/vendor/prism/prism.css:
--------------------------------------------------------------------------------
1 | /* http://prismjs.com/download.html?themes=prism-twilight&languages=markup+css+clike+javascript */
2 | /**
3 | * prism.js Twilight theme
4 | * Based (more or less) on the Twilight theme originally of Textmate fame.
5 | * @author Remy Bach
6 | */
7 | code[class*="language-"],
8 | pre[class*="language-"] {
9 | color: white;
10 | background: none;
11 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
12 | text-align: left;
13 | text-shadow: 0 -.1em .2em black;
14 | white-space: pre;
15 | word-spacing: normal;
16 | word-break: normal;
17 | word-wrap: normal;
18 | line-height: 1.5;
19 |
20 | -moz-tab-size: 4;
21 | -o-tab-size: 4;
22 | tab-size: 4;
23 |
24 | -webkit-hyphens: none;
25 | -moz-hyphens: none;
26 | -ms-hyphens: none;
27 | hyphens: none;
28 | }
29 |
30 | pre[class*="language-"],
31 | :not(pre) > code[class*="language-"] {
32 | background: hsl(0, 0%, 8%); /* #141414 */
33 | }
34 |
35 | /* Code blocks */
36 | pre[class*="language-"] {
37 | border-radius: .5em;
38 | border: .3em solid hsl(0, 0%, 33%); /* #282A2B */
39 | box-shadow: 1px 1px .5em black inset;
40 | margin: .5em 0;
41 | overflow: auto;
42 | padding: 1em;
43 | }
44 |
45 | pre[class*="language-"]::-moz-selection {
46 | /* Firefox */
47 | background: hsl(200, 4%, 16%); /* #282A2B */
48 | }
49 |
50 | pre[class*="language-"]::selection {
51 | /* Safari */
52 | background: hsl(200, 4%, 16%); /* #282A2B */
53 | }
54 |
55 | /* Text Selection colour */
56 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
57 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
58 | text-shadow: none;
59 | background: hsla(0, 0%, 93%, 0.15); /* #EDEDED */
60 | }
61 |
62 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
63 | code[class*="language-"]::selection, code[class*="language-"] ::selection {
64 | text-shadow: none;
65 | background: hsla(0, 0%, 93%, 0.15); /* #EDEDED */
66 | }
67 |
68 | /* Inline code */
69 | :not(pre) > code[class*="language-"] {
70 | border-radius: .3em;
71 | border: .13em solid hsl(0, 0%, 33%); /* #545454 */
72 | box-shadow: 1px 1px .3em -.1em black inset;
73 | padding: .15em .2em .05em;
74 | white-space: normal;
75 | }
76 |
77 | .token.comment,
78 | .token.prolog,
79 | .token.doctype,
80 | .token.cdata {
81 | color: hsl(0, 0%, 47%); /* #777777 */
82 | }
83 |
84 | .token.punctuation {
85 | opacity: .7;
86 | }
87 |
88 | .namespace {
89 | opacity: .7;
90 | }
91 |
92 | .token.tag,
93 | .token.boolean,
94 | .token.number,
95 | .token.deleted {
96 | color: hsl(14, 58%, 55%); /* #CF6A4C */
97 | }
98 |
99 | .token.keyword,
100 | .token.property,
101 | .token.selector,
102 | .token.constant,
103 | .token.symbol,
104 | .token.builtin {
105 | color: hsl(53, 89%, 79%); /* #F9EE98 */
106 | }
107 |
108 | .token.attr-name,
109 | .token.attr-value,
110 | .token.string,
111 | .token.char,
112 | .token.operator,
113 | .token.entity,
114 | .token.url,
115 | .language-css .token.string,
116 | .style .token.string,
117 | .token.variable,
118 | .token.inserted {
119 | color: hsl(76, 21%, 52%); /* #8F9D6A */
120 | }
121 |
122 | .token.atrule {
123 | color: hsl(218, 22%, 55%); /* #7587A6 */
124 | }
125 |
126 | .token.regex,
127 | .token.important {
128 | color: hsl(42, 75%, 65%); /* #E9C062 */
129 | }
130 |
131 | .token.important,
132 | .token.bold {
133 | font-weight: bold;
134 | }
135 | .token.italic {
136 | font-style: italic;
137 | }
138 |
139 | .token.entity {
140 | cursor: help;
141 | }
142 |
143 | pre[data-line] {
144 | padding: 1em 0 1em 3em;
145 | position: relative;
146 | }
147 |
148 | /* Markup */
149 | .language-markup .token.tag,
150 | .language-markup .token.attr-name,
151 | .language-markup .token.punctuation {
152 | color: hsl(33, 33%, 52%); /* #AC885B */
153 | }
154 |
155 | /* Make the tokens sit above the line highlight so the colours don't look faded. */
156 | .token {
157 | position: relative;
158 | z-index: 1;
159 | }
160 |
161 | .line-highlight {
162 | background: hsla(0, 0%, 33%, 0.25); /* #545454 */
163 | background: linear-gradient(to right, hsla(0, 0%, 33%, .1) 70%, hsla(0, 0%, 33%, 0)); /* #545454 */
164 | border-bottom: 1px dashed hsl(0, 0%, 33%); /* #545454 */
165 | border-top: 1px dashed hsl(0, 0%, 33%); /* #545454 */
166 | left: 0;
167 | line-height: inherit;
168 | margin-top: 0.75em; /* Same as .prism’s padding-top */
169 | padding: inherit 0;
170 | pointer-events: none;
171 | position: absolute;
172 | right: 0;
173 | white-space: pre;
174 | z-index: 0;
175 | }
176 |
177 | .line-highlight:before,
178 | .line-highlight[data-end]:after {
179 | background-color: hsl(215, 15%, 59%); /* #8794A6 */
180 | border-radius: 999px;
181 | box-shadow: 0 1px white;
182 | color: hsl(24, 20%, 95%); /* #F5F2F0 */
183 | content: attr(data-start);
184 | font: bold 65%/1.5 sans-serif;
185 | left: .6em;
186 | min-width: 1em;
187 | padding: 0 .5em;
188 | position: absolute;
189 | text-align: center;
190 | text-shadow: none;
191 | top: .4em;
192 | vertical-align: .3em;
193 | }
194 |
195 | .line-highlight[data-end]:after {
196 | bottom: .4em;
197 | content: attr(data-end);
198 | top: auto;
199 | }
200 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | baseURL: "https://skygear-demo.github.io/skypad",
3 | skygearAPIEndpoint: "https://skypad.skygeario.com/", // trailing slash is required
4 | skygearAPIKey: "ac59c61350b14227ad5a6114a40176ba",
5 | writerUser: "writer",
6 | writerPass: "writerpass"
7 | }
8 |
9 | let skygearPad = $("div#skypad-display");
10 | let skygearTitle = $("div#skypad-title input");
11 | let noteList = $("#note-list");
12 | let codeHighlightSelector = $(".code-highlight-selector");
13 | const Note = skygear.Record.extend("Note");
14 | let thisNote = null;
15 | let ramdomToken = Math.random().toString(36).substring(7); // for distinguishing tabs
16 |
17 | let cachedCode = '';
18 |
19 | function logoutDefaultUser(callback) {
20 | if (skygear.auth.currentUser && skygear.auth.currentUser.username == config.writerUser) {
21 | skygear.auth.logout().then(
22 | skygear.auth.signupAnonymously().then(
23 | function(user) {
24 | callback(user);
25 | }
26 | )
27 | );
28 | }
29 | }
30 |
31 | function createNoteListItem (note) {
32 | let li = document.createElement('li');
33 |
34 | let item = document.createElement('a');
35 | let noteURL = config.baseURL+"#"+note._id;
36 | item.textContent = note.title;
37 | item.setAttribute('href', noteURL);
38 |
39 | li.append(item)
40 | return li;
41 | }
42 |
43 | function createNote() {
44 | var note = new Note({
45 | title:"",
46 | content: "",
47 | viewcount: 0
48 | });
49 |
50 | note.setPublicReadWriteAccess();
51 | return skygear.publicDB.save(note);
52 | }
53 |
54 | function saveNote(content) {
55 | thisNote['content'] = content;
56 |
57 | var toSaveNote = new Note({
58 | _id: thisNote.id,
59 | content: thisNote.content
60 | });
61 | skygear.publicDB.save(toSaveNote);
62 | }
63 |
64 | function updateTitleUI(title) {
65 | document.title = "Skypad - "+ title + " ";
66 | }
67 |
68 | function saveTitle(title) {
69 | thisNote['title'] = title;
70 |
71 | var toSaveNote = new Note({
72 | _id: thisNote.id,
73 | title: thisNote.title
74 | });
75 | skygear.publicDB.save(toSaveNote);
76 | }
77 |
78 | function increaseNoteCount(note) {
79 | var viewCount = note.viewcount;
80 | viewCount = (viewCount == undefined)? 0 :viewCount;
81 | var toSaveNote = new Note({
82 | _id: note.id,
83 | viewcount: viewCount + 1
84 | });
85 | skygear.publicDB.save(toSaveNote);
86 | }
87 |
88 | function fireSyncTitle(title) {
89 | if (thisNote) {
90 | skygear.pubsub.publish('note/' + thisNote._id, {
91 | token: ramdomToken,
92 | title: title
93 | });
94 | updateTitleUI(title);
95 | saveTitle(title);
96 | }
97 | }
98 |
99 |
100 | function fireSync(content) {
101 | if (thisNote) {
102 | skygear.pubsub.publish('note/' + thisNote._id, {
103 | token: ramdomToken,
104 | content: content
105 | });
106 | saveNote(content);
107 | }
108 | }
109 |
110 | function sync(data) {
111 | if (data.content !== undefined) {
112 | if (data.token === ramdomToken) {
113 | return;
114 | } else {
115 | cachedCode = data.content;
116 | flask.update(data.content);
117 | }
118 | } else {
119 | syncTitle(data);
120 | }
121 | }
122 |
123 | function syncTitle(data) {
124 | loadUserNotes(skygear.auth.currentUser._id);
125 | if (data.token === ramdomToken) {
126 | return;
127 | } else {
128 | skygearTitle.val(data.title);
129 | }
130 | }
131 |
132 | function loadUserNotes(userId) { // User created notes, not included notes user has edited
133 | const query = new skygear.Query(Note);
134 | query.equalTo('_created_by', userId);
135 | query.addDescending('_created_at');
136 |
137 | skygear.publicDB.query(query)
138 | .then(function(records) {
139 | if (records.length == 0) {
140 | console.log("No Record for " + userId);
141 | return;
142 | }
143 |
144 | console.log(`Found ${records.length} notes for user.`);
145 | noteList.empty();
146 | for (let note of records) {
147 | let noteItem = createNoteListItem(note);
148 | noteList.append(noteItem);
149 | }
150 |
151 | }, function(error) {
152 | console.error(error);
153 | });
154 | }
155 |
156 | function loadExistingNote(noteId) {
157 | const query = new skygear.Query(Note);
158 | query.equalTo('_id', noteId);
159 |
160 | skygear.publicDB.query(query)
161 | .then(function(records) {
162 | if (records.length == 0) {
163 | console.log("No Record for " + noteId);
164 | flask.update(
165 | "// ❌ 404 not found.\n\nYou can create a new pad at " + config.baseURL
166 | );
167 | return;
168 | }
169 |
170 | const record = records[0];
171 | var noteURL = config.baseURL+"#"+record._id;
172 |
173 | increaseNoteCount(record);
174 |
175 | skygear.pubsub.on('note/' + record._id, sync);
176 | thisNote = record;
177 |
178 | flask.update(record.content);
179 | displaySharingOptions(noteURL);
180 |
181 | var titleText = record.title? record.title : "untitled";
182 | skygearTitle.val(titleText);
183 | updateTitleUI(titleText);
184 |
185 | }, function(error) {
186 | console.error(error);
187 | });
188 | }
189 |
190 | function initNote (user) {
191 | var noteId = getHashFromURL();
192 | codeHighlightSelector.show();
193 | if (noteId) {
194 | loadExistingNote(noteId);
195 | loadUserNotes(skygear.auth.currentUser._id)
196 | } else {
197 | createNote().then(function(note) {
198 | var noteURL = config.baseURL + "#" + note._id;
199 |
200 | thisNote = note;
201 | skygear.pubsub.on('note/' + note._id, sync);
202 | window.location.hash = note._id;
203 |
204 | var initTitle = 'untitled';
205 | var initContent = '// Welcome to Skypad!' +
206 | '\n// 😎 Share with this URL ' + noteURL +
207 | '\n\n// Start typing.';
208 |
209 | initContent += '\n\n// Now supports Syntax highlight. Uncomment the following lines to try:'+
210 | '\n'+
211 | '\n/*'+
212 | '\nfunction hello(name) {'+
213 | '\n console.log(`hello ${name}!`);'+
214 | '\n}'+
215 | '\n'+
216 | '\nhello(\'world\');'+
217 | '\n*/';
218 | flask.update(initContent);
219 | skygearTitle.val(initTitle);
220 | displaySharingOptions(noteURL)
221 | fireSync(initContent);
222 | fireSyncTitle(initTitle);
223 | loadUserNotes(skygear.auth.currentUser._id);
224 | });
225 | }
226 | }
227 |
228 | function configSkygear(apiEndpoint, apiKey) {
229 | skygear.config({
230 | 'endPoint': apiEndpoint,
231 | 'apiKey': apiKey,
232 | }).then(function() {
233 | if (skygear.auth.currentUser) {
234 | logoutDefaultUser(initNote);
235 | initNote(skygear.auth.currentUser);
236 | } else {
237 | skygear.auth.signupAnonymously().then(function(user) {
238 | initNote(user);
239 | });
240 | }
241 | }, function(error) {
242 | console.error(error.message);
243 | });
244 | }
245 |
246 | function getHashFromURL() {
247 | var readNote = null;
248 | if (window.location.hash) {
249 | // Fragment exists
250 | readNote = window.location.hash.substr(1);
251 | }
252 | return readNote;
253 | }
254 |
255 | function displaySharingOptions(noteURL) {
256 | var shareBarEl = $("#share-bar");
257 |
258 | var shareURLEl = $("#share-url");
259 | var shareTwitterEl = $("#share-twitter");
260 | var shareFBEl = $("#share-fb");
261 |
262 | shareURLEl[0].href = noteURL;
263 | noteURL=noteURL.replace("#","%23");
264 | shareTwitterEl[0].href = shareTwitterEl[0].href.replace('{{note-url}}',noteURL);
265 | shareFBEl[0].href = shareFBEl[0].href.replace('{{note-url}}',noteURL);
266 |
267 | shareBarEl.show();
268 | }
269 |
270 | $().ready(function() {
271 | configSkygear(config.skygearAPIEndpoint, config.skygearAPIKey);
272 | })
273 |
274 | // Code highlight
275 | var flask = new CodeFlask;
276 | flask.run('#skypad-display', { language: 'javascript'});
277 | $('#code-highlight-caption').text("JavaScript");
278 | flask.onUpdate(function(code) {
279 | if (cachedCode !== code) {
280 | cachedCode = code;
281 | fireSync(code);
282 | }
283 |
284 | skygearTitle.on("keyup change blur", function(e){
285 | var title = skygearTitle.val();
286 | fireSyncTitle(title);
287 | })
288 | });
289 |
290 | $(".code-highlight-selector ul li a").on("click touch", function(e) {
291 | var langChosen = e.target.dataset.lang;
292 | flask.run('#skypad-display', { language: langChosen});
293 | langChosen = (langChosen =="clike")? "C" : langChosen;
294 | $('#code-highlight-caption').text(langChosen);
295 | flask.onUpdate(function(code) {
296 | if (cachedCode !== code) {
297 | cachedCode = code;
298 | fireSync(code);
299 | }
300 | });
301 | })
302 |
--------------------------------------------------------------------------------
/vendor/codeflask/codeflask.js:
--------------------------------------------------------------------------------
1 | function CodeFlask(indent) {
2 | this.indent = indent || " ";
3 | this.docroot = document;
4 | }
5 |
6 | CodeFlask.isString = function(x) {
7 | return Object.prototype.toString.call(x) === "[object String]";
8 | }
9 |
10 | CodeFlask.prototype.run = function(selector, opts) {
11 | var target = CodeFlask.isString(selector) ? this.docroot.querySelectorAll(selector) : [selector];
12 |
13 | if(target.length > 1) {
14 | throw 'CodeFlask.js ERROR: run() expects only one element, ' +
15 | target.length + ' given. Use .runAll() instead.';
16 | } else {
17 | this.scaffold(target[0], false, opts);
18 | }
19 | }
20 |
21 | CodeFlask.prototype.runAll = function(selector, opts) {
22 | // Remove update API for bulk rendering
23 | this.update = null;
24 | this.onUpdate = null;
25 |
26 | var target = CodeFlask.isString(selector) ? this.docroot.querySelectorAll(selector) : selector;
27 |
28 | var i;
29 | for(i=0; i < target.length; i++) {
30 | this.scaffold(target[i], true, opts);
31 | }
32 |
33 | // Add the MutationObserver below for each one of the textAreas so we can listen
34 | // to when the dir attribute has been changed and also return the placeholder
35 | // dir attribute with it so it reflects the changes made to the textarea.
36 | var textAreas = this.docroot.getElementsByClassName("CodeFlask__textarea");
37 | for(var i = 0; i < textAreas.length; i++)
38 | {
39 | window.MutationObserver = window.MutationObserver
40 | || window.WebKitMutationObserver
41 | || window.MozMutationObserver;
42 |
43 | var target = textAreas[i];
44 |
45 | observer = new MutationObserver(function(mutation) {
46 | var textAreas = this.docroot.getElementsByClassName("CodeFlask__textarea");
47 | for(var i = 0; i < textAreas.length; i++)
48 | {
49 | // If the text direction values are different set them
50 | if(textAreas[i].nextSibling.getAttribute("dir") != textAreas[i].getAttribute("dir")){
51 | textAreas[i].nextSibling.setAttribute("dir", textAreas[i].getAttribute("dir"));
52 | }
53 | }
54 | }),
55 | config = {
56 | attributes: true,
57 | attributeFilter : ['dir']
58 | };
59 | observer.observe(target, config);
60 | }
61 | }
62 |
63 | CodeFlask.prototype.scaffold = function(target, isMultiple, opts) {
64 | var textarea = document.createElement('TEXTAREA'),
65 | highlightPre = document.createElement('PRE'),
66 | highlightCode = document.createElement('CODE'),
67 | initialCode = target.textContent,
68 | lang;
69 |
70 | if(opts && !opts.enableAutocorrect)
71 | {
72 | // disable autocorrect and spellcheck features
73 | textarea.setAttribute('spellcheck', 'false');
74 | textarea.setAttribute('autocapitalize', 'off');
75 | textarea.setAttribute('autocomplete', 'off');
76 | textarea.setAttribute('autocorrect', 'off');
77 | }
78 |
79 | if(opts)
80 | {
81 | lang = this.handleLanguage(opts.language);
82 | }
83 |
84 | this.defaultLanguage = target.dataset.language || lang || 'markup';
85 |
86 |
87 | // Prevent these vars from being refreshed when rendering multiple
88 | // instances
89 | if(!isMultiple) {
90 | this.textarea = textarea;
91 | this.highlightCode = highlightCode;
92 | }
93 |
94 | target.classList.add('CodeFlask');
95 | textarea.classList.add('CodeFlask__textarea');
96 | highlightPre.classList.add('CodeFlask__pre');
97 | highlightCode.classList.add('CodeFlask__code');
98 | highlightCode.classList.add('language-' + this.defaultLanguage);
99 |
100 | // Fixing iOS "drunk-text" issue
101 | if(/iPad|iPhone|iPod/.test(navigator.platform)) {
102 | highlightCode.style.paddingLeft = '3px';
103 | }
104 |
105 | // If RTL add the text-align attribute
106 | if(opts.rtl == true){
107 | textarea.setAttribute("dir", "rtl")
108 | highlightPre.setAttribute("dir", "rtl")
109 | }
110 |
111 | if(opts.lineNumbers) {
112 | highlightPre.classList.add('line-numbers');
113 | highlightPre.classList.add('CodeFlask__pre_line-numbers');
114 | textarea.classList.add('CodeFlask__textarea_line-numbers')
115 | }
116 |
117 | // Appending editor elements to DOM
118 | target.innerHTML = '';
119 | target.appendChild(textarea);
120 | target.appendChild(highlightPre);
121 | highlightPre.appendChild(highlightCode);
122 |
123 | // Render initial code inside tag
124 | textarea.value = initialCode;
125 | this.renderOutput(highlightCode, textarea);
126 |
127 | this.highlight(highlightCode);
128 |
129 | this.handleInput(textarea, highlightCode, highlightPre);
130 | this.handleScroll(textarea, highlightPre);
131 |
132 | }
133 |
134 | CodeFlask.prototype.renderOutput = function(highlightCode, input) {
135 | highlightCode.innerHTML = input.value.replace(/&/g, "&")
136 | .replace(//g, ">") + "\n";
138 | }
139 |
140 | CodeFlask.prototype.handleInput = function(textarea, highlightCode, highlightPre) {
141 | var self = this,
142 | input,
143 | selStartPos,
144 | inputVal,
145 | roundedScroll,
146 | currentLineStart,
147 | indentLength;
148 |
149 | textarea.addEventListener('input', function(e) {
150 | input = this;
151 |
152 | input.value = input.value.replace(/\t/g, self.indent);
153 |
154 | self.renderOutput(highlightCode, input);
155 |
156 | self.highlight(highlightCode);
157 | });
158 |
159 | textarea.addEventListener('keydown', function(e) {
160 | input = this,
161 | selStartPos = input.selectionStart,
162 | inputVal = input.value;
163 | currentLineStart = selStartPos - input.value.substr(0, selStartPos).split("\n").pop().length;
164 |
165 | // If tab pressed, indent
166 | if (e.keyCode === 9) {
167 | e.preventDefault();
168 |
169 | // Allow shift-tab
170 | if (e.shiftKey) {
171 | indentLength = self.indent.length;
172 |
173 | // If the current line begins with the indent, unindent
174 | if (inputVal.substring(currentLineStart, currentLineStart + indentLength) == self.indent) {
175 | input.value = inputVal.substring(0, currentLineStart) +
176 | inputVal.substring(currentLineStart + indentLength, input.value.length);
177 | input.selectionStart = selStartPos - self.indent.length;
178 | input.selectionEnd = selStartPos - self.indent.length;
179 | }
180 | } else {
181 | input.value = inputVal.substring(0, selStartPos) + self.indent +
182 | inputVal.substring(selStartPos, input.value.length);
183 | input.selectionStart = selStartPos + self.indent.length;
184 | input.selectionEnd = selStartPos + self.indent.length;
185 | }
186 |
187 | highlightCode.innerHTML = input.value.replace(/&/g, "&")
188 | .replace(//g, ">") + "\n";
190 | self.highlight(highlightCode);
191 | }
192 | });
193 | }
194 |
195 | CodeFlask.prototype.handleScroll = function(textarea, highlightPre) {
196 | textarea.addEventListener('scroll', function(){
197 |
198 | roundedScroll = Math.floor(this.scrollTop);
199 |
200 | // Fixes issue of desync text on mouse wheel, fuck Firefox.
201 | if(navigator.userAgent.toLowerCase().indexOf('firefox') < 0) {
202 | this.scrollTop = roundedScroll;
203 | }
204 |
205 | highlightPre.style.top = "-" + roundedScroll + "px";
206 | });
207 | }
208 |
209 | CodeFlask.prototype.handleLanguage = function(lang) {
210 | if(lang.match(/html|xml|xhtml|svg/)) {
211 | return 'markup';
212 | } else if(lang.match(/js/)) {
213 | return 'javascript';
214 | } else {
215 | return lang;
216 | }
217 | }
218 |
219 | CodeFlask.prototype.onUpdate = function(cb) {
220 | if(typeof(cb) == "function") {
221 | // this.textarea.addEventListener('input', function(e) {
222 | // cb(this.value);
223 | // });
224 | var area = this.textarea;
225 | if (area.addEventListener) {
226 | area.addEventListener('input', function(e) {
227 | // event handling code for sane browsers
228 | cb(this.value);
229 | }, false);
230 | } else if (area.attachEvent) {
231 | area.attachEvent('onpropertychange', function(e) {
232 | cb(this.value);
233 | // IE-specific event handling code
234 | });
235 | }
236 |
237 | }else{
238 | throw 'CodeFlask.js ERROR: onUpdate() expects function, ' +
239 | typeof(cb) + ' given instead.';
240 | }
241 | }
242 |
243 | CodeFlask.prototype.update = function(string) {
244 | var evt = document.createEvent("HTMLEvents");
245 |
246 | this.textarea.value = string;
247 | this.renderOutput(this.highlightCode, this.textarea);
248 | this.highlight(this.highlightCode);
249 |
250 | evt.initEvent("input", false, true);
251 | this.textarea.dispatchEvent(evt);
252 | }
253 |
254 | CodeFlask.prototype.highlight = function(highlightCode) {
255 | Prism.highlightElement(highlightCode);
256 | }
257 |
--------------------------------------------------------------------------------
/vendor/prism/prism.js:
--------------------------------------------------------------------------------
1 | /* http://prismjs.com/download.html?themes=prism-twilight&languages=markup+css+clike+javascript */
2 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={manual:_self.Prism&&_self.Prism.manual,util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(w instanceof s)){h.lastIndex=0;var _=h.exec(w),P=1;if(!_&&m&&b!=t.length-1){if(h.lastIndex=k,_=h.exec(e),!_)break;for(var A=_.index+(d?_[1].length:0),j=_.index+_[0].length,x=b,O=k,S=t.length;S>x&&(j>O||!t[x].type&&!t[x-1].greedy);++x)O+=t[x].length,A>=O&&(++b,k=O);if(t[b]instanceof s||t[x-1].greedy)continue;P=x-b,w=e.slice(k,O),_.index-=k}if(_){d&&(p=_[1].length);var A=_.index+p,_=_[0].slice(p),j=A+_.length,N=w.slice(0,A),C=w.slice(j),E=[b,P];N&&(++b,k+=N.length,E.push(N));var L=new s(u,f?n.tokenize(_,f):_,y,_,m);if(E.push(L),C&&E.push(C),Array.prototype.splice.apply(t,E),1!=P&&n.matchGrammar(e,t,a,b,k,!0,u),l)break}else if(l)break}}}}},tokenize:function(e,t){var a=[e],r=t.rest;if(r){for(var i in r)t[i]=r[i];delete t.rest}return n.matchGrammar(e,a,t,0,0,!1),a},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var i={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}n.hooks.run("wrap",i);var o=Object.keys(i.attributes).map(function(e){return e+'="'+(i.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+(o?" "+o:"")+">"+i.content+""+i.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,i=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),i&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,!document.addEventListener||n.manual||r.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
3 | Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\s\S])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\s\S]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup;
4 | Prism.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:{pattern:/("|')(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/(