├── .DS_Store
├── Example.sketch
├── Images
├── Screen Shot 2019-01-28 at 2.59.21 PM.png
├── Screen Shot 2019-01-28 at 2.59.39 PM.png
├── Screen Shot 2019-01-28 at 3.00.03 PM.png
├── Screen Shot 2019-01-28 at 3.00.10 PM.png
├── Screen Shot 2019-01-28 at 3.01.17 PM.png
├── Sketch Arboards1.png
├── Sketch Artboards.png
├── Sketch Options.png
└── Sketch Options1.png
├── README.md
└── Table of Contents.sketchplugin
└── Contents
├── Resources
└── World.pdf
└── Sketch
├── library.cocoascript
├── manifest.json
└── script.cocoascript
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youwenliang/Table-of-Contents/1b030f2dab91ee446b2877bcde640b110513be77/.DS_Store
--------------------------------------------------------------------------------
/Example.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youwenliang/Table-of-Contents/1b030f2dab91ee446b2877bcde640b110513be77/Example.sketch
--------------------------------------------------------------------------------
/Images/Screen Shot 2019-01-28 at 2.59.21 PM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youwenliang/Table-of-Contents/1b030f2dab91ee446b2877bcde640b110513be77/Images/Screen Shot 2019-01-28 at 2.59.21 PM.png
--------------------------------------------------------------------------------
/Images/Screen Shot 2019-01-28 at 2.59.39 PM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youwenliang/Table-of-Contents/1b030f2dab91ee446b2877bcde640b110513be77/Images/Screen Shot 2019-01-28 at 2.59.39 PM.png
--------------------------------------------------------------------------------
/Images/Screen Shot 2019-01-28 at 3.00.03 PM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youwenliang/Table-of-Contents/1b030f2dab91ee446b2877bcde640b110513be77/Images/Screen Shot 2019-01-28 at 3.00.03 PM.png
--------------------------------------------------------------------------------
/Images/Screen Shot 2019-01-28 at 3.00.10 PM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youwenliang/Table-of-Contents/1b030f2dab91ee446b2877bcde640b110513be77/Images/Screen Shot 2019-01-28 at 3.00.10 PM.png
--------------------------------------------------------------------------------
/Images/Screen Shot 2019-01-28 at 3.01.17 PM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youwenliang/Table-of-Contents/1b030f2dab91ee446b2877bcde640b110513be77/Images/Screen Shot 2019-01-28 at 3.01.17 PM.png
--------------------------------------------------------------------------------
/Images/Sketch Arboards1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youwenliang/Table-of-Contents/1b030f2dab91ee446b2877bcde640b110513be77/Images/Sketch Arboards1.png
--------------------------------------------------------------------------------
/Images/Sketch Artboards.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youwenliang/Table-of-Contents/1b030f2dab91ee446b2877bcde640b110513be77/Images/Sketch Artboards.png
--------------------------------------------------------------------------------
/Images/Sketch Options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youwenliang/Table-of-Contents/1b030f2dab91ee446b2877bcde640b110513be77/Images/Sketch Options.png
--------------------------------------------------------------------------------
/Images/Sketch Options1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youwenliang/Table-of-Contents/1b030f2dab91ee446b2877bcde640b110513be77/Images/Sketch Options1.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Table-of-Contents
2 | **Updates (1/28)**: Made the plugin compatible with Sketch 53 beta.
3 |
4 | This is a Sketch Plugin I created upon a request from our Mozilla UX team. As a memember of the UX team we usually have to create different specs using Sketch, and we have to manually add a "Table of Contents" page to list out the contents. For this plugin you can just press the shortcut and it will automatically generate a Table of Contents page for you. (There are some rules to follow when you create your spec.)
5 |
6 | 
7 | 
8 |
9 | ## Artboads Setup
10 | 1. Artboard size: larger than 1440 x 1024 preferred.
11 | 2. Artboard order: top first (Export as PDF order).
12 | 3. Having a Cover as the first artboard & Release Notes or Version History as the second (Support multiple version history pages now).
13 |
14 |
15 | ## Titles Setup
16 | 1. Use **Folders** named "Header1" & "Header2" as first level titles & second level titles.
17 |
18 | 
19 |
20 | ## Example File structure
21 | 1. Cover
22 | 2. Release Notes
23 | 3. Topic A (using Header1)
24 | 4. Topic A-1 (using Header2)
25 | 5. Topic A-2 (using Header2)
26 | 6. Topic A-3 (using Header2)
27 | 7. Topic B (using Header1)
28 | 8. Topic B-1 (using Header2)
29 | 9. Topic B-2 (using Header2)
30 | 10. Topic C (using Header1)
31 | ...
32 |
33 | 
34 |
35 | Checkout the Example Sketch file for more information: https://github.com/youwenliang/Table-of-Contents/blob/master/Example.sketch
36 | Table of Contents will be generated after the Cover artboard.
37 | Shortcut: control + option + ⌘command + T
38 |
39 | ## Additional Options:
40 | You can change the artboard background color, or change the margin and font size according to the amount of your contents.
41 |
42 | ## Refresh / Delete your ToC:
43 | If you've already generated a Table of Contents, you can click the button (control + option + ⌘command + T) again to refresh or delete your current Table of Contents.
44 |
45 | 
46 |
--------------------------------------------------------------------------------
/Table of Contents.sketchplugin/Contents/Resources/World.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youwenliang/Table-of-Contents/1b030f2dab91ee446b2877bcde640b110513be77/Table of Contents.sketchplugin/Contents/Resources/World.pdf
--------------------------------------------------------------------------------
/Table of Contents.sketchplugin/Contents/Sketch/library.cocoascript:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {NSMutableArray} array
3 | * @param {Number} atIndex
4 | * @param {Number} toIndex
5 | * @returns {NSMutableArray}
6 | */
7 |
8 | function moveObject(array, atIndex, toIndex) {
9 | if (atIndex != toIndex) {
10 | var object = [[[array objectAtIndex:atIndex] retain] autorelease]
11 |
12 | [array removeObjectAtIndex:atIndex]
13 | [array insertObject:object atIndex:toIndex]
14 | }
15 |
16 | return array
17 | }
18 |
19 |
20 |
21 | /**
22 | * Sets `isSelected` to `true` for all given layers
23 | * @param {NSArray.} layers
24 | */
25 |
26 | function restoreSelection(layers) {
27 | var loop = [layers objectEnumerator]
28 |
29 | while (layer = [loop nextObject]) {
30 | [layer setIsSelected:true]
31 | }
32 | }
33 |
34 |
35 |
36 | /**
37 | * Makes `NSApp sendAction` for `layer` `times` times
38 | * @param {NSDictionary} context
39 | * @param {String} action
40 | * @param {MSShapeGroup} layer
41 | * @param {Number} times
42 | */
43 |
44 | function sendActionTimes(context, action, object, times) {
45 | var doc = context.document
46 |
47 | [[doc currentPage] deselectAllLayers]
48 | [object setIsSelected:true]
49 |
50 | for (var i = 0; i < times; i++) {
51 | [NSApp sendAction:action to:nil from:doc]
52 | }
53 |
54 | restoreSelection(context.selection)
55 | }
56 |
57 |
58 |
59 | /**
60 | * Returns layers sorted by first number in name
61 | * @param {NSArray.} layers
62 | * @returns {NSArray.}
63 | */
64 |
65 | function sortLayers(layers, isAscending) {
66 | var sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:'name'
67 | ascending:isAscending
68 | selector:'localizedStandardCompare:']
69 |
70 | return [layers sortedArrayUsingDescriptors:[sortDescriptor]]
71 | }
72 |
73 |
74 |
75 | /**
76 | * @typedef {Object} StepsStruct
77 | * @property {MSShapeGroup} layer
78 | * @property {Number} steps
79 | */
80 |
81 | /**
82 | * Returns the quantity of steps that needed to reorder `layers`
83 | * in according to order of `sortedLayers`
84 | * @param {NSArray.} selection
85 | * @param {NSArray.} sortedLayers
86 | * @returns {Array.}
87 | */
88 |
89 | function getSteps(selection, sortedLayers) {
90 | var steps = []
91 | var selectedLayers = [selection mutableCopy]
92 |
93 | for (var i = 0; i < [sortedLayers count]; i++) {
94 | var layer = [sortedLayers objectAtIndex:i]
95 | var index = [selectedLayers indexOfObject:layer]
96 |
97 | steps.push({ layer: layer, steps: index - i })
98 |
99 | selectedLayers = moveObject(selectedLayers, index, i)
100 | }
101 |
102 | return steps
103 | }
104 |
105 |
106 |
107 | /**
108 | * @param {NSArray.} selection
109 | * @returns {String}
110 | */
111 |
112 | function getObjectName(selection) {
113 | var object = [selection firstObject]
114 |
115 | switch ([object class]) {
116 | case [MSShapeGroup class]:
117 | return 'layers'
118 | case [MSArtboardGroup class]:
119 | return 'artboards'
120 | default:
121 | return 'objects'
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/Table of Contents.sketchplugin/Contents/Sketch/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "author" : "Mark Liang",
3 | "commands" : [
4 | {
5 | "script" : "script.cocoascript",
6 | "handler" : "onRun",
7 | "shortcut" : "cmd ctrl option t",
8 | "name" : "Table of Contents",
9 | "identifier" : "tableofcontents"
10 | }
11 | ],
12 | "menu": {
13 | "isRoot": true,
14 | "items": [
15 | "tableofcontents"
16 | ]
17 | },
18 | "identifier" : "com.example.sketch.tableofcontents",
19 | "version" : "1.2",
20 | "description" : "Sketch Plugin for creating a Table of Contents artboard automatically.",
21 | "authorEmail" : "mliang@mozilla.com",
22 | "name" : "Table of Contents"
23 | }
24 |
--------------------------------------------------------------------------------
/Table of Contents.sketchplugin/Contents/Sketch/script.cocoascript:
--------------------------------------------------------------------------------
1 |
2 | @import "library.cocoascript";
3 |
4 | var onRun = function(context) {
5 | var sketch = context.api();
6 | var document = sketch.selectedDocument;
7 | var doc = context.document; // the current document (MSDocument)
8 | var plugin = context.plugin; // the plugin (MSPluginBundle)
9 | var page = [doc currentPage]; // the current page (MSPage)
10 | var artboards = doc.currentPage().artboards();
11 | var numberArtboard = artboards.length;
12 | var Text = require('sketch/dom').Text
13 |
14 | // Prompt Dialog
15 | if(numberArtboard > 2) {
16 | var t = artboards[numberArtboard-1];
17 | if([t name] == "Table of Contents") {
18 | showCheckWindow(doc, plugin, page, artboards, numberArtboard, context);
19 | }
20 | else {
21 | addTableOfContents(doc, plugin, page, artboards, numberArtboard, context);
22 | }
23 | }
24 |
25 | function showCheckWindow(doc, plugin, page, artboards, numberArtboard, context) {
26 | var alertWindow = COSAlertWindow.new()
27 | alertWindow.setMessageText('Do you want to refresh or delete your Table of Contents?')
28 |
29 | alertWindow.addButtonWithTitle('Refresh')
30 | alertWindow.addButtonWithTitle('Delete')
31 |
32 |
33 | if (alertWindow.runModal() == -NSModalResponseStop) {
34 | deleteTableOfContents(artboards);
35 | var a = doc.currentPage().artboards();
36 | var n = a.length;
37 | addTableOfContents(doc, plugin, page, a, n, context);
38 | [doc showMessage: "Table of Contents refreshed."]
39 |
40 | } else {
41 | deleteTableOfContents(artboards);
42 | [doc showMessage: "Table of Contents deleted."]
43 | }
44 | }
45 |
46 | function showAlertWindow() {
47 | var alertWindow = COSAlertWindow.new()
48 |
49 | // Checkbox
50 | var regexpRename = regexpRename || {};
51 |
52 | regexpRename.createCheckbox = function ( label, value, flag ) {
53 |
54 | flag = ( flag == false ) ? NSOffState : NSOnState;
55 |
56 | var checkbox = NSButton.alloc().initWithFrame( NSMakeRect( 0, 0, 300, 18 ) );
57 | checkbox.setButtonType( NSSwitchButton );
58 | checkbox.setTitle( label );
59 | checkbox.setTag( value );
60 | checkbox.setState( flag );
61 |
62 | return checkbox;
63 | }
64 |
65 | alertWindow.setMessageText('Create Table of Contents')
66 |
67 | alertWindow.addTextLabelWithValue('Enter background color')
68 | alertWindow.addTextFieldWithValue('#FFFFFF')
69 |
70 | alertWindow.addTextLabelWithValue('Enter text color')
71 | alertWindow.addTextFieldWithValue('#4a4a4a')
72 |
73 | alertWindow.addTextLabelWithValue('Enter margin between contents')
74 | alertWindow.addTextFieldWithValue('50')
75 |
76 | alertWindow.addTextLabelWithValue('Enter contents font size')
77 | alertWindow.addTextFieldWithValue('20')
78 |
79 | //alertWindow.addAccessoryView( regexpRename.createCheckbox( 'Add numbers to title', 'a', true ) )
80 | //alertWindow.addAccessoryView( regexpRename.createCheckbox( 'Show page numbers', 's', true ) )
81 |
82 | alertWindow.addButtonWithTitle('OK')
83 | alertWindow.addButtonWithTitle('Cancel')
84 |
85 | if (alertWindow.runModal() == -NSModalResponseStop) {
86 | [doc showMessage: "Table of Contents created."]
87 | return new UserInput(
88 | alertWindow.viewAtIndex(1).stringValue(),
89 | alertWindow.viewAtIndex(3).stringValue(),
90 | alertWindow.viewAtIndex(5).stringValue(),
91 | alertWindow.viewAtIndex(7).stringValue(),
92 | //alertWindow.viewAtIndex(8).state(),
93 | //alertWindow.viewAtIndex(9).state()
94 | )
95 | } else {
96 | return null
97 | }
98 | }
99 |
100 | // Dialog Results
101 | function UserInput(bgColor, txtColor, margin, fsize) {
102 | this.bgColor = bgColor
103 | this.txtColor = txtColor
104 | this.margin = margin
105 | this.fsize = fsize
106 | //this.titleN = titleN
107 | //this.pageN = pageN
108 | }
109 |
110 | function deleteTableOfContents(a){
111 | var n = a.length
112 | a[n-1].removeFromParent();
113 | for(var i = 0; i < n-1; i++) {
114 | var temp = a[i];
115 | var l = (temp.children().toString().match(/ -1) ;
296 | // else {
297 | // if(header == 2 && count != 0) {
298 | // var titleNumber = count + "." + k + " ";
299 | // if(userInput.titleN == 0) {
300 | // titleNumber = "-";
301 | // }
302 | // var textLayer = MSTextLayer.new();
303 | // textLayer.setName([temp name]);
304 | // textLayer.setStringValue(" " + titleNumber + title);
305 | // textLayer.addAttribute_value(NSFontAttributeName, NSFont.fontWithName_size("Fira Sans", userInput.fsize));
306 | // selected_artboard.addLayers([textLayer]);
307 |
308 | // k++
309 | // } else {
310 | // count++;
311 | // var titleNumber = count + ". ";
312 | // if(userInput.titleN == 0) {
313 | // titleNumber = "";
314 | // }
315 |
316 | // var textLayer = MSTextLayer.new();
317 | // textLayer.setName([temp name]);
318 | // textLayer.setStringValue(titleNumber + title);
319 | // textLayer.addAttribute_value(NSFontAttributeName, NSFont.fontWithName_size("Fira Sans", userInput.fsize));
320 | // selected_artboard.addLayers([textLayer]);
321 |
322 | // k = 1;
323 | // }
324 | // if(userInput.pageN == 1) {
325 |
326 | // var numberLayer = MSTextLayer.new();
327 | // numberLayer.setName(page+"");
328 | // numberLayer.setStringValue(page+"");
329 | // numberLayer.addAttribute_value(NSFontAttributeName, NSFont.fontWithName_size("Fira Sans", userInput.fsize));
330 | // selected_artboard.addLayers([numberLayer]);
331 | // }
332 |
333 | // // set content position
334 | // textLayer.frame().setX(100 + 500*parseInt(add/15));
335 | // textLayer.frame().setY(100+(add%15)*(24+parseInt(userInput.margin)) + 120);
336 | // textLayer.setTextColor(MSImmutableColor.colorWithSVGString("#FFF").newMutableCounterpart());
337 |
338 | // if(userInput.pageN == 1) {
339 | // numberLayer.frame().setX(500 + 500*parseInt(add/15));
340 | // numberLayer.frame().setY(100+(add%15)*(24+parseInt(userInput.margin)) + 120);
341 | // numberLayer.setTextColor(MSImmutableColor.colorWithSVGString("#FFF").newMutableCounterpart());
342 | // }
343 |
344 | // add++;
345 | // }
346 | // }
347 | }
348 |
349 | [selected_artboard select:true byExpandingSelection:false];
350 | [view zoomToSelection];
351 | }
352 | }
353 | }
354 |
--------------------------------------------------------------------------------