├── README.md
├── worknotes.md
├── demo
├── code.js
├── styles.css
└── index.html
├── LICENSE
├── bookmarks.js
└── source.opml
/README.md:
--------------------------------------------------------------------------------
1 | # bookmarksMenu
2 |
3 | Client JavaScript code that manages a Bookmarks menu.
4 |
5 | ### Demo
6 |
7 | A demo app is included.
8 |
9 | To test the app, choose Add bookmark from the Bookmarks menu.
10 |
11 | It will confirm that you want to add a bookmark to the app itself and if you confirm, it is added.
12 |
13 | It saves your menu to localStorage, restores it when the app is reloaded.
14 |
15 |
--------------------------------------------------------------------------------
/worknotes.md:
--------------------------------------------------------------------------------
1 | #### 1/15/24; 10:32:34 AM by DW
2 |
3 | We're now using the latest Concord.
4 |
5 | And the latest outlinedialog.
6 |
7 | #### 12/8/23; 12:28:51 PM by DW
8 |
9 | The hard-coded styles are a problem if you want to produce a mobile version.
10 |
11 | Add an option to not add styles. FeedLand is going to do it this way.
12 |
13 | #### 9/30/23; 3:46:47 PM by DW
14 |
15 | Increased font size and lineheight.
16 |
17 | Manage concord.handleEvents.
18 |
19 | #### 12/29/22 by DW
20 |
21 | Portable bookmarks. Make it easy to add the feature to any JavaScript app.
22 |
23 |
--------------------------------------------------------------------------------
/demo/code.js:
--------------------------------------------------------------------------------
1 | var appPrefs = {
2 | }
3 | var myBookmarksMenu;
4 |
5 | function getSavedBookmarks () {
6 | if (localStorage.savedBookmarks === undefined) {
7 | return (undefined);
8 | }
9 | else {
10 | return (localStorage.savedBookmarks);
11 | }
12 | }
13 | function saveBookmarks (opmltext) {
14 | localStorage.savedBookmarks = opmltext;
15 | }
16 |
17 | function startup () {
18 | console.log ("startup");
19 |
20 | var options = {
21 | opmltext: getSavedBookmarks (),
22 | saveBookmarks,
23 | idList: "idBookmarksList",
24 | maxMenuItemChars: 20
25 | };
26 |
27 | myBookmarksMenu = new bookMarksMenu (options);
28 | myBookmarksMenu.start ();
29 |
30 | hitCounter ();
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Dave Winer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/demo/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Ubuntu;
3 | font-size: 18px;
4 | background-color: white;
5 | }
6 | .divPageBody {
7 | width: 800px;
8 | margin-top: 30px;
9 | margin-left: auto;
10 | margin-right: auto;
11 | margin-bottom: 400px;
12 | }
13 | .divPageBody p {
14 | line-height: 140%;
15 | }
16 | .hovering {
17 | background-color: whitesmoke;
18 | cursor: pointer;
19 | }
20 | .divUrlInput {
21 | margin-bottom: 30px;
22 | }
23 | .divUrlInput input {
24 | margin-top: 11px;
25 | margin-right: 5px;
26 | height: 32px;
27 | width: 600px;
28 | }
29 | .divUrlInput .btnGo {
30 | height: 32px;
31 | }
32 | .divTitleText {
33 | font-size: 16px;
34 | font-weight: bold;
35 | line-height: 1.25em;
36 | margin-bottom: 10px;
37 | }
38 | .divBodyText {
39 | font-size: 13px;
40 | color: #9e9e9e;
41 | line-height: 18px;
42 | }
43 |
44 | .divBigBox {
45 | height: 300px;
46 | width: 300px;
47 | background-color: gainsboro;
48 | border: 1px dotted gray;
49 | margin-top: 25px;
50 | tabindex: -1;
51 | }
52 |
53 | .divMenubar .dropdown-menu li {
54 | font-size: 14px;
55 | padding-left: 0;
56 | padding-right: 0;
57 | font-weight: normal;
58 | cursor: pointer;
59 | }
60 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Bookmark feature
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
48 |
49 |
50 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/bookmarks.js:
--------------------------------------------------------------------------------
1 | function bookMarksMenu (options) {
2 | console.log ("bookMarksMenu");
3 |
4 | const me = this;
5 | const defaultOptions = {
6 | opmltext: undefined,
7 | saveBookmarks: function () {
8 | },
9 | idList: "idBookmarksList",
10 | maxMenuItemChars: 30,
11 | flAddBookmarkCommand: true,
12 | flCanInsertStyles: true, //12/8/23 by DW
13 | whereToAppend: $("body"), //1/12/24 by DW
14 | editBookmarksText: "Edit bookmarks...", //1/13/24 by DW
15 | getBookmarkTitle: function () {
16 | return (document.title);
17 | },
18 | handleMenuChoice: function (item) { //1/13/24 by DW
19 | console.log ("handleMenuChoice, atts == " + jsonStringify (item));
20 | if (item.url === undefined) {
21 | alertDialog ("Can't open the bookmark because there is no \"url\" attribute.");
22 | }
23 | else {
24 | window.open (item.url);
25 | }
26 | },
27 | isItemChecked: function (item) { //1/13/24 by DW
28 | return (false);
29 | }
30 | };
31 | for (var x in defaultOptions) {
32 | if (options [x] === undefined) {
33 | options [x] = defaultOptions [x];
34 | }
35 | }
36 |
37 | const nowstring = getNowstring ();
38 | const check = "";
39 | const emptyMenu = {
40 | opml: {
41 | head: {
42 | title: "Bookmarks",
43 | dateCreated: nowstring,
44 | dateModified: nowstring
45 | },
46 | body: {
47 | subs: [
48 | {
49 | text: ""
50 | }
51 | ]
52 | }
53 | }
54 | };
55 | var theMenuOutline;
56 |
57 | function getNowstring () {
58 | return (new Date ().toGMTString ());
59 | }
60 | function addAndEditNewBookmark (title, atts) {
61 | editBookmarks (function () {
62 | opFirstSummit ();
63 | opInsert (title, up);
64 | opSetAtts (atts);
65 | opSetOneAtt ("type", "bookmark");
66 | opSetOneAtt ("icon", "bookmark");
67 | opSetOneAtt ("created", getNowstring ());
68 | });
69 | }
70 | function haveBookmarks () { //return true iff there are any bookmarks
71 | const flHaveBookmarks = theMenuOutline.opml.body.subs.length > 0;
72 | return (flHaveBookmarks);
73 | }
74 | function isItemChecked (item) {
75 | return (options.isItemChecked (item));
76 | }
77 |
78 | function buildMenu () {
79 | const theList = $("#" + options.idList);
80 | theList.empty ();
81 |
82 | function addDivider (theList) {
83 | theList.append ($(""));
84 | }
85 | function addBookmarkLevel (listInOutline, listInDom) {
86 | if (listInOutline !== undefined) { //1/12/24 by DW
87 | listInOutline.forEach (function (item) {
88 | var itemtext = trimWhitespace (item.text);
89 | itemtext = maxStringLength (itemtext, options.maxMenuItemChars, false, true);
90 | if (item.subs === undefined) {
91 | if (itemtext == "-") {
92 | addDivider (listInDom);
93 | }
94 | else {
95 | const myCheck = (isItemChecked (item)) ? check : "";
96 | const menuItem = $("" + myCheck + itemtext + "");
97 | listInDom.append (menuItem);
98 | menuItem.click (function () {
99 | options.handleMenuChoice (item);
100 | });
101 | }
102 | }
103 | else {
104 | var liMenuItem = $("");
105 | var ulSubMenu = $("");
106 | listInDom.append (liMenuItem);
107 | addBookmarkLevel (item.subs, ulSubMenu);
108 | liMenuItem.append (ulSubMenu);
109 | }
110 | });
111 | }
112 | }
113 |
114 | if (options.flAddBookmarkCommand) {
115 | const addBookmarkCommand = $("Add bookmark...");
116 | addBookmarkCommand.click (function () {
117 | var title = options.getBookmarkTitle ();
118 | confirmDialog ("Add \"" + title + "\" to the Bookmarks menu?", function () {
119 | const atts = {
120 | url: location.href
121 | };
122 | addAndEditNewBookmark (title, atts);
123 | });
124 | });
125 | theList.append (addBookmarkCommand);
126 | addDivider (theList);
127 | }
128 |
129 | addBookmarkLevel (theMenuOutline.opml.body.subs, theList);
130 |
131 | addDivider (theList);
132 | const editBookmarksCommand = $("" + options.editBookmarksText + "");
133 | editBookmarksCommand.click (function () {
134 | editBookmarks (undefined);
135 | });
136 | theList.append (editBookmarksCommand);
137 | }
138 | function editBookmarks (afterOpenCallback) {
139 | console.log ("editBookmarks");
140 | const styles = (getBoolean (options.flCanInsertStyles)) ? ".divOutlineDialog {width: 400px; left: 50%;}\n" : "";
141 | appPrefs.outlineFontSize = 14;
142 | appPrefs.outlineLineHeight = 20;
143 |
144 | appPrefs.outlineFontSize = 16; //9/30/23 by DW
145 | appPrefs.outlineLineHeight = 26;
146 |
147 | const opmltext = opmlStringify (theMenuOutline);
148 | flEnableBackgroundTasks = false;
149 |
150 | function afterOpen () {
151 | if (afterOpenCallback !== undefined) {
152 | afterOpenCallback ();
153 | }
154 | $(opGetActiveOutliner ()).concord ({ //12/31/22 by DW
155 | callbacks: {
156 | opExpand: function () {
157 | var atts = opGetAtts ();
158 | if (atts.url !== undefined) {
159 | window.open (atts.url);
160 | }
161 | }
162 | }
163 | });
164 | }
165 |
166 | var flRestoreConcordHandleEvents = false; //9/30/23 by DW
167 | if (!concord.handleEvents) {
168 | concord.handleEvents = true;
169 | flRestoreConcordHandleEvents = true;
170 | }
171 |
172 | const outlineDialogOptions = {
173 | title: "Bookmarks",
174 | flReadOnly: false,
175 | whereToAppend: options.whereToAppend, //1/12/24 by DW
176 | divDialogStyles: "divBookmarksDialog",
177 | opmltext,
178 | afterOpenCallback: afterOpen
179 | };
180 | outlineDialog (outlineDialogOptions, function (flSave, opmltext) {
181 | flEnableBackgroundTasks = true;
182 | if (flRestoreConcordHandleEvents) { //9/30/23 by DW
183 | flRestoreConcordHandleEvents = false;
184 | }
185 | if (flSave) {
186 | options.opmltext = opmltext;
187 | saveBookmarks ();
188 | theMenuOutline = opml.parse (opmltext);
189 | buildMenu ();
190 | }
191 | });
192 | }
193 | function saveBookmarks () {
194 | options.saveBookmarks (options.opmltext);
195 | }
196 | function updateMenuOutline (theNewMenuOutline) { //1/13/24 by DW -- a caller outside can add something to the menu
197 | theMenuOutline = theNewMenuOutline;
198 | options.opmltext = opml.stringify (theMenuOutline);
199 | buildMenu ();
200 | }
201 |
202 | me.start = function (callback) {
203 | if (options.opmltext === undefined) {
204 | theMenuOutline = emptyMenu;
205 | options.opmltext = opmlStringify (emptyMenu);
206 | saveBookmarks ();
207 | }
208 | else {
209 | theMenuOutline = opml.parse (options.opmltext);
210 | }
211 | buildMenu ();
212 | };
213 | me.addAndEdit = addAndEditNewBookmark;
214 | me.haveBookmarks = haveBookmarks;
215 | me.updateMenuOutline = updateMenuOutline; //1/13/24 by DW
216 | me.buildMenu = buildMenu; //1/13/24 by DW
217 | me.editBookmarks = editBookmarks; //6/7/24 by DW
218 | }
219 |
--------------------------------------------------------------------------------
/source.opml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
16 |
17 | nodeEditor: bookmarksMenu
18 | Sat, 31 Dec 2022 15:11:23 GMT
19 | Fri, 07 Jun 2024 14:05:58 GMT
20 | Dave Winer
21 | http://davewiner.com/
22 | 1, 3, 10, 19, 20, 29, 30, 32, 33, 34, 67, 74, 77, 89, 90, 91, 102, 103, 104, 105, 106, 107, 109, 111
23 | 49
24 | 131
25 | 686
26 | 1082
27 | 1899
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
--------------------------------------------------------------------------------