├── Resources ├── Icons.sketch ├── Export Wireframes.png ├── Create Artboard Slice.png ├── Artboard Description Add.png ├── Artboard Layout Settings.png ├── Artboard Title Settings.png ├── Section Title Settings.png └── Artboard Description Settings.png ├── README.md ├── SNCR.sketchplugin └── Contents │ ├── Resources │ ├── icon.png │ ├── icons │ │ ├── layout-update.png │ │ ├── sections-link.png │ │ ├── titles-create.png │ │ ├── annotations-link.png │ │ ├── descriptions-set.png │ │ ├── layout-include.png │ │ ├── layout-preclude.png │ │ ├── other-hotspots.png │ │ ├── sections-unlink.png │ │ ├── sections-update.png │ │ ├── titles-include.png │ │ ├── titles-preclude.png │ │ ├── wireframes-add.png │ │ ├── annotations-create.png │ │ ├── annotations-update.png │ │ ├── descriptions-link.png │ │ ├── wireframes-export.png │ │ ├── wireframes-include.png │ │ ├── annotations-designate.png │ │ ├── descriptions-unlink.png │ │ ├── descriptions-update.png │ │ ├── layout-include-page.png │ │ ├── layout-preclude-page.png │ │ └── wireframes-preclude.png │ └── strings │ │ └── en.plist │ └── Sketch │ ├── Document │ ├── get-current-id.js │ ├── set-random-id.js │ └── set-custom-id.js │ ├── Groups │ └── update-content-bounds.js │ ├── Hotspots │ ├── toggle-all-in-document.js │ └── create-over-selected-overrides.js │ ├── Symbols │ ├── disable-style-overrides.js │ ├── replace-override-fills-with-swatch.js │ └── replace-override-for-instances-of-selection.js │ ├── Export │ └── page-to-zeplin.js │ ├── Text Layers │ ├── update-text-to-match-style-name.js │ └── recreate-and-remove-original.js │ ├── Artboards │ ├── update-export-settings-for-all-on-page.js │ ├── insert-mask-in.js │ ├── create-artboard-instance-of-artboard-symbol.js │ ├── increment-version-numbers.js │ └── create-slice-around.js │ ├── Data │ ├── script.js │ └── system.js │ ├── delegate.js │ ├── handlers.js │ ├── Customization │ ├── run-manifest-audit.js │ ├── add-feature-link-to-manifest.js │ └── add-strings-to-manifest.js │ ├── Text Styles │ └── convert-to-japanese-style.js │ ├── library.js │ ├── manifest.json │ └── functions.js └── appcast.xml /Resources/Icons.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Icons.sketch -------------------------------------------------------------------------------- /Resources/Export Wireframes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Export Wireframes.png -------------------------------------------------------------------------------- /Resources/Create Artboard Slice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Create Artboard Slice.png -------------------------------------------------------------------------------- /Resources/Artboard Description Add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Artboard Description Add.png -------------------------------------------------------------------------------- /Resources/Artboard Layout Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Artboard Layout Settings.png -------------------------------------------------------------------------------- /Resources/Artboard Title Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Artboard Title Settings.png -------------------------------------------------------------------------------- /Resources/Section Title Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Section Title Settings.png -------------------------------------------------------------------------------- /Resources/Artboard Description Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Artboard Description Settings.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sncr-sketch-plugin 2 | A plugin of Sketch actions which are useful in the workflows of the designers at Synchronoss Technologies Inc. 3 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icon.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/layout-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/layout-update.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/sections-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/sections-link.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/titles-create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/titles-create.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/annotations-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/annotations-link.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/descriptions-set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/descriptions-set.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/layout-include.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/layout-include.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/layout-preclude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/layout-preclude.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/other-hotspots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/other-hotspots.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/sections-unlink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/sections-unlink.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/sections-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/sections-update.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/titles-include.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/titles-include.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/titles-preclude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/titles-preclude.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/wireframes-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/wireframes-add.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/annotations-create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/annotations-create.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/annotations-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/annotations-update.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/descriptions-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/descriptions-link.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/wireframes-export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/wireframes-export.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/wireframes-include.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/wireframes-include.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/annotations-designate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/annotations-designate.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/descriptions-unlink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/descriptions-unlink.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/descriptions-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/descriptions-update.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/layout-include-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/layout-include-page.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/layout-preclude-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/layout-preclude-page.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/icons/wireframes-preclude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/wireframes-preclude.png -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Document/get-current-id.js: -------------------------------------------------------------------------------- 1 | @import '../library.js'; 2 | 3 | var onRun = function(context) { 4 | sketch.UI.alert('Current Document ID',context.document.documentData().objectID()); 5 | } 6 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Document/set-random-id.js: -------------------------------------------------------------------------------- 1 | @import '../library.js'; 2 | 3 | var onRun = function(context) { 4 | var oldID = context.document.documentData().objectID(); 5 | var newID = context.document.documentData().generateObjectID(); 6 | 7 | sketch.UI.alert('Set Random ID','A new document ID, ' + newID + ', has been created for the document (the old ID was ' + oldID + ').\n\nThe document must be saved for the change to take effect.'); 8 | } 9 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Groups/update-content-bounds.js: -------------------------------------------------------------------------------- 1 | @import '../library.js'; 2 | 3 | const document = sketch.getSelectedDocument(); 4 | const selection = document.selectedLayers; 5 | 6 | var onRun = function(context) { 7 | if (!selection.length) { 8 | sketch.UI.alert('Update Group Content Bounds','Nothing is selected.'); 9 | return false; 10 | } 11 | 12 | selection.forEach(layer => layer.sketchObject.fixGeometryWithOptions(0)); 13 | 14 | sketch.UI.message('The bounds of the selected group(s) have been updated'); 15 | } 16 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Document/set-custom-id.js: -------------------------------------------------------------------------------- 1 | @import '../library.js'; 2 | 3 | var onRun = function(context) { 4 | var oldID = context.document.documentData().objectID(); 5 | 6 | sketch.UI.getInputFromUser( 7 | "Custom ID for Document:", 8 | { 9 | initialValue: '', 10 | }, 11 | (err,val) => { 12 | if (err) { 13 | // most likely the user canceled the input 14 | return 15 | } 16 | 17 | if (val) { 18 | context.document.documentData().setObjectID(val); 19 | 20 | sketch.UI.message(oldID + ' has been replaced with ' + val); 21 | } 22 | } 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Hotspots/toggle-all-in-document.js: -------------------------------------------------------------------------------- 1 | @import '../library.js' 2 | 3 | var onRun = function(context) { 4 | var sketch = require('sketch') 5 | var hotspotGroups = [] 6 | 7 | sketch.getSelectedDocument().pages.forEach(getHotspotGroups) 8 | 9 | function getHotspotGroups(page) { 10 | page.sketchObject.children().forEach(function(layer){ 11 | if (layer.class() == 'MSLayerGroup' && layer.name() == 'Hotspots') hotspotGroups.push(layer) 12 | }) 13 | } 14 | 15 | var currentState = hotspotGroups[0].isVisible() 16 | 17 | hotspotGroups.forEach(function(group){ 18 | group.setIsVisible(!currentState) 19 | }) 20 | 21 | var message = (currentState == 0) ? 'visible 🙉' : 'hidden 🙈' 22 | 23 | sketch.UI.message('All document hotspots are now ' + message) 24 | } 25 | -------------------------------------------------------------------------------- /appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SNCR Plugins 5 | http://sparkle-project.org/files/sparkletestcast.xml 6 | A plugin of Sketch actions which are useful in the workflows of the designers at Synchronoss Technologies Inc. 7 | en 8 | 9 | Version 1.21 10 | 11 | 13 |
  • Improved mask in artboard function, added function to create a hotspot over selected overrides, added function to swap styles to Japanese styles.
  • 14 | 15 | ]]> 16 |
    17 | 18 |
    19 |
    20 |
    21 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Symbols/disable-style-overrides.js: -------------------------------------------------------------------------------- 1 | @import '../library.js'; 2 | 3 | var onRun = function(context) { 4 | var document = sketch.getSelectedDocument(); 5 | var page = document.selectedPage; 6 | var count = 0; 7 | 8 | page.sketchObject.symbols().forEach(function(symbol){ 9 | var symbol = sketch.fromNative(symbol); 10 | 11 | if (symbol.overrides) { 12 | symbol.overrides.forEach(function(override){ 13 | if (override.editable == 1 && override.property == 'layerStyle' || override.property == 'textStyle') { 14 | let overridePoint = override.sketchObject.overridePoint(); 15 | 16 | symbol.sketchObject.setOverridePoint_editable(overridePoint,0); 17 | 18 | log(overridePoint + ' on ' + symbol.name + ' has been disabled'); 19 | 20 | count++; 21 | } 22 | }); 23 | } 24 | }); 25 | 26 | document.sketchObject.reloadInspector(); 27 | 28 | sketch.UI.message(count + ' style overrides were disabled'); 29 | } 30 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Export/page-to-zeplin.js: -------------------------------------------------------------------------------- 1 | @import '../library.js' 2 | 3 | const document = sketch.getSelectedDocument() 4 | const pages = document.pages 5 | const page = document.selectedPage 6 | 7 | const pluginPath = NSHomeDirectory() + '/Library/Application Support/com.bohemiancoding.sketch3/Plugins/Zeplin.sketchplugin' 8 | const pluginURL = NSURL.fileURLWithPath(pluginPath) 9 | 10 | var onRun = function(context) { 11 | if (!NSFileManager.defaultManager().fileExistsAtPath(pluginPath)) { 12 | sketch.UI.alert(`Missing Zeplin Plugin`,`Expected the Zeplin plugin in the following location:\n${pluginPath}`) 13 | 14 | return false 15 | } else { 16 | MSPluginBundle.alloc().initPluginBundleWithURL(pluginURL) 17 | } 18 | 19 | page.layers.forEach(l => { 20 | if (l.sketchObject.class() == 'MSArtboardGroup' || l.sketchObject.class() == 'MSSymbolMaster') { 21 | l.selected = true 22 | } 23 | }) 24 | 25 | AppController.sharedInstance().runPluginCommandWithIdentifier_fromBundleAtURL('export',pluginURL) 26 | } 27 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Text Layers/update-text-to-match-style-name.js: -------------------------------------------------------------------------------- 1 | @import '../library.js'; 2 | 3 | var onRun = function(context) { 4 | var documentData = context.document.documentData(); 5 | var selections = context.selection; 6 | 7 | if (!selections.length) { 8 | sketch.UI.alert('Update Text to Match Style Name…','Select at least one text layer.'); 9 | return false; 10 | } 11 | 12 | selections.forEach(updateLayerText); 13 | 14 | function updateLayerText(textLayer) { 15 | var textLayerSharedStyleID = textLayer.sharedStyleID(); 16 | var textLayerStyle = documentData.textStyleWithID(textLayerSharedStyleID); 17 | var textLayerStyleName = textLayerStyle.name(); 18 | 19 | textLayer.setStringValue(textLayerStyleName); 20 | textLayer.setName(textLayerStyleName); 21 | } 22 | 23 | var message = 'The text layer name has been updated'; 24 | 25 | if (selections.length > 1) { 26 | message = selections.length + ' text layer names have been updated'; 27 | } 28 | 29 | sketch.UI.message(message); 30 | } 31 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Artboards/update-export-settings-for-all-on-page.js: -------------------------------------------------------------------------------- 1 | @import '../library.js'; 2 | 3 | var onRun = function(context) { 4 | var document = context.document; 5 | var count = 0; 6 | 7 | document.currentPage().artboards().forEach(updateExportSettings); 8 | 9 | function updateExportSettings(artboard) { 10 | var updated = false; 11 | 12 | if (artboard.exportOptions().layerOptions() != 2) { 13 | artboard.exportOptions().setLayerOptions(2); 14 | 15 | updated = true; 16 | } 17 | 18 | if (!artboard.exportOptions().exportFormats().length) { 19 | var format = artboard.exportOptions().addExportFormat(); 20 | 21 | format.setScale(2); 22 | format.setFileFormat('png'); 23 | 24 | updated = true; 25 | } 26 | 27 | if (updated) { 28 | log(`Export settings were updated for ${artboard.name()}`); 29 | 30 | count++; 31 | } 32 | } 33 | 34 | document.reloadInspector(); 35 | 36 | var message = 'No artboards need updating'; 37 | 38 | if (count !== 0) { 39 | message = count + ' artboards were updated'; 40 | } 41 | 42 | sketch.UI.message(message); 43 | } 44 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Artboards/insert-mask-in.js: -------------------------------------------------------------------------------- 1 | @import '../library.js' 2 | 3 | var document = sketch.getSelectedDocument() 4 | var selections = document.selectedLayers.layers 5 | 6 | var onRun = function(context) { 7 | if (!selections.length) { 8 | sketch.UI.alert('Insert Mask in Selected Artboards','Select at least one artboard (or symbol).') 9 | return false 10 | } 11 | 12 | selections.forEach(selection => { 13 | if (selection.type == 'Artboard' || selection.type == 'SymbolMaster') { 14 | let mask = new sketch.ShapePath({ 15 | frame: { 16 | x: 0, 17 | y: 0, 18 | width: selection.frame.width, 19 | height: selection.frame.height 20 | }, 21 | name: 'Mask', 22 | parent: selection 23 | }) 24 | 25 | mask.moveToBack() 26 | 27 | mask.sketchObject.setHasFixedTop(1) 28 | mask.sketchObject.setHasFixedRight(1) 29 | mask.sketchObject.setHasFixedBottom(1) 30 | mask.sketchObject.setHasFixedLeft(1) 31 | mask.sketchObject.setHasClippingMask(1) 32 | } 33 | }) 34 | 35 | var reselect = selections.slice(0) 36 | 37 | document.selectedLayers.clear() 38 | 39 | reselect.forEach(selection => { 40 | selection.sketchObject.select_byExtendingSelection(1,1) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Symbols/replace-override-fills-with-swatch.js: -------------------------------------------------------------------------------- 1 | @import '../library.js' 2 | @import '../delegate.js' 3 | 4 | const document = sketch.getSelectedDocument() 5 | const selections = document.selectedLayers 6 | const swatches = document.swatches 7 | 8 | var onRun = function(context) { 9 | if (selections.length == 0) { 10 | sketch.UI.alert('Replace Override Fills with Swatch','Select at least one symbol instance') 11 | 12 | return 13 | } 14 | 15 | sketch.UI.getInputFromUser('Select the swatch to override with…', 16 | { 17 | type: sketch.UI.INPUT_TYPE.selection, 18 | possibleValues: swatches.map(s => s.name), 19 | }, 20 | (err,val) => { 21 | if (err) { 22 | // User most likely canceled input 23 | return 24 | } 25 | 26 | let swatch = swatches.find(s => s.name == val) 27 | let swatchReference = swatch.sketchObject.makeReferencingColor() 28 | 29 | let updateCount = 0 30 | 31 | selections.layers.forEach(selection => { 32 | if (selection.overrides) { 33 | let uneditedOverrides = selection.overrides.filter(o => o.property == 'fillColor' && o.isDefault == true) 34 | //let editedOverrides = selection.overrides.filter(o => o.property == 'fillColor' && o.isDefault == false) 35 | 36 | uneditedOverrides.forEach(o => { 37 | o.value = swatchReference 38 | updateCount++ 39 | }) 40 | } 41 | }) 42 | 43 | sketch.UI.message(`${updateCount} overrides have been updated`) 44 | } 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Text Layers/recreate-and-remove-original.js: -------------------------------------------------------------------------------- 1 | @import '../library.js'; 2 | 3 | var onRun = function(context) { 4 | var documentData = context.document.documentData(); 5 | 6 | var selection = context.selection; 7 | 8 | if (!selection.length) { 9 | sketch.UI.alert('Recreate and Remove Original…','Select at least one text layer.'); 10 | return false; 11 | } 12 | 13 | selection.forEach(replaceTextLayer); 14 | 15 | function replaceTextLayer(textLayer) { 16 | var textLayerParent = textLayer.parentObject(); 17 | var textLayerIndex = textLayerParent.indexOfLayer(textLayer); 18 | var textLayerString = textLayer.stringValue(); 19 | var textLayerSharedStyleID = textLayer.sharedStyleID(); 20 | var textLayerStyle = (textLayerSharedStyleID) ? documentData.textStyleWithID(textLayerSharedStyleID) : textLayer.style(); 21 | 22 | var newTextLayer = MSTextLayer.new(); 23 | 24 | newTextLayer.setStringValue(textLayerString); 25 | newTextLayer.setName(textLayerString); 26 | newTextLayer.setFrame(textLayer.frame()); 27 | 28 | if (textLayerSharedStyleID) { 29 | newTextLayer.setSharedStyle(textLayerStyle); 30 | } else { 31 | newTextLayer.setStyle(textLayerStyle); 32 | } 33 | 34 | textLayerParent.insertLayer_atIndex(newTextLayer,textLayerIndex); 35 | textLayer.removeFromParent(); 36 | } 37 | 38 | var message = 'The text layer has been recreated'; 39 | 40 | if (selection.length > 1) { 41 | message = selection.length + ' text layers have been recreated'; 42 | } 43 | 44 | sketch.UI.message(message); 45 | } 46 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Artboards/create-artboard-instance-of-artboard-symbol.js: -------------------------------------------------------------------------------- 1 | @import '../library.js'; 2 | 3 | var document = sketch.getSelectedDocument(); 4 | var selections = document.selectedLayers.layers; 5 | var page = document.selectedPage; 6 | 7 | var onRun = function(context) { 8 | if (!selections.length || selections.length > 1) { 9 | sketch.UI.alert('Create Artboard Instance of Artboard Symbol','Select one artboard symbol.'); 10 | return false; 11 | } 12 | 13 | var selection = selections[0]; 14 | 15 | if (selection.type != 'SymbolMaster') { 16 | sketch.UI.alert('Create Artboard Instance of Artboard Symbol','Artboard is not a symbol.'); 17 | return false; 18 | } 19 | 20 | var artboard = new sketch.Artboard({ 21 | name: selection.name, 22 | frame: new sketch.Rectangle( 23 | selection.frame.x + selection.frame.width + 100, 24 | selection.frame.y, 25 | selection.frame.width, 26 | selection.frame.height 27 | ) 28 | }); 29 | 30 | artboard = artboard.sketchObject; 31 | 32 | page.sketchObject.insertLayer_afterLayer(artboard,selection.sketchObject); 33 | 34 | var instance = selection.sketchObject.newSymbolInstance(); 35 | instance.setHasFixedTop(1); 36 | instance.setHasFixedRight(1); 37 | instance.setHasFixedBottom(1); 38 | instance.setHasFixedLeft(1); 39 | 40 | artboard.addLayer(instance); 41 | artboard.setHasBackgroundColor(1); 42 | artboard.setResizesContent(1); 43 | artboard.exportOptions().setLayerOptions(2); 44 | 45 | var exportFormat = artboard.exportOptions().addExportFormat(); 46 | exportFormat.setScale(2); 47 | exportFormat.setFileFormat('png'); 48 | 49 | artboard.select_byExtendingSelection(1,0); 50 | 51 | sketch.UI.message('Instance of artboard created'); 52 | } 53 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Hotspots/create-over-selected-overrides.js: -------------------------------------------------------------------------------- 1 | @import '../library.js' 2 | 3 | var onRun = function(context) { 4 | const sketch = require('sketch') 5 | const document = sketch.getSelectedDocument() 6 | const selections = document.selectedLayers 7 | const selection = selections.layers[0] 8 | 9 | if (!selections.length || selections.length > 1 || selection.type !== 'SymbolInstance') { 10 | sketch.UI.alert('Create Hotspot Over Selected Overrides','Select one or many overrides within a single symbol instance.') 11 | return 12 | } 13 | 14 | let selectedOverrides = selection.overrides.filter(o => o.selected) 15 | 16 | if (!selectedOverrides) { 17 | sketch.UI.alert('Create Hotspot Over Selected Overrides','Select one or many overrides within a single symbol instance.') 18 | return 19 | } 20 | 21 | let parentArtboard = selection.getParentArtboard() 22 | let hotspotGroup = parentArtboard.layers.find(l => sketch.Settings.layerSettingForKey(l,'hotspotGroup') == true) 23 | let hotspotCount = 0 24 | 25 | if (!hotspotGroup) { 26 | hotspotGroup = new sketch.Group({ 27 | name: 'Hotspots', 28 | locked: true, 29 | parent: parentArtboard 30 | }) 31 | 32 | sketch.Settings.setLayerSettingForKey(hotspotGroup,'hotspotGroup',true) 33 | } 34 | 35 | selectedOverrides.forEach(o => { 36 | new sketch.HotSpot({ 37 | name: 'Hotspot', 38 | frame: new sketch.Rectangle( 39 | o.getFrame().x - hotspotGroup.frame.x, 40 | o.getFrame().y - hotspotGroup.frame.y, 41 | o.getFrame().width, 42 | o.getFrame().height 43 | ), 44 | parent: hotspotGroup 45 | }) 46 | 47 | hotspotCount++ 48 | }) 49 | 50 | hotspotGroup.adjustToFit() 51 | 52 | sketch.UI.message(`${hotspotCount} hotspots created`) 53 | } 54 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Artboards/increment-version-numbers.js: -------------------------------------------------------------------------------- 1 | @import '../library.js' 2 | 3 | const document = sketch.getSelectedDocument() 4 | const page = document.selectedPage 5 | const selections = document.selectedLayers 6 | 7 | var onRun = function(context) { 8 | var positions = ['First','Second','Third','Fourth'] 9 | 10 | var alertWindow = createAlertWindow(context,'Increment Version Numbers') 11 | 12 | var positionLabel = createAlertLabel('Number position:',NSMakeRect(0,0,300,14)) 13 | 14 | alertWindow.addAccessoryView(positionLabel) 15 | 16 | var positionSelect = createAlertPopupButton(positions,1,NSMakeRect(0,0,120,28)) 17 | 18 | alertWindow.addAccessoryView(positionSelect) 19 | 20 | var amountLabel = createAlertLabel('Increment amount (e.g. 1, 2 -1):',NSMakeRect(0,0,300,14)) 21 | 22 | alertWindow.addAccessoryView(amountLabel) 23 | 24 | var amountValue = createAlertField('',NSMakeRect(0,0,60,23)) 25 | 26 | alertWindow.addAccessoryView(amountValue) 27 | 28 | var submit = alertWindow.addButtonWithTitle('OK') 29 | var cancel = alertWindow.addButtonWithTitle('Cancel') 30 | 31 | // Set key order 32 | setKeyOrder(alertWindow.alert(),[ 33 | positionSelect, 34 | amountValue, 35 | submit 36 | ]) 37 | 38 | var response = alertWindow.runModal() 39 | 40 | if (response == 1000) { 41 | var amount = Number(amountValue.stringValue()) 42 | 43 | if (Number.isInteger(amount) && amount !== 0) { 44 | let layers = selections.layers || page.layers 45 | 46 | layers.forEach(l => { 47 | var types = ['Artboard','SymbolMaster'] 48 | 49 | if (types.includes(l.type)) { 50 | let version = l.name.substr(0,l.name.indexOf(' ')) 51 | let versionParts = version.split('.') 52 | let remainder = l.name.substr(l.name.indexOf(' ')) 53 | 54 | let position = positionSelect.indexOfSelectedItem() 55 | 56 | versionParts[position] = Number(versionParts[position]) + amount 57 | 58 | l.name = versionParts.join('.') + remainder 59 | } 60 | }) 61 | 62 | let scope = (selections.layers) ? 'Selected' : 'All' 63 | 64 | sketch.UI.message(`${scope} artboards incremented by ${amount}`) 65 | } else { 66 | sketch.UI.alert('Increment Version Numbers for Artboards on Page','Please provide a valid number to increment by.') 67 | } 68 | } else return false 69 | } 70 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Data/script.js: -------------------------------------------------------------------------------- 1 | @import 'system.js'; 2 | 3 | const sketch = require("sketch") 4 | const util = require("util") 5 | 6 | var DataSupplier = require("sketch/data-supplier"); 7 | 8 | var onStartup = function() { 9 | DataSupplier.registerDataSupplier("public.text", "Text From File", "SupplyTextFromFile"); 10 | DataSupplier.registerDataSupplier("public.image", "Image From Folder", "SupplyImageFromFolder"); 11 | DataSupplier.registerDataSupplier("public.text", "Random Text From File", "SupplyRandomTextFromFile"); 12 | DataSupplier.registerDataSupplier("public.image", "Random Image From Folder", "SupplyRandomImageFromFolder"); 13 | }; 14 | 15 | var onShutdown = function() { 16 | DataSupplier.deregisterDataSuppliers(); 17 | }; 18 | 19 | var onSupplyTextFromFile = function(context) { 20 | var texts = System.textsFromChooseFile(); 21 | if (texts.length > 0) { 22 | supplyOrderedData(context, texts); 23 | } 24 | }; 25 | 26 | var onSupplyImageFromFolder = function(context) { 27 | var images = System.imagesFromChooseFolder(); 28 | if (images.length > 0) { 29 | supplyOrderedData(context, images); 30 | } 31 | }; 32 | 33 | var onSupplyRandomTextFromFile = function(context) { 34 | var texts = System.textsFromChooseFile(); 35 | if (texts.length > 0) { 36 | supplyRandomData(context, texts); 37 | } 38 | }; 39 | 40 | var onSupplyRandomImageFromFolder = function(context) { 41 | var images = System.imagesFromChooseFolder(); 42 | if (images.length > 0) { 43 | supplyRandomData(context, images); 44 | } 45 | }; 46 | 47 | function supplyOrderedData(context, data) { 48 | for (var i = 0; i < context.data.requestedCount; i++) { 49 | var dataIndex; 50 | if (context.data.isSymbolInstanceOverride == 1) { 51 | var selection = NSDocumentController.sharedDocumentController().currentDocument().selectedLayers().layers(); 52 | dataIndex = selection.indexOfObject(context.data.items.objectAtIndex(i).symbolInstance()) 53 | } else { 54 | dataIndex = i; 55 | } 56 | DataSupplier.supplyDataAtIndex(context.data.key, data[dataIndex % data.length], i); 57 | } 58 | } 59 | 60 | function supplyRandomData(context, data) { 61 | for (var i = 0; i < context.data.requestedCount; i++) { 62 | DataSupplier.supplyDataAtIndex(context.data.key, data[Math.floor(Math.random() * data.length)], i); 63 | } 64 | } -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/delegate.js: -------------------------------------------------------------------------------- 1 | // 2 | // MochaJSDelegate.js 3 | // MochaJSDelegate 4 | // 5 | // Created by Matt Curtis 6 | // Copyright (c) 2015. All rights reserved. 7 | // 8 | 9 | var MochaJSDelegate = function(selectorHandlerDict){ 10 | var uniqueClassName = "MochaJSDelegate_DynamicClass_" + NSUUID.UUID().UUIDString(); 11 | 12 | var delegateClassDesc = MOClassDescription.allocateDescriptionForClassWithName_superclass_(uniqueClassName, NSObject); 13 | 14 | delegateClassDesc.registerClass(); 15 | 16 | // Handler storage 17 | 18 | var handlers = {}; 19 | 20 | // Define interface 21 | 22 | this.setHandlerForSelector = function(selectorString, func){ 23 | var handlerHasBeenSet = (selectorString in handlers); 24 | var selector = NSSelectorFromString(selectorString); 25 | 26 | handlers[selectorString] = func; 27 | 28 | if(!handlerHasBeenSet){ 29 | /* 30 | For some reason, Mocha acts weird about arguments: 31 | https://github.com/logancollins/Mocha/issues/28 32 | 33 | We have to basically create a dynamic handler with a likewise dynamic number of predefined arguments. 34 | */ 35 | 36 | var dynamicHandler = function(){ 37 | var functionToCall = handlers[selectorString]; 38 | 39 | if(!functionToCall) return; 40 | 41 | return functionToCall.apply(delegateClassDesc, arguments); 42 | }; 43 | 44 | var args = [], regex = /:/g; 45 | while(match = regex.exec(selectorString)) args.push("arg"+args.length); 46 | 47 | dynamicFunction = eval("(function("+args.join(",")+"){ return dynamicHandler.apply(this, arguments); })"); 48 | 49 | delegateClassDesc.addInstanceMethodWithSelector_function_(selector, dynamicFunction); 50 | } 51 | }; 52 | 53 | this.removeHandlerForSelector = function(selectorString){ 54 | delete handlers[selectorString]; 55 | }; 56 | 57 | this.getHandlerForSelector = function(selectorString){ 58 | return handlers[selectorString]; 59 | }; 60 | 61 | this.getAllHandlers = function(){ 62 | return handlers; 63 | }; 64 | 65 | this.getClass = function(){ 66 | return NSClassFromString(uniqueClassName); 67 | }; 68 | 69 | this.getClassInstance = function(){ 70 | return NSClassFromString(uniqueClassName).new(); 71 | }; 72 | 73 | // Conveience 74 | 75 | if(typeof selectorHandlerDict == "object"){ 76 | for(var selectorString in selectorHandlerDict){ 77 | this.setHandlerForSelector(selectorString, selectorHandlerDict[selectorString]); 78 | } 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Data/system.js: -------------------------------------------------------------------------------- 1 | var System = {}; 2 | 3 | System.chooseFile = function() { 4 | var panel = NSOpenPanel.openPanel(); 5 | panel.setCanChooseDirectories(false); 6 | panel.setCanChooseFiles(true); 7 | panel.setCanCreateDirectories(false); 8 | panel.setAllowedFileTypes(["txt"]); 9 | if (panel.runModal() == NSOKButton) { 10 | return panel.URL().path(); 11 | } 12 | }; 13 | 14 | System.chooseFolder = function(setCanCreateDirectories) { 15 | var panel = NSOpenPanel.openPanel(); 16 | panel.setCanChooseDirectories(true); 17 | panel.setCanChooseFiles(false); 18 | if (setCanCreateDirectories == true) { 19 | panel.setCanCreateDirectories(true); 20 | } 21 | if (panel.runModal() == NSOKButton) { 22 | return panel.URL().path(); 23 | } 24 | }; 25 | 26 | System.savePanel = function(defaultName) { 27 | var panel = NSSavePanel.savePanel(); 28 | if (defaultName) { 29 | panel.setNameFieldStringValue(defaultName); 30 | } 31 | panel.setCanCreateDirectories(true); 32 | if (panel.runModal() == NSOKButton) { 33 | return panel.URL().path(); 34 | } 35 | }; 36 | 37 | System.textsFromFile = function(path) { 38 | var contents = NSString.stringWithContentsOfFile_encoding_error(path, NSUTF8StringEncoding, nil); 39 | var data = contents.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()); 40 | var texts = []; 41 | var loopData = data.objectEnumerator(); 42 | var item; 43 | while (item = loopData.nextObject()) { 44 | if (item.length() > 0) { 45 | texts.push(String(item)); 46 | } 47 | } 48 | return texts; 49 | }; 50 | 51 | System.imagesFromFolder = function(path) { 52 | var images = []; 53 | var supportFormats = ["png", "jpg", "jpeg", "tif", "tiff", "gif", "webp", "bmp"]; 54 | var fileManager = NSFileManager.defaultManager(); 55 | var fileList = fileManager.contentsOfDirectoryAtPath_error(path, nil); 56 | fileList = fileList.sortedArrayUsingSelector("localizedStandardCompare:"); 57 | fileList.forEach(function(file) { 58 | if (supportFormats.indexOf(String(file.pathExtension().lowercaseString())) != -1) { 59 | images.push(path + "/" + file); 60 | } 61 | }); 62 | return images; 63 | }; 64 | 65 | System.textsFromChooseFile = function() { 66 | var textFile = System.chooseFile(); 67 | if (textFile == nil) { 68 | return []; 69 | } else { 70 | return System.textsFromFile(textFile); 71 | } 72 | } 73 | 74 | System.imagesFromChooseFolder = function() { 75 | var imageFolder = System.chooseFolder(); 76 | if (imageFolder == nil) { 77 | return []; 78 | } else { 79 | return System.imagesFromFolder(imageFolder); 80 | } 81 | } 82 | 83 | System.writeStringToFile = function(content, filePath) { 84 | NSString.stringWithString(content).writeToFile_atomically_encoding_error_( 85 | filePath, true, NSUTF8StringEncoding, nil 86 | ); 87 | }; 88 | 89 | System.showInFinder = function(filePath) { 90 | NSWorkspace.sharedWorkspace().selectFile_inFileViewerRootedAtPath(filePath, nil); 91 | }; 92 | 93 | System.mkdir = function(filePath) { 94 | return NSFileManager.defaultManager().createDirectoryAtPath_withIntermediateDirectories_attributes_error_( 95 | filePath, true, nil, nil 96 | ); 97 | }; 98 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/handlers.js: -------------------------------------------------------------------------------- 1 | @import 'functions.js'; 2 | 3 | var sectionsLink = function(context) { 4 | sncr.init(context,"sections-link"); 5 | } 6 | 7 | var sectionsUnlink = function(context) { 8 | sncr.init(context,"sections-unlink"); 9 | } 10 | 11 | var sectionsUpdate = function(context) { 12 | sncr.init(context,"sections-update"); 13 | } 14 | 15 | var sectionsSettings = function(context) { 16 | sncr.init(context,"sections-settings"); 17 | } 18 | 19 | var titlesCreate = function(context) { 20 | sncr.init(context,"titles-create"); 21 | } 22 | 23 | var titlesInclude = function(context) { 24 | sncr.init(context,"titles-include"); 25 | } 26 | 27 | var titlesPreclude = function(context) { 28 | sncr.init(context,"titles-preclude"); 29 | } 30 | 31 | var titlesSettings = function(context) { 32 | sncr.init(context,"titles-settings"); 33 | } 34 | 35 | var descriptionsSet = function(context) { 36 | sncr.init(context,"descriptions-set"); 37 | } 38 | 39 | var descriptionsLink = function(context) { 40 | sncr.init(context,"descriptions-link"); 41 | } 42 | 43 | var descriptionsUnlink = function(context) { 44 | sncr.init(context,"descriptions-unlink"); 45 | } 46 | 47 | var descriptionsUpdate = function(context) { 48 | sncr.init(context,"descriptions-update"); 49 | } 50 | 51 | var descriptionsSettings = function(context) { 52 | sncr.init(context,"descriptions-settings"); 53 | } 54 | 55 | var layoutUpdate = function(context) { 56 | sncr.init(context,"layout-update"); 57 | } 58 | 59 | var layoutIncludeSelected = function(context) { 60 | sncr.init(context,"layout-include-selected"); 61 | } 62 | 63 | var layoutPrecludeSelected = function(context) { 64 | sncr.init(context,"layout-preclude-selected"); 65 | } 66 | 67 | var layoutIncludePage = function(context) { 68 | sncr.init(context,"layout-include-page"); 69 | } 70 | 71 | var layoutPrecludePage = function(context) { 72 | sncr.init(context,"layout-preclude-page"); 73 | } 74 | 75 | var layoutSettings = function(context) { 76 | sncr.init(context,"layout-settings"); 77 | } 78 | 79 | var annotationsCreate = function(context) { 80 | sncr.init(context,"annotations-create"); 81 | } 82 | 83 | var annotationsDesignate = function(context) { 84 | sncr.init(context,"annotations-designate"); 85 | } 86 | 87 | var annotationsLink = function(context) { 88 | sncr.init(context,"annotations-link"); 89 | } 90 | 91 | var annotationsPosition = function(context) { 92 | sncr.init(context,"annotations-position"); 93 | } 94 | 95 | var annotationsUpdate = function(context) { 96 | sncr.init(context,"annotations-update"); 97 | } 98 | 99 | var annotationsSettings = function(context) { 100 | sncr.init(context,"annotations-settings"); 101 | } 102 | 103 | var wireframesAdd = function(context) { 104 | sncr.init(context,"wireframes-add"); 105 | } 106 | 107 | var wireframesInclude = function(context) { 108 | sncr.init(context,"wireframes-include"); 109 | } 110 | 111 | var wireframesPreclude = function(context) { 112 | sncr.init(context,"wireframes-preclude"); 113 | } 114 | 115 | var wireframesExport = function(context) { 116 | sncr.init(context,"wireframes-export"); 117 | } 118 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Customization/run-manifest-audit.js: -------------------------------------------------------------------------------- 1 | var onRun = function(context) { 2 | const sketch = require('sketch') 3 | const document = sketch.getSelectedDocument() 4 | 5 | const fileManager = NSFileManager.defaultManager() 6 | 7 | let manifestName = 'manifest.json' 8 | let manifestPath = document.path.replace(document.sketchObject.displayName(),manifestName) 9 | manifestPath = manifestPath.replaceAll('%20',' ') 10 | let manifestData = {} 11 | 12 | if (fileManager.fileExistsAtPath(manifestPath)) { 13 | manifestData = NSString.stringWithContentsOfFile_encoding_error(manifestPath,NSUTF8StringEncoding,null) 14 | manifestData = JSON.parse(manifestData) 15 | } else { 16 | sketch.UI.alert(`Audit Manifest`,`A manifest could not be found for the current document.`) 17 | 18 | return 19 | } 20 | 21 | let missingStringObjects = [] 22 | let missingStringOverrides = [] 23 | let updateCount = 0 24 | 25 | if (manifestData.strings) { 26 | manifestData.strings.forEach(string => { 27 | let objectIDs = string.stringObjectID.split('+') 28 | 29 | let layer = document.getLayerWithID(objectIDs[0]) 30 | 31 | if (!layer) missingStringObjects.push(string) 32 | 33 | if (layer && layer.type == 'SymbolInstance') { 34 | let override = layer.overrides.find(o => o.id == objectIDs[1]) 35 | 36 | if (!override) { 37 | let overrideList = [] 38 | let overrideProp = string.stringObjectID.split('_')[1] 39 | let overrides = layer.overrides.filter(o => o.editable && o.property == overrideProp) 40 | 41 | overrides.forEach(o => overrideList.push(o.id)) 42 | 43 | let parent = layer.getParentArtboard() 44 | 45 | document.centerOnLayer(parent) 46 | 47 | sketch.UI.getInputFromUser( 48 | `The symbol instance ${layer.name} has an orphaned manifest string. Select a new string for "${string.stringExample}" (${objectIDs[1]}) from the list below.`, 49 | { 50 | type: sketch.UI.INPUT_TYPE.selection, 51 | possibleValues: overrideList 52 | }, 53 | (err,val) => { 54 | if (err) { 55 | missingStringOverrides.push(string) 56 | 57 | return 58 | } 59 | 60 | if (val) { 61 | string.stringObjectID = `${layer.id}+${val}` 62 | 63 | updateCount++ 64 | 65 | return 66 | } 67 | } 68 | ) 69 | } 70 | } 71 | }) 72 | } 73 | 74 | if (updateCount) { 75 | manifestData = JSON.stringify(manifestData) 76 | manifestData = NSString.stringWithFormat('%@',manifestData) 77 | manifestData.dataUsingEncoding_(NSUTF8StringEncoding).writeToFile_atomically_(manifestPath,true) 78 | } 79 | 80 | let messageStringObjects = (missingStringObjects) ? `${missingStringObjects.length} string layers` : `` 81 | let messageStringOverrides = (missingStringOverrides) ? `, ${missingStringOverrides.length} string layer overrides` : `` 82 | let messageStringUpdates = (updateCount) ? `, ${updateCount} strings updated` : `` 83 | 84 | sketch.UI.message(`Manifest Audit: missing ${messageStringObjects}${messageStringOverrides}${messageStringUpdates}`) 85 | 86 | console.log(`Manifest Audit:`,`missingStringObjects`,missingStringObjects) 87 | console.log(`Manifest Audit:`,`missingStringOverrides`,missingStringOverrides) 88 | } 89 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Symbols/replace-override-for-instances-of-selection.js: -------------------------------------------------------------------------------- 1 | @import '../library.js'; 2 | @import '../delegate.js'; 3 | 4 | const document = sketch.getSelectedDocument(); 5 | const selection = document.selectedLayers; 6 | 7 | var onRun = function(context) { 8 | if (selection.length != 1) { 9 | sketch.UI.alert('Replace Text Override for Instances of Selected','Select one symbol instance'); 10 | return false; 11 | } 12 | 13 | var instance = selection.layers[0]; 14 | var instanceOverrides = getInstanceOverrides(instance); 15 | var masterInstances = instance.master.getAllInstances(); 16 | 17 | var alert = NSAlert.alloc().init(); 18 | 19 | alert.setMessageText('Replace Text Override for Instances of Selected'); 20 | alert.icon = getPluginAlertIcon(); 21 | 22 | var alertContent = NSView.alloc().init(); 23 | 24 | alertContent.setFlipped(true); 25 | 26 | var instanceLabel = createAlertLabelBold(instance.master.name + ' has ' + masterInstances.length + ' instance(s)',NSMakeRect(0,0,alertWidth,alertLabelHeight)); 27 | 28 | alertContent.addSubview(instanceLabel); 29 | 30 | var overrideSelectLabel = createAlertLabel('Text override to replace:',NSMakeRect(0,getMaxYOfView(alertContent,alertItemPadding),alertWidth,alertLabelHeight)); 31 | 32 | alertContent.addSubview(overrideSelectLabel); 33 | 34 | var overrideSelect = createAlertSelect(instanceOverrides.valueForKey('affectedLayer').valueForKey('name'),0,NSMakeRect(0,getMaxYOfView(alertContent),alertWidth,alertSelectHeight)); 35 | 36 | var overrideSelectDelegate = new MochaJSDelegate({ 37 | "comboBoxSelectionDidChange:" : (function() { 38 | let currentValue = instanceOverrides[overrideSelect.indexOfSelectedItem()].valueForKey('currentValue'); 39 | 40 | overrideValue.setStringValue(currentValue); 41 | }) 42 | }); 43 | 44 | overrideSelect.setDelegate(overrideSelectDelegate.getClassInstance()); 45 | 46 | alertContent.addSubview(overrideSelect); 47 | 48 | var overrideValueLabel = createAlertLabel('New override value:',NSMakeRect(0,getMaxYOfView(alertContent,alertItemPadding),alertWidth,alertLabelHeight)); 49 | 50 | alertContent.addSubview(overrideValueLabel); 51 | 52 | var overrideValue = createAlertField(instanceOverrides[0].valueForKey('currentValue'),NSMakeRect(0,getMaxYOfView(alertContent),alertWidth,alertFieldHeight)); 53 | 54 | alertContent.addSubview(overrideValue); 55 | 56 | alertContent.setFrame(NSMakeRect(0,0,alertWidth,getMaxYOfView(alertContent))); 57 | 58 | alert.setAccessoryView(alertContent); 59 | 60 | var submit = alert.addButtonWithTitle('OK'); 61 | var cancel = alert.addButtonWithTitle('Cancel'); 62 | 63 | setKeyOrder(alert,[ 64 | overrideSelect, 65 | overrideValue, 66 | submit 67 | ]); 68 | 69 | var responseCode = alert.runModal(); 70 | 71 | if (responseCode == 1000) { 72 | let override = instanceOverrides[overrideSelect.indexOfSelectedItem()]; 73 | let value = overrideValue.stringValue(); 74 | 75 | masterInstances.forEach(instance => instance.setOverrideValue(override,value)); 76 | 77 | document.sketchObject.reloadInspector(); 78 | 79 | sketch.UI.message(masterInstances.length + ' symbol instances have been updated'); 80 | } else return false; 81 | 82 | function getInstanceOverrides(instance) { 83 | let overrides = NSMutableArray.array(); 84 | 85 | instance.sketchObject.availableOverrides().forEach(function(override){ 86 | if (override.isEditable() && override.affectedLayer().class() == 'MSImmutableTextLayer') overrides.addObject(override); 87 | }); 88 | 89 | return overrides.reverseObjectEnumerator().allObjects(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Customization/add-feature-link-to-manifest.js: -------------------------------------------------------------------------------- 1 | var onRun = function(context) { 2 | const sketch = require('sketch') 3 | const document = sketch.getSelectedDocument() 4 | const selections = document.selectedLayers.layers 5 | 6 | if (!selections.length) { 7 | sketch.UI.alert(`Add Feature Link to Manifest`,`Something must be selected.`) 8 | 9 | return 10 | } 11 | 12 | var fileManager = NSFileManager.defaultManager() 13 | 14 | var manifestName = 'manifest.json' 15 | var manifestPath = document.path.replace(document.sketchObject.displayName(),manifestName) 16 | manifestPath = manifestPath.replaceAll('%20',' ') 17 | var manifestData = {} 18 | var manifestExists = false 19 | 20 | if (fileManager.fileExistsAtPath(manifestPath)) { 21 | manifestData = NSString.stringWithContentsOfFile_encoding_error(manifestPath,NSUTF8StringEncoding,null) 22 | manifestData = JSON.parse(manifestData) 23 | manifestExists = true 24 | } 25 | 26 | var displayList = ['Select one…'] 27 | var featureList = [ 28 | { 29 | "key" : "appleAuth", 30 | "name" : "Apple Authentication" 31 | }, 32 | { 33 | "key" : "backupEnabled", 34 | "name" : "Backup & Restore" 35 | }, 36 | { 37 | "key" : "genius", 38 | "name" : "Genius" 39 | }, 40 | { 41 | "key" : "googleAuth", 42 | "name" : "Google Authentication" 43 | }, 44 | { 45 | "key" : "manageStorage", 46 | "name" : "Manage Storage" 47 | }, 48 | { 49 | "key" : "photoPrint", 50 | "name" : "Photo Printing" 51 | }, 52 | { 53 | "key" : "privateFolder", 54 | "name" : "Private Folder" 55 | }, 56 | { 57 | "key" : "scanQRCode", 58 | "name" : "Scan QR Code (Kitamura)" 59 | }, 60 | { 61 | "key" : "tagSearch", 62 | "name" : "Tag & Search" 63 | } 64 | ] 65 | 66 | featureList.forEach(f => displayList.push(f.name)) 67 | 68 | if (!manifestData.featureLayers) manifestData.featureLayers = {} 69 | 70 | let addCount = 0 71 | let skipCount = 0 72 | 73 | let selectedFeature 74 | let selectionText = (selections.length == 1) ? `selection` : `${selections.length} selections` 75 | 76 | sketch.UI.getInputFromUser( 77 | `Which feature should the ${selectionText} be linked to?`, 78 | { 79 | type: sketch.UI.INPUT_TYPE.selection, 80 | possibleValues: displayList 81 | }, 82 | (err,val) => { 83 | if (err || val == displayList[0]) { 84 | // most likely the user canceled the input 85 | return 86 | } 87 | 88 | if (val) { 89 | selectedFeature = featureList.find(f => f.name == val) 90 | } 91 | } 92 | ) 93 | 94 | let manifestObject = manifestData.featureLayers[selectedFeature.key] 95 | 96 | if (!manifestObject) { 97 | manifestData.featureLayers[selectedFeature.key] = [] 98 | manifestObject = manifestData.featureLayers[selectedFeature.key] 99 | } 100 | 101 | selections.forEach(selection => { 102 | let layerToAdd = selection.id 103 | 104 | if (selection.type == 'SymbolInstance') { 105 | let selectedOverride = selection.overrides.find(o => o.selected) 106 | 107 | if (selectedOverride) { 108 | layerToAdd = `${layerToAdd}+${selectedOverride.id}` 109 | } 110 | } 111 | 112 | if (layerToAdd && !manifestObject.includes(layerToAdd)) { 113 | manifestObject.push(layerToAdd) 114 | addCount++ 115 | } else { 116 | skipCount++ 117 | } 118 | }) 119 | 120 | if (selectedFeature && manifestObject) { 121 | manifestData = JSON.stringify(manifestData) 122 | manifestData = NSString.stringWithFormat('%@',manifestData) 123 | 124 | if (!fileManager.fileExistsAtPath(manifestPath)) { 125 | fileManager.createDirectoryAtPath_withIntermediateDirectories_attributes_error(manifestPath.replace(manifestName,''),true,null,null) 126 | } 127 | 128 | if (addCount) manifestData.dataUsingEncoding_(NSUTF8StringEncoding).writeToFile_atomically_(manifestPath,true) 129 | 130 | let messageType = (manifestExists) ? 'updated' : 'created' 131 | let messageAdd = (addCount == 1) ? `${addCount} layer linked` : `${addCount} layers linked` 132 | let messageSkip = (skipCount) ? `, ${skipCount} layers skipped (already existed)` : '' 133 | 134 | sketch.UI.message(`Manifest ${messageType}: ${messageAdd}${messageSkip} to ${selectedFeature.name}`) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Artboards/create-slice-around.js: -------------------------------------------------------------------------------- 1 | @import '../library.js'; 2 | 3 | var document = MSDocument.currentDocument(); 4 | var page = document.currentPage(); 5 | 6 | var defaultSettings = {}; 7 | defaultSettings.sliceType = 1; 8 | defaultSettings.sliceMargin = '100'; 9 | defaultSettings.exportScales = ['.5x','1x','2x','3x']; 10 | defaultSettings.exportScale = 0; 11 | defaultSettings.exportFormats = ['JPG','PDF','PNG']; 12 | defaultSettings.exportFormat = 1; 13 | 14 | var sliceAll = function(context) { 15 | var artboards = page.artboards(); 16 | 17 | createSlice(artboards,0); 18 | } 19 | 20 | var sliceSelected = function(context) { 21 | var selection = context.selection; 22 | 23 | if (!selection.length) { 24 | sketch.UI.alert('Create Slice Around Selected…','Select at least one artboard.'); 25 | return false; 26 | } 27 | 28 | createSlice(selection,1); 29 | } 30 | 31 | function createSlice(scope,type) { 32 | var sliceName = (type == 0) ? 'Artboards' : 'Selections'; 33 | var sliceScope = (type == 0) ? 'all' : 'selected'; 34 | var sliceSettings = getSliceSettings(); 35 | 36 | if (!sliceSettings) return false; 37 | 38 | var selectionSize = getSelectionSize(scope); 39 | 40 | var sliceLayer = MSSliceLayer.new(); 41 | sliceLayer.setName(sliceName); 42 | sliceLayer.setBackgroundColor(MSColor.colorWithRed_green_blue_alpha(239/255,239/255,239/255,1.0)); 43 | sliceLayer.frame().setX(selectionSize.minX - sliceSettings.sliceMargin); 44 | sliceLayer.frame().setY(selectionSize.minY - sliceSettings.sliceMargin); 45 | sliceLayer.frame().setWidth(selectionSize.width + (sliceSettings.sliceMargin * 2)); 46 | sliceLayer.frame().setHeight(selectionSize.height + (sliceSettings.sliceMargin * 2)); 47 | 48 | var sliceExport = sliceLayer.exportOptions().addExportFormat(); 49 | sliceExport.setScale(defaultSettings.exportScales[sliceSettings.exportScale].slice(0,-1)); 50 | sliceExport.setFileFormat(defaultSettings.exportFormats[sliceSettings.exportFormat].toLowerCase()); 51 | 52 | page.addLayers([sliceLayer]); 53 | 54 | MSLayerMovement.moveToBack([sliceLayer]); 55 | 56 | sketch.UI.message('Slice created around ' + sliceScope + ' artboards'); 57 | } 58 | 59 | function getSliceSettings() { 60 | var userSettings = getDocumentSettings(defaultSettings); 61 | 62 | var alert = NSAlert.alloc().init(); 63 | alert.setMessageText('Create Artboard Slice…'); 64 | alert.icon = getPluginAlertIcon(); 65 | 66 | var alertContent = NSView.alloc().init(); 67 | alertContent.setFlipped(true); 68 | 69 | var sliceMarginLabel = createAlertLabel('Slice margin:',NSMakeRect(0,0,alertWidth,alertLabelHeight)); 70 | alertContent.addSubview(sliceMarginLabel); 71 | 72 | var sliceMarginValue = createAlertField(userSettings.sliceMargin,NSMakeRect(0,getMaxYOfView(alertContent),60,alertFieldHeight)); 73 | alertContent.addSubview(sliceMarginValue); 74 | 75 | var exportScaleLabel = createAlertLabel('Export scale:',NSMakeRect(0,getMaxYOfView(alertContent,alertItemPadding),alertWidth,alertLabelHeight)); 76 | alertContent.addSubview(exportScaleLabel); 77 | 78 | var exportScaleSelect = createAlertSelect(userSettings.exportScales,userSettings.exportScale,NSMakeRect(0,getMaxYOfView(alertContent),100,alertSelectHeight)); 79 | alertContent.addSubview(exportScaleSelect); 80 | 81 | var exportFormatLabel = createAlertLabel('Export format:',NSMakeRect(0,getMaxYOfView(alertContent,alertItemPadding),alertWidth,alertLabelHeight)); 82 | alertContent.addSubview(exportFormatLabel); 83 | 84 | var exportFormatSelect = createAlertSelect(userSettings.exportFormats,userSettings.exportFormat,NSMakeRect(0,getMaxYOfView(alertContent),100,alertSelectHeight)); 85 | alertContent.addSubview(exportFormatSelect); 86 | 87 | alertContent.setFrame(NSMakeRect(0,0,alertWidth,getMaxYOfView(alertContent))); 88 | 89 | alert.setAccessoryView(alertContent); 90 | 91 | var submit = alert.addButtonWithTitle('OK'); 92 | var cancel = alert.addButtonWithTitle('Cancel'); 93 | 94 | var reset = alert.addButtonWithTitle('Defaults'); 95 | reset.setAction('callAction:'); 96 | reset.setCOSJSTargetFunction(function() { 97 | sliceMarginValue.setStringValue(defaultSettings.sliceMargin); 98 | exportScaleSelect.selectItemAtIndex(defaultSettings.exportScale); 99 | exportFormatSelect.selectItemAtIndex(defaultSettings.exportFormat); 100 | }); 101 | 102 | setKeyOrder(alert,[ 103 | sliceMarginValue, 104 | exportScaleSelect, 105 | exportFormatSelect, 106 | submit 107 | ]); 108 | 109 | var responseCode = alert.runModal(); 110 | 111 | if (responseCode == 1000) { 112 | var sliceSettings = {}; 113 | sliceSettings.sliceMargin = sliceMarginValue.stringValue(); 114 | sliceSettings.exportScale = exportScaleSelect.indexOfSelectedItem(); 115 | sliceSettings.exportFormat = exportFormatSelect.indexOfSelectedItem(); 116 | 117 | setDocumentSettings(sliceSettings); 118 | 119 | return sliceSettings; 120 | } else return false; 121 | } 122 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Text Styles/convert-to-japanese-style.js: -------------------------------------------------------------------------------- 1 | const sketch = require("sketch") 2 | const document = sketch.getSelectedDocument() 3 | const selections = document.selectedLayers 4 | const sharedTextStyles = document.sharedTextStyles 5 | 6 | var onRun = function(context) { 7 | let errorCount = 0 8 | let skipCount = 0 9 | let updateCount = 0 10 | 11 | let prefixes = ["JP"] 12 | let prefix = prefixes[0] + "/" 13 | 14 | if (!selections.length) { 15 | sketch.UI.alert(`Convert to Japanese Style`,`Nothing is selected.`) 16 | 17 | return 18 | } 19 | 20 | selections.forEach(selection => { 21 | if (selection.layers) { 22 | processGroup(selection.layers) 23 | } else { 24 | processLayer(selection) 25 | } 26 | }) 27 | 28 | function processGroup(layers,loop) { 29 | if (!loop) { 30 | for (var i = layers.length - 1; i >= 0; i--) { 31 | processLayer(layers[i]) 32 | } 33 | } else { 34 | layers.forEach(layer => processLayer(layer)) 35 | } 36 | } 37 | 38 | function processLayer(layer) { 39 | // Process symbol instances 40 | if (layer.type == "SymbolInstance") { 41 | // Get textStyle overrides for selection 42 | let textStyleOverrides = layer.overrides.filter(o => o.editable && o.property == "textStyle") 43 | 44 | // Iterate textStyle overrides 45 | textStyleOverrides.forEach(o => { 46 | // Get shared style of textStyle override 47 | let sharedStyle = sharedTextStyles.find(s => s.id === o.value) 48 | 49 | // Bail out if shared style not found 50 | if (!sharedStyle) { 51 | console.error(`Unable to find the shared style for the text style override applied to ${o.affectedLayer.name}, could be orphan or belong to a disabled library`) 52 | 53 | // Incremenet error count 54 | errorCount++ 55 | 56 | return 57 | } 58 | 59 | // Get library of shared style 60 | let sharedStyleLibrary = sharedStyle.getLibrary() 61 | 62 | // Bail out if shared style library not found 63 | if (!sharedStyleLibrary) { 64 | console.error(`Unable to retrieve the library for the text style override ${sharedStyle.name} applied to ${o.affectedLayer.name}, could be a disabled library`) 65 | 66 | // Incremenet error count 67 | errorCount++ 68 | 69 | return 70 | } 71 | 72 | // Get text styles from shared library 73 | let libraryTextStyles = sharedStyleLibrary.getImportableTextStyleReferencesForDocument(document) 74 | 75 | // Get shared style of JP textStyle 76 | let sharedStyleJP = libraryTextStyles.find(s => s.name === prefix + sharedStyle.name) 77 | 78 | // Bail out if JP shared style not found, or JP style already applied 79 | if (!sharedStyleJP || sharedStyle.id === sharedStyleJP.id) { 80 | // Incremenet skip count 81 | skipCount++ 82 | 83 | return 84 | } 85 | 86 | // Import shared style JP textStyle 87 | let sharedStyleJPReference = sharedStyleJP.import() 88 | 89 | // Apply JP textStyle override 90 | layer.setOverrideValue(o,sharedStyleJPReference.id) 91 | 92 | // Incremenet update count 93 | updateCount++ 94 | }) 95 | } 96 | // Process text layers 97 | else if (layer.type == "Text") { 98 | // Get shared style of text layer 99 | let sharedStyle = layer.sharedStyle 100 | 101 | // Bail out if shared style not found 102 | if (!sharedStyle) { 103 | console.error(`Unable to find a shared style for the text layer - ${layer.name} (${layer.id})`) 104 | 105 | // Incremenet error count 106 | errorCount++ 107 | 108 | return 109 | } 110 | 111 | // Get library of shared style 112 | let sharedStyleLibrary = sharedStyle.getLibrary() 113 | 114 | // Bail out if shared style library not found 115 | if (!sharedStyleLibrary) { 116 | console.error(`Unable to retrieve the library for the text style override ${sharedStyle.name} applied to ${layer.name}, could be a disabled library`) 117 | 118 | // Incremenet error count 119 | errorCount++ 120 | 121 | return 122 | } 123 | 124 | // Get text styles from shared library 125 | let libraryTextStyles = sharedStyleLibrary.getImportableTextStyleReferencesForDocument(document) 126 | 127 | // Get shared style of JP textStyle 128 | let sharedStyleJP = libraryTextStyles.find(s => s.name === prefix + sharedStyle.name) 129 | 130 | // Bail out if JP shared style not found, or JP style already applied 131 | if (!sharedStyleJP || sharedStyle.id === sharedStyleJP.id) { 132 | // Incremenet skip count 133 | skipCount++ 134 | 135 | return 136 | } 137 | 138 | // Import shared style JP textStyle 139 | let sharedStyleJPReference = sharedStyleJP.import() 140 | 141 | // Apply JP textStyle 142 | layer.sharedStyle = sharedStyleJPReference 143 | 144 | // Sync with JP textStyle 145 | layer.style.syncWithSharedStyle(sharedStyleJPReference) 146 | 147 | // Incremenet update count 148 | updateCount++ 149 | } 150 | 151 | // If symbol instance has layers, process them too 152 | if (layer.layers) processGroup(layer.layers,1) 153 | } 154 | 155 | // Output results to user 156 | sketch.UI.message(`${updateCount} styles updated, ${skipCount} styles skipped, ${errorCount} styles had problems`) 157 | } -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/library.js: -------------------------------------------------------------------------------- 1 | @import 'delegate.js'; 2 | 3 | // Shared variables 4 | var sketch = require('sketch'); 5 | 6 | // Alert window variables 7 | var alertWidth = 300; 8 | var alertItemPadding = 8; 9 | var alertFieldHeight = 22; 10 | var alertLabelHeight = 20; 11 | var alertSelectHeight = 26; 12 | 13 | function createAlertField(value,frame) { 14 | var textField = NSTextField.alloc().initWithFrame(frame); 15 | 16 | textField.setStringValue(value); 17 | textField.setFont(NSFont.systemFontOfSize(13)); 18 | textField.setBezeled(true); 19 | 20 | return textField; 21 | } 22 | 23 | function createAlertLabel(text,frame) { 24 | var textField = NSTextField.alloc().initWithFrame(frame); 25 | 26 | textField.setStringValue(text); 27 | textField.setFont(NSFont.systemFontOfSize(12)); 28 | textField.setBezeled(false); 29 | textField.setDrawsBackground(false); 30 | textField.setEditable(false); 31 | textField.setSelectable(false); 32 | 33 | return textField; 34 | } 35 | 36 | function createAlertLabelBold(text,frame) { 37 | var textField = NSTextField.alloc().initWithFrame(frame); 38 | 39 | textField.setStringValue(text); 40 | textField.setFont(NSFont.boldSystemFontOfSize(12)) 41 | textField.setBezeled(false); 42 | textField.setDrawsBackground(false); 43 | textField.setEditable(false); 44 | textField.setSelectable(false); 45 | 46 | return textField; 47 | } 48 | 49 | function createAlertPopupButton(items,select,frame) { 50 | var button = NSPopUpButton.alloc().initWithFrame(frame); 51 | 52 | button.addItemsWithTitles(items); 53 | button.selectItemAtIndex(select); 54 | //button.setFont(NSFont.systemFontOfSize(12)); 55 | 56 | return button; 57 | } 58 | 59 | function createAlertRadios(options,selected,format,x,y) { 60 | var rows = options.length; 61 | var columns = 1; 62 | var buttonMatrixWidth = alertWidth; 63 | var buttonCellWidth = buttonMatrixWidth; 64 | var x = (x) ? x : 0; 65 | var y = (y) ? y : 0; 66 | 67 | if (format && format != 0) { 68 | rows = options.length / 2; 69 | columns = 2; 70 | buttonCellWidth = buttonMatrixWidth / columns; 71 | } 72 | 73 | var buttonCell = NSButtonCell.alloc().init(); 74 | 75 | buttonCell.setButtonType(NSRadioButton); 76 | 77 | var buttonMatrix = NSMatrix.alloc().initWithFrame_mode_prototype_numberOfRows_numberOfColumns( 78 | NSMakeRect(x,y,buttonMatrixWidth,rows * 20), 79 | NSRadioModeMatrix, 80 | buttonCell, 81 | rows, 82 | columns 83 | ); 84 | 85 | buttonMatrix.setCellSize(NSMakeSize(buttonCellWidth,20)); 86 | 87 | for (i = 0; i < options.length; i++) { 88 | buttonMatrix.cells().objectAtIndex(i).setTitle(options[i]); 89 | buttonMatrix.cells().objectAtIndex(i).setTag(i); 90 | } 91 | 92 | buttonMatrix.selectCellAtRow_column(selected,0); 93 | 94 | return buttonMatrix; 95 | } 96 | 97 | function createAlertSelect(items,select,frame) { 98 | var comboBox = NSComboBox.alloc().initWithFrame(frame); 99 | var select = (select > -1) ? select : 0; 100 | 101 | comboBox.addItemsWithObjectValues(items); 102 | comboBox.selectItemAtIndex(select); 103 | comboBox.setNumberOfVisibleItems(16); 104 | comboBox.setCompletes(1); 105 | 106 | return comboBox; 107 | } 108 | 109 | function createAlertWindow(context,name,text) { 110 | var alertWindow = COSAlertWindow.new(); 111 | 112 | var iconPath = context.plugin.urlForResourceNamed('icon.png').path(); 113 | var icon = NSImage.alloc().initByReferencingFile(iconPath); 114 | 115 | alertWindow.setIcon(icon); 116 | alertWindow.setMessageText(name); 117 | 118 | if (text) { alertWindow.setInformativeText(text); } 119 | 120 | return alertWindow; 121 | } 122 | 123 | function getDocumentSettings(settings) { 124 | var document = sketch.getSelectedDocument(); 125 | var settings = Object.assign({},settings); 126 | 127 | for (key in settings) { 128 | var value = sketch.Settings.documentSettingForKey(document,key); 129 | 130 | if (value != null) settings[key] = value; 131 | } 132 | 133 | return settings; 134 | } 135 | 136 | function getMaxYOfView(view,add) { 137 | var add = (add) ? add : 0; 138 | 139 | return CGRectGetMaxY(view.subviews().lastObject().frame()) + add; 140 | } 141 | 142 | function getPluginAlertIcon() { 143 | if (__command.pluginBundle() && __command.pluginBundle().alertIcon()) { 144 | return __command.pluginBundle().alertIcon(); 145 | } 146 | 147 | return NSImage.imageNamed('plugins'); 148 | } 149 | 150 | function getSelectionSize(selections) { 151 | let minX, minY, maxX, maxY 152 | 153 | minX = minY = Number.MAX_VALUE 154 | maxX = maxY = -0xFFFFFFFF 155 | 156 | selections.forEach(selection => { 157 | let rect = selection.rect() 158 | 159 | minX = Math.min(minX,CGRectGetMinX(rect)) 160 | minY = Math.min(minY,CGRectGetMinY(rect)) 161 | maxX = Math.max(maxX,CGRectGetMaxX(rect)) 162 | maxY = Math.max(maxY,CGRectGetMaxY(rect)) 163 | }) 164 | 165 | return { 166 | width: maxX - minX, 167 | height: maxY - minY, 168 | minX: minX, 169 | minY: minY 170 | } 171 | } 172 | 173 | function performAction(action) { 174 | var controller = document.sketchObject.actionsController(); 175 | 176 | controller.actionForID(action).doPerformAction(nil); 177 | } 178 | 179 | function setDocumentSettings(settings) { 180 | var document = sketch.getSelectedDocument(); 181 | 182 | for (key in settings) { 183 | sketch.Settings.setDocumentSettingForKey(document,key,settings[key]); 184 | } 185 | } 186 | 187 | function setKeyOrder(alert,order) { 188 | for (var i = 0; i < order.length; i++) { 189 | var thisItem = order[i]; 190 | var nextItem = order[i+1]; 191 | 192 | if (nextItem) thisItem.setNextKeyView(nextItem); 193 | } 194 | 195 | alert.window().setInitialFirstResponder(order[0]); 196 | } 197 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/Customization/add-strings-to-manifest.js: -------------------------------------------------------------------------------- 1 | var onRun = function(context) { 2 | const sketch = require('sketch') 3 | const document = sketch.getSelectedDocument() 4 | const selections = document.selectedLayers.layers 5 | 6 | if (!selections.length) { 7 | sketch.UI.alert(`Add Translation Text to Manifest`,`Select a text layer or symbol instance with a text override.`) 8 | return 9 | } 10 | 11 | var fileManager = NSFileManager.defaultManager() 12 | 13 | var manifestName = 'manifest.json' 14 | var manifestPath = document.path.replace(escape(document.sketchObject.displayName()),manifestName) 15 | manifestPath = manifestPath.replaceAll('%20',' ') 16 | var manifestData = {} 17 | var manifestExists = false 18 | 19 | if (fileManager.fileExistsAtPath(manifestPath)) { 20 | manifestData = NSString.stringWithContentsOfFile_encoding_error(manifestPath,NSUTF8StringEncoding,null) 21 | manifestData = JSON.parse(manifestData) 22 | manifestExists = true 23 | } 24 | 25 | if (!manifestData.strings) manifestData.strings = [] 26 | 27 | var addCount = 0 28 | var matchCount = 0 29 | var skipCount = 0 30 | 31 | const addString = (platform,parentArtboard,parentGroup,stringObjectID,stringExample,stringKey,stringReplace) => { 32 | let string = { 33 | platform : platform, 34 | parentArtboard : parentArtboard, 35 | parentGroup : parentGroup, 36 | stringObjectID : stringObjectID, 37 | stringExample : stringExample, 38 | stringKey : stringKey 39 | } 40 | 41 | if (stringReplace) string.stringReplace = stringReplace 42 | 43 | manifestData.strings.push(string) 44 | 45 | console.log(`${stringObjectID} (${stringExample}) added to the manifest`) 46 | 47 | addCount++ 48 | } 49 | 50 | const skipString = (stringObjectID,stringExample) => { 51 | console.log(`${stringObjectID} (${stringExample}) not added to the manifest (already exists)`) 52 | 53 | skipCount++ 54 | } 55 | 56 | selections.forEach(selection => { 57 | if (selection.layers) { 58 | processGroup(selection.layers) 59 | } else { 60 | processLayer(selection) 61 | } 62 | }) 63 | 64 | function processGroup(layers,loop) { 65 | if (!loop) { 66 | for (var i = layers.length - 1; i >= 0; i--) { 67 | processLayer(layers[i]) 68 | } 69 | } else { 70 | layers.forEach(layer => processLayer(layer)) 71 | } 72 | } 73 | 74 | function processLayer(layer) { 75 | if (layer.type == 'Text' || layer.type == 'SymbolInstance' && layer.overrides.filter(o => o.property === 'stringValue' && o.editable && o.value != ' ').length) { 76 | let parentPage = layer.sketchObject.parentPage().name() 77 | let parentArtboard = `${layer.sketchObject.parentArtboard().name()} (${layer.sketchObject.parentArtboard().objectID()})` 78 | let parentGroup = (layer.sketchObject.parentGroup()) ? `${layer.sketchObject.parentGroup().name()} (${layer.sketchObject.parentGroup().objectID()})` : '' 79 | 80 | let platform = '' 81 | 82 | if (parentPage.includes('Android') || parentArtboard.includes('Android')) { 83 | platform = 'android' 84 | } else if (parentPage.includes('iOS') || parentArtboard.includes('iPad') || parentArtboard.includes('iPhone')) { 85 | platform = 'ios' 86 | } 87 | 88 | let overrides = (layer.overrides) ? layer.overrides.filter(o => o.property === 'stringValue' && o.editable && o.value != ' ') : null 89 | 90 | if (overrides) { 91 | overrides.reverse().forEach(override => { 92 | let stringObjectID = String(layer.id + '+' + override.id) 93 | let stringExample = override.value 94 | let stringKey = '' 95 | let stringReplace 96 | 97 | let stringEntry = manifestData.strings.find(s => s.stringObjectID == stringObjectID) 98 | 99 | if (!stringEntry) { 100 | if (platform != '') { 101 | let stringMatch = manifestData.strings.find(s => s.platform == platform && s.stringObjectID.includes(override.id) && s.stringExample == stringExample) 102 | 103 | if (stringMatch && stringMatch.stringKey) { 104 | stringKey = stringMatch.stringKey 105 | 106 | if (stringMatch.stringReplace) stringReplace = stringMatch.stringReplace 107 | 108 | matchCount++ 109 | } 110 | } 111 | 112 | addString(platform,parentArtboard,parentGroup,stringObjectID,stringExample,stringKey,stringReplace) 113 | } else { 114 | stringEntry.platform = platform 115 | stringEntry.parentArtboard = parentArtboard 116 | stringEntry.parentGroup = parentGroup 117 | stringEntry.stringExample = stringExample 118 | 119 | skipString(stringObjectID,stringExample) 120 | } 121 | }) 122 | } else { 123 | let stringObjectID = String(layer.id) 124 | let stringExample = layer.text 125 | let stringKey = '' 126 | 127 | let stringEntry = manifestData.strings.find(s => s.stringObjectID == stringObjectID) 128 | 129 | if (!stringEntry) { 130 | addString(platform,parentArtboard,parentGroup,stringObjectID,stringExample,stringKey) 131 | } else { 132 | stringEntry.platform = platform 133 | stringEntry.parentArtboard = parentArtboard 134 | stringEntry.parentGroup = parentGroup 135 | stringEntry.stringExample = stringExample 136 | 137 | skipString(stringObjectID,stringExample) 138 | } 139 | } 140 | } 141 | 142 | if (layer.layers) processGroup(layer.layers,1) 143 | } 144 | 145 | manifestData = JSON.stringify(manifestData) 146 | manifestData = NSString.stringWithFormat('%@',manifestData) 147 | 148 | if (!fileManager.fileExistsAtPath(manifestPath)) { 149 | fileManager.createDirectoryAtPath_withIntermediateDirectories_attributes_error(manifestPath.replace(manifestName,''),true,null,null) 150 | } 151 | 152 | manifestData.dataUsingEncoding_(NSUTF8StringEncoding).writeToFile_atomically_(manifestPath,true) 153 | 154 | var messageType = (manifestExists) ? 'updated' : 'created' 155 | var messageAdd = (addCount == 1) ? `${addCount} string added` : `${addCount} strings added` 156 | var messageMatch = (matchCount) ? ` (${matchCount} pre-populated)` : '' 157 | var messageSkip = (skipCount) ? `, ${skipCount} skipped (already existed)` : '' 158 | 159 | sketch.UI.message(`Manifest ${messageType}: ${messageAdd}${messageMatch}${messageSkip}`) 160 | } 161 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Resources/strings/en.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | general-button-ok 7 | OK 8 | general-button-cancel 9 | Cancel 10 | general-save-failed 11 | Unable to save settings. 12 | 13 | section-link-plugin 14 | Link Section Title 15 | section-link-problem 16 | Select one section title and one artboard to link. The section title cannot be an artboard itself. 17 | section-link-complete 18 | section title is now linked to 19 | section-unlink-plugin 20 | Unlink Section Title 21 | section-unlink-problem 22 | Select a section title to unlink. 23 | section-unlink-complete 24 | section title is no longer linked to 25 | section-unlinks-complete 26 | section titles unlinked 27 | section-titles-selected 28 | section title(s) selected 29 | section-titles-updated 30 | Section titles updated 31 | section-titles-updated-unlinked 32 | section title(s) were unlinked due to missing artboards 33 | section-settings-plugin 34 | Section Title Settings 35 | section-settings-width 36 | Width (leave blank to use existing size): 37 | section-settings-offsetX 38 | Horizontal offset: 39 | section-settings-offsetY 40 | Vertical offset: 41 | 42 | title-preclude-plugin 43 | Preclude Selected Artboards 44 | title-preclude-problem 45 | Select artboard(s) to mark as precluded from Create Artboard Titles. 46 | title-preclude-complete 47 | is now precluded from Create Artboard Titles 48 | title-precludes-complete 49 | artboards are now precluded from Create Artboard Titles 50 | title-include-plugin 51 | Include Selected Artboards 52 | title-include-problem 53 | Select artboard(s) to mark as included in Create Artboard Titles. 54 | title-include-complete 55 | is now included in Create Artboard Titles 56 | title-includes-complete 57 | artboards are now included in Create Artboard Titles 58 | title-create-plugin 59 | Create Artboard Titles 60 | title-create-problem 61 | There are no artboards on the current page, or all artboards on the page are precluded, therefore no titles to create. 62 | title-create-complete 63 | Screen titles created 64 | 65 | description-link-plugin 66 | Link Artboard Description 67 | description-link-problem 68 | Select one artboard description (a text layer with \"Wireframe/Artboard Description\" style applied) and one artboard to link. 69 | description-link-complete 70 | is now linked to 71 | description-unlink-plugin 72 | Unlink Artboard Description 73 | description-unlink-problem 74 | Select an artboard description to unlink. 75 | description-unlink-complete 76 | artboard description is no longer linked to 77 | description-unlinks-complete 78 | artboard description(s) unlinked 79 | description-selects-complete 80 | artboard description(s) selected 81 | description-set-plugin 82 | Add/Edit Artboard Description 83 | description-set-problem 84 | Select one artboard to add/edit a description. 85 | description-set-complete 86 | Artboard description added 87 | description-update-complete 88 | Artboard description updated 89 | description-updates-complete 90 | artboard description(s) updated 91 | description-updates-complete-unlinked 92 | artboard description(s) were unlinked due to missing artboards 93 | description-settings-title 94 | Artboard Description Settings 95 | description-settings-width 96 | Width (leave blank to use artboard width): 97 | description-settings-position 98 | Position: 99 | description-settings-offsetX 100 | Horizontal offset: 101 | description-settings-offsetY 102 | Vertical offset: 103 | 104 | layout-preclude-plugin 105 | Preclude Selected Artboards 106 | layout-preclude-problem 107 | Select artboard(s) to mark as precluded from Layout Artboards. 108 | layout-preclude-complete 109 | is now precluded from Layout Artboards 110 | layout-precludes-complete 111 | artboards are now precluded from Layout Artboards 112 | layout-preclude-page-complete 113 | will no longer auto-layout with Layout Artboards 114 | layout-include-plugin 115 | Include Selected Artboards 116 | layout-include-problem 117 | Select artboard(s) to mark as included in Layout Artboards. 118 | layout-include-complete 119 | is now included in Layout Artboards 120 | layout-includes-complete 121 | artboards are now included in Layout Artboards 122 | layout-include-page-complete 123 | will now auto-layout with Layout Artboards 124 | layout-artboards-plugin 125 | Layout Artboards 126 | layout-artboards-problem 127 | There are no artboards to layout. 128 | layout-artboards-complete 129 | Artboard layout complete 130 | layout-settings-title 131 | Artboard Layout Settings 132 | layout-settings-artboard-count 133 | Artboards per row: 134 | layout-settings-layout-type 135 | Layout type: 136 | layout-settings-sort-type 137 | Sort type: 138 | layout-settings-spacing-horizontal 139 | Horizontal spacing: 140 | layout-settings-spacing-vertical 141 | Vertical spacing: 142 | 143 | annotation-designate-plugin 144 | Designate Selected Annotations 145 | annotation-designate-problem 146 | Select text layer(s) to designate as annotation(s). This is required in order to link an annotation to an artboard. 147 | annotation-designate-complete 148 | is now designated as an annotation 149 | annotation-designates-complete 150 | text layers are now designated as annotations 151 | annotation-link-plugin 152 | Link Artboard Annotation 153 | annotation-link-problem-selection 154 | Select one annotation and one artboard or object to link. 155 | annotation-link-problem-textlayer 156 | Designate one of the selections as an annotation first. 157 | annotation-link-complete 158 | is now linked to 159 | annotation-unlink-complete 160 | artboard annotation is no longer linked to 161 | annotation-update-complete 162 | annotation(s) updated 163 | annotation-update-complete-unlinked 164 | annotation(s) were unlinked due to missing artboards 165 | annotation-settings 166 | Layer Annotation Settings 167 | annotation-settings-automatic 168 | 171 | 172 | wireframe-add-complete 173 | Wireframe slice created and included in Export Wireframes 174 | wireframe-include-plugin 175 | Include Selected Slice 176 | wireframe-include-problem 177 | Select one slice to include in Export Wireframes. 178 | wireframe-include-complete 179 | is now included in Export Wireframes 180 | wireframe-preclude-plugin 181 | Preclude Selected Slice 182 | wireframe-preclude-problem 183 | Select one slice to preclude from Export Wireframes. 184 | wireframe-preclude-complete 185 | is now precluded from Export Wireframes 186 | wireframe-export-plugin 187 | Export Wireframes 188 | wireframe-export-problem 189 | No wireframes were found to export. 190 | wireframe-export-complete 191 | wireframes were exported to your Downloads directory 192 | wireframe-export-intro 193 | Select which wireframes to export... 194 | wireframe-export-outro 195 | The export process may take some time, be patient. 196 | 197 | 198 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "SNCR", 3 | "description" : "A plugin of Sketch actions which are useful in the workflows of the designers at Synchronoss Technologies Inc.", 4 | "author" : "Jason Burns", 5 | "homepage" : "https://github.com/sonburn/sncr-sketch-plugin", 6 | "version" : "1.21", 7 | "identifier" : "com.sncr.sketch", 8 | "appcast" : "https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/master/appcast.xml", 9 | "icon" : "icon.png", 10 | "suppliesData": true, 11 | "commands" : [ 12 | { 13 | "name" : "Link Selected Title and Artboard", 14 | "identifier" : "sections-link", 15 | "script" : "handlers.js", 16 | "handler" : "sectionsLink", 17 | "icon" : "icons/sections-link.png" 18 | }, 19 | { 20 | "name" : "Unlink Selected Titles", 21 | "identifier" : "sections-unlink", 22 | "script" : "handlers.js", 23 | "handler" : "sectionsUnlink", 24 | "icon" : "icons/sections-unlink.png" 25 | }, 26 | { 27 | "name" : "Update All Linked Titles on Page", 28 | "identifier" : "sections-update", 29 | "script" : "handlers.js", 30 | "handler" : "sectionsUpdate", 31 | "icon" : "icons/sections-update.png" 32 | }, 33 | { 34 | "name" : "Settings…", 35 | "identifier" : "sections-settings", 36 | "script" : "handlers.js", 37 | "handler" : "sectionsSettings" 38 | }, 39 | { 40 | "name" : "Create Titles for Artboards on Page", 41 | "identifier" : "titles-create", 42 | "script" : "handlers.js", 43 | "handler" : "titlesCreate", 44 | "icon" : "icons/titles-create.png" 45 | }, 46 | { 47 | "name" : "Include Selected Artboards", 48 | "identifier" : "titles-include", 49 | "script" : "handlers.js", 50 | "handler" : "titlesInclude", 51 | "icon" : "icons/titles-include.png" 52 | }, 53 | { 54 | "name" : "Preclude Selected Artboards", 55 | "identifier" : "titles-preclude", 56 | "script" : "handlers.js", 57 | "handler" : "titlesPreclude", 58 | "icon" : "icons/titles-preclude.png" 59 | }, 60 | { 61 | "name" : "Settings…", 62 | "identifier" : "titles-settings", 63 | "script" : "handlers.js", 64 | "handler" : "titlesSettings" 65 | }, 66 | { 67 | "name" : "Add/Edit Description on Selected Artboard", 68 | "identifier" : "descriptions-set", 69 | "script" : "handlers.js", 70 | "handler" : "descriptionsSet", 71 | "icon" : "icons/descriptions-set.png" 72 | }, 73 | { 74 | "name" : "Link Selected Description and Artboard", 75 | "identifier" : "descriptions-link", 76 | "script" : "handlers.js", 77 | "handler" : "descriptionsLink", 78 | "icon" : "icons/descriptions-link.png" 79 | }, 80 | { 81 | "name" : "Unlink Selected Descriptions", 82 | "identifier" : "descriptions-unlink", 83 | "script" : "handlers.js", 84 | "handler" : "descriptionsUnlink", 85 | "icon" : "icons/descriptions-unlink.png" 86 | }, 87 | { 88 | "name" : "Update All Linked Descriptions on Page", 89 | "identifier" : "descriptions-update", 90 | "script" : "handlers.js", 91 | "handler" : "descriptionsUpdate", 92 | "icon" : "icons/descriptions-update.png" 93 | }, 94 | { 95 | "name" : "Settings…", 96 | "identifier" : "descriptions-settings", 97 | "script" : "handlers.js", 98 | "handler" : "descriptionsSettings" 99 | }, 100 | { 101 | "name" : "Layout Artboards on Page", 102 | "identifier" : "layout-update", 103 | "script" : "handlers.js", 104 | "handlers" : { 105 | "actions" : { 106 | "run" : "layoutUpdate", 107 | "LayersMoved.finish" : "layoutUpdate", 108 | "ResizeArtboardToFit.finish" : "layoutUpdate" 109 | } 110 | }, 111 | "icon" : "icons/layout-update.png" 112 | }, 113 | { 114 | "name" : "Include Selected Artboards", 115 | "identifier" : "layout-includeSelected", 116 | "script" : "handlers.js", 117 | "handler" : "layoutIncludeSelected", 118 | "icon" : "icons/layout-include.png" 119 | }, 120 | { 121 | "name" : "Preclude Selected Artboards", 122 | "identifier" : "layout-precludeSelected", 123 | "script" : "handlers.js", 124 | "handler" : "layoutPrecludeSelected", 125 | "icon" : "icons/layout-preclude.png" 126 | }, 127 | { 128 | "name" : "Enable Automatic Layout for Page", 129 | "identifier" : "layout-includePage", 130 | "script" : "handlers.js", 131 | "handler" : "layoutIncludePage", 132 | "icon" : "icons/layout-include-page.png" 133 | }, 134 | { 135 | "name" : "Disable Automatic Layout for Page", 136 | "identifier" : "layout-precludePage", 137 | "script" : "handlers.js", 138 | "handler" : "layoutPrecludePage", 139 | "icon" : "icons/layout-preclude-page.png" 140 | }, 141 | { 142 | "name" : "Settings for Page…", 143 | "identifier" : "layout-settings", 144 | "script" : "handlers.js", 145 | "handler" : "layoutSettings" 146 | }, 147 | { 148 | "name" : "Create Annotations for Selections", 149 | "identifier" : "annotations-create", 150 | "script" : "handlers.js", 151 | "handlers" : { 152 | "actions" : { 153 | "run" : "annotationsCreate", 154 | "AddFlow.finish" : "annotationsCreate" 155 | } 156 | }, 157 | "icon" : "icons/annotations-create.png" 158 | }, 159 | { 160 | "name" : "Designate Selected Layers as Annotations", 161 | "identifier" : "annotations-designate", 162 | "script" : "handlers.js", 163 | "handler" : "annotationsDesignate", 164 | "icon" : "icons/annotations-designate.png" 165 | }, 166 | { 167 | "name" : "Link Selected Annotation and Object/Artboard", 168 | "identifier" : "annotations-link", 169 | "script" : "handlers.js", 170 | "handler" : "annotationsLink", 171 | "icon" : "icons/annotations-link.png" 172 | }, 173 | { 174 | "name" : "Force Selected Annotations Position to Right", 175 | "identifier" : "annotations-position", 176 | "script" : "handlers.js", 177 | "handler" : "annotationsPosition" 178 | }, 179 | { 180 | "name" : "Update All Linked Annotations on Page", 181 | "identifier" : "annotations-update", 182 | "script" : "handlers.js", 183 | "handler" : "annotationsUpdate", 184 | "icon" : "icons/annotations-update.png" 185 | }, 186 | { 187 | "name" : "Settings…", 188 | "identifier" : "annotations-settings", 189 | "script" : "handlers.js", 190 | "handler" : "annotationsSettings" 191 | }, 192 | { 193 | "name" : "Add/Edit Condition for Selected Layer", 194 | "identifier" : "conditions-set", 195 | "script" : "handlers.js", 196 | "handler" : "conditionsSet" 197 | }, 198 | { 199 | "name" : "Add Wireframe Slice", 200 | "identifier" : "wireframes-add", 201 | "script" : "handlers.js", 202 | "handler" : "wireframesAdd", 203 | "icon" : "icons/wireframes-add.png" 204 | }, 205 | { 206 | "name" : "Include Selected Slice", 207 | "identifier" : "wireframes-include", 208 | "script" : "handlers.js", 209 | "handler" : "wireframesInclude", 210 | "icon" : "icons/wireframes-include.png" 211 | }, 212 | { 213 | "name" : "Preclude Selected Slice", 214 | "identifier" : "wireframes-preclude", 215 | "script" : "handlers.js", 216 | "handler" : "wireframesPreclude", 217 | "icon" : "icons/wireframes-preclude.png" 218 | }, 219 | { 220 | "name" : "Export Wireframes…", 221 | "identifier" : "wireframes-export", 222 | "script" : "handlers.js", 223 | "handler" : "wireframesExport", 224 | "icon" : "icons/wireframes-export.png" 225 | }, 226 | { 227 | "name" : "Create Artboard Instance of Artboard Symbol", 228 | "identifier" : "artboards-create-artboard-instance-of-artboard-symbol", 229 | "script" : "Artboards/create-artboard-instance-of-artboard-symbol.js" 230 | }, 231 | { 232 | "name" : "Create Slice Around All…", 233 | "identifier" : "artboards-create-slice-around-all", 234 | "script" : "Artboards/create-slice-around.js", 235 | "handler" : "sliceAll" 236 | }, 237 | { 238 | "name" : "Create Slice Around Selected…", 239 | "identifier" : "artboards-create-slice-around-selected", 240 | "script" : "Artboards/create-slice-around.js", 241 | "handler" : "sliceSelected" 242 | }, 243 | { 244 | "name" : "Increment Version Numbers for Selected…", 245 | "identifier" : "artboards-increment-version-numbers-for-selected", 246 | "script" : "Artboards/increment-version-numbers.js" 247 | }, 248 | { 249 | "name" : "Increment Version Numbers for Artboards on Page…", 250 | "identifier" : "artboards-increment-version-numbers-on-page", 251 | "script" : "Artboards/increment-version-numbers.js" 252 | }, 253 | { 254 | "name" : "Insert Mask in Selected Artboards", 255 | "identifier" : "artboards-insert-mask-in-selected", 256 | "script" : "Artboards/insert-mask-in.js" 257 | }, 258 | { 259 | "name" : "Update Export Settings for All on Page", 260 | "identifier" : "artboards-update-export-settings-for-all-on-page", 261 | "script" : "Artboards/update-export-settings-for-all-on-page.js" 262 | }, 263 | { 264 | "name" : "Get Current ID", 265 | "identifier" : "document-get-current-id", 266 | "script" : "Document/get-current-id.js" 267 | }, 268 | { 269 | "name" : "Set Custom ID", 270 | "identifier" : "document-set-custom-id", 271 | "script" : "Document/set-custom-id.js" 272 | }, 273 | { 274 | "name" : "Set Random ID", 275 | "identifier" : "document-set-random-id", 276 | "script" : "Document/set-random-id.js" 277 | }, 278 | { 279 | "name" : "Export Page to Zeplin", 280 | "identifier" : "export-page-to-zeplin", 281 | "script" : "Export/page-to-zeplin.js" 282 | }, 283 | { 284 | "name" : "Update Content Bounds", 285 | "identifier" : "group-update-content-bounds", 286 | "script" : "Groups/update-content-bounds.js" 287 | }, 288 | { 289 | "name" : "Create Hotspot Over Selected Overrides", 290 | "identifier" : "hotspots-create-over-selected-overrides", 291 | "script" : "Hotspots/create-over-selected-overrides.js", 292 | "shortcut" : "cmd option shift h", 293 | }, 294 | { 295 | "name" : "Toggle All Hotspots in Document", 296 | "identifier" : "hotspots-toggle-all-in-document", 297 | "script" : "Hotspots/toggle-all-in-document.js", 298 | "icon" : "icons/other-hotspots.png" 299 | }, 300 | { 301 | "name" : "Disable Style Overrides for All Symbols on Page", 302 | "identifier" : "symbols-disable-style-overrides", 303 | "script" : "Symbols/disable-style-overrides.js" 304 | }, 305 | { 306 | "name" : "Replace Text Override for Instances of Selected", 307 | "identifier" : "symbols-replace-override-for-instances-of-selection", 308 | "script" : "Symbols/replace-override-for-instances-of-selection.js" 309 | }, 310 | { 311 | "name" : "Replace Override Fills with Swatch", 312 | "identifier" : "symbols-replace-override-fills-with-swatch", 313 | "script" : "Symbols/replace-override-fills-with-swatch.js" 314 | }, 315 | { 316 | "name" : "Convert to Japanese Style", 317 | "identifier" : "textstyles-convert-to-japanese-style", 318 | "script" : "Text Styles/convert-to-japanese-style.js" 319 | }, 320 | { 321 | "name" : "Recreate and Remove Original", 322 | "identifier" : "textlayers-recreate-and-remove-original", 323 | "script" : "Text Layers/recreate-and-remove-original.js" 324 | }, 325 | { 326 | "name" : "Update Text to Match Style Name", 327 | "identifier" : "textlayers-update-text-to-match-style-name", 328 | "script" : "Text Layers/update-text-to-match-style-name.js" 329 | }, 330 | { 331 | "name" : "Add Feature Link to Manifest", 332 | "identifier" : "customization-add-feature-link-to-manifest", 333 | "script" : "Customization/add-feature-link-to-manifest.js" 334 | }, 335 | { 336 | "name" : "Add Strings to Manifest", 337 | "identifier" : "customization-add-strings-to-manifest", 338 | "script" : "Customization/add-strings-to-manifest.js" 339 | }, 340 | { 341 | "name" : "Run Manifest Audit", 342 | "identifier" : "customization-run-manifest-audit", 343 | "script" : "Customization/run-manifest-audit.js" 344 | }, 345 | { 346 | "script": "Data/script.js", 347 | "handlers": { 348 | "actions": { 349 | "Startup" : "onStartup", 350 | "Shutdown" : "onShutdown", 351 | "SupplyTextFromFile" : "onSupplyTextFromFile", 352 | "SupplyImageFromFolder" : "onSupplyImageFromFolder", 353 | "SupplyRandomTextFromFile" : "onSupplyRandomTextFromFile", 354 | "SupplyRandomImageFromFolder" : "onSupplyRandomImageFromFolder" 355 | } 356 | } 357 | } 358 | ], 359 | "menu" : { 360 | "title" : "SNCR", 361 | "items" : [ 362 | { 363 | "title" : "Section Titles", 364 | "items" : [ 365 | "sections-link", 366 | "sections-unlink", 367 | "sections-update", 368 | "-", 369 | "sections-settings" 370 | ] 371 | }, 372 | "-", 373 | { 374 | "title" : "Artboard Titles", 375 | "items" : [ 376 | "titles-create", 377 | "titles-include", 378 | "titles-preclude", 379 | "-", 380 | "titles-settings" 381 | ] 382 | }, 383 | { 384 | "title" : "Artboard Descriptions", 385 | "items" : [ 386 | "descriptions-set", 387 | "descriptions-link", 388 | "descriptions-unlink", 389 | "descriptions-update", 390 | "-", 391 | "descriptions-settings" 392 | ] 393 | }, 394 | "-", 395 | { 396 | "title" : "Layer Annotations", 397 | "items" : [ 398 | "annotations-create", 399 | "annotations-designate", 400 | "annotations-link", 401 | "annotations-position", 402 | "annotations-update", 403 | "-", 404 | "annotations-settings" 405 | ] 406 | }, 407 | "-", 408 | { 409 | "title" : "Page Layout", 410 | "items" : [ 411 | "layout-update", 412 | "layout-includeSelected", 413 | "layout-precludeSelected", 414 | "-", 415 | "layout-includePage", 416 | "layout-precludePage", 417 | "-", 418 | "layout-settings" 419 | ] 420 | }, 421 | "-", 422 | { 423 | "title" : "Wireframes", 424 | "items" : [ 425 | "wireframes-add", 426 | "wireframes-include", 427 | "wireframes-preclude", 428 | "wireframes-export" 429 | ] 430 | }, 431 | "-", 432 | { 433 | "title" : "Customization Portal", 434 | "items" : [ 435 | "customization-add-feature-link-to-manifest", 436 | "customization-add-strings-to-manifest", 437 | "-", 438 | "customization-run-manifest-audit" 439 | ] 440 | }, 441 | "-", 442 | { 443 | "title" : "Other", 444 | "items" : [ 445 | { 446 | "title" : "Artboards", 447 | "items" : [ 448 | "artboards-create-artboard-instance-of-artboard-symbol", 449 | "-", 450 | "artboards-create-slice-around-all", 451 | "artboards-create-slice-around-selected", 452 | "-", 453 | "artboards-increment-version-numbers-on-page", 454 | "-", 455 | "artboards-insert-mask-in-selected", 456 | "-", 457 | "artboards-update-export-settings-for-all-on-page" 458 | ] 459 | }, 460 | { 461 | "title" : "Document", 462 | "items" : [ 463 | "document-get-current-id", 464 | "document-set-custom-id", 465 | "document-set-random-id" 466 | ] 467 | }, 468 | { 469 | "title" : "Export", 470 | "items" : [ 471 | "export-page-to-zeplin" 472 | ] 473 | }, 474 | { 475 | "title" : "Groups", 476 | "items" : [ 477 | "group-update-content-bounds" 478 | ] 479 | }, 480 | { 481 | "title" : "Hotspots", 482 | "items" : [ 483 | "hotspots-create-over-selected-overrides", 484 | "hotspots-toggle-all-in-document" 485 | ] 486 | }, 487 | { 488 | "title" : "Symbols", 489 | "items" : [ 490 | "symbols-disable-style-overrides", 491 | "symbols-replace-override-for-instances-of-selection", 492 | "symbols-replace-override-fills-with-swatch" 493 | ] 494 | }, 495 | { 496 | "title" : "Text Styles", 497 | "items" : [ 498 | "textstyles-convert-to-japanese-style" 499 | ] 500 | }, 501 | { 502 | "title" : "Text Layers", 503 | "items" : [ 504 | "textlayers-recreate-and-remove-original", 505 | "textlayers-update-text-to-match-style-name" 506 | ] 507 | } 508 | ] 509 | } 510 | ] 511 | } 512 | } 513 | -------------------------------------------------------------------------------- /SNCR.sketchplugin/Contents/Sketch/functions.js: -------------------------------------------------------------------------------- 1 | @import 'delegate.js'; 2 | 3 | var sketch = require("sketch"); 4 | var debug = false; 5 | 6 | var sncr = { 7 | init: function(context,command) { 8 | this.pluginDomain = "com.sncr.sketch"; 9 | 10 | this.document = context.document || context.actionContext.document; 11 | this.selection = context.selection; 12 | this.command = context.command; 13 | this.pages = this.document.pages(); 14 | this.page = this.document.currentPage(); 15 | this.data = this.document.documentData(); 16 | this.symbols = this.data.allSymbols(); 17 | this.symbolsPage = this.data.symbolsPage(); 18 | 19 | this.localeID = "en"; 20 | this.stringsPath = context.plugin.urlForResourceNamed("strings/" + this.localeID + ".plist").path(); 21 | this.strings = NSDictionary.dictionaryWithContentsOfFile(this.stringsPath); 22 | 23 | this.parentGroupName = "SNCR"; 24 | this.artboardNoteGroupName = "Annotations"; 25 | this.descriptionsGroupName = "Descriptions"; 26 | this.titlesGroupName = "Titles"; 27 | 28 | this.annotations.config = { 29 | connectionsGroupKey : "connectionsGroup", 30 | connectionsGroupName : "Connections", 31 | annotationXOffset : 48, 32 | annotationYOffset : 0, 33 | annotationWidth : 240, 34 | annotationSpacing : 8, 35 | annotationLinkKey : "linkedToObject", 36 | annotationLinkTypeKey : "linkType", 37 | annotationLinkTypeValue : "annotation", 38 | annotationParentKey : "linkedParentArtboard", 39 | annotationLinkPrefix : "🔗 ", 40 | annotationStyleData : { 41 | fontFace : "Helvetica Neue", 42 | fontSize : 14, 43 | fontColor : "#000000", 44 | lineHeight : 18, 45 | textAlignment : 0 46 | }, 47 | annotationArrowName : "Arrow", 48 | annotationArrowStrokeWidth : 1, 49 | annotationArrowStrokeColor : "#00AEEF", 50 | annotationArrowStartType : 4, // 0 = None, 1 = Angled Arrow, 2 = Solid Arrow, 3 = Line, 4 = Empty Circle, 5 = Solid Circle, 6 = Empty Square, 7 = Solid Square 51 | annotationArrowStartXOffset : 0, 52 | annotationArrowStartYOffset : 0, 53 | annotationArrowEndType : 2, // 0 = None, 1 = Angled Arrow, 2 = Solid Arrow, 3 = Line, 4 = Empty Circle, 5 = Solid Circle, 6 = Empty Square, 7 = Solid Square 54 | annotationArrowEndXOffset : -12, 55 | annotationArrowEndYOffset : 9 // Half annotationStyleData lineHeight 56 | } 57 | 58 | this.descriptions.config = { 59 | descriptionStyleName : "Wireframe/Artboard Description", 60 | descriptionStyleData : { 61 | fontFace : "Neue Haas Grotesk Text Std 55 Roman", 62 | fontSize : 14, 63 | lineHeight : 18, 64 | textAlignment : 0 65 | }, 66 | descriptionXOffset : 0, 67 | descriptionYOffset : 24, 68 | descriptionLinkKey : "linkedToArtboard", 69 | descriptionLinkTypeKey : "linkType", 70 | descriptionLinkTypeValue : "description", 71 | descriptionLinkPrefix : "🔗 " 72 | } 73 | 74 | this.layout.config = { 75 | featureKey : "layoutArtboards", 76 | pageNamePrefix : "🔹 " 77 | } 78 | 79 | this.sections.config = { 80 | symbolMasterKey : "sectionTitleSymbol", 81 | titleLinkKey : "sncrScreenTitleLinkedTo", 82 | titleLinkPrefix : "🔗 " 83 | } 84 | 85 | this.titles.config = { 86 | featureKey : "createArtboardTitles" 87 | } 88 | 89 | this.wireframes.config = { 90 | featureKey : "wireframeExport" 91 | } 92 | 93 | if (command) { 94 | switch (command) { 95 | case "sections-link" : 96 | this.sections.linkSelected(context); 97 | break; 98 | case "sections-unlink" : 99 | this.sections.unlinkSelected(context); 100 | break; 101 | case "sections-update" : 102 | this.sections.updateAllOnPage(context); 103 | break; 104 | case "sections-settings" : 105 | this.sections.settings(context); 106 | break; 107 | case "titles-create" : 108 | this.titles.create(context,"create"); 109 | break; 110 | case "titles-include" : 111 | this.titles.include(context); 112 | break; 113 | case "titles-preclude" : 114 | this.titles.preclude(context); 115 | break; 116 | case "titles-settings" : 117 | this.titles.settings(context); 118 | break; 119 | case "descriptions-set" : 120 | this.descriptions.addEdit(context); 121 | break; 122 | case "descriptions-link" : 123 | this.descriptions.linkSelected(context); 124 | break; 125 | case "descriptions-unlink" : 126 | this.descriptions.unlinkSelected(context); 127 | break; 128 | case "descriptions-update" : 129 | this.descriptions.updateAllOnPage(context); 130 | break; 131 | case "descriptions-settings" : 132 | this.descriptions.settings(context); 133 | break; 134 | case "layout-update" : 135 | this.layout.update(context); 136 | break; 137 | case "layout-include-selected" : 138 | this.layout.includeSelected(context); 139 | break; 140 | case "layout-preclude-selected" : 141 | this.layout.precludeSelected(context); 142 | break; 143 | case "layout-include-page" : 144 | this.layout.includePage(context); 145 | break; 146 | case "layout-preclude-page" : 147 | this.layout.precludePage(context); 148 | break; 149 | case "layout-settings" : 150 | this.layout.settings(context); 151 | break; 152 | case "annotations-create" : 153 | this.annotations.annotateSelected(context); 154 | break; 155 | case "annotations-designate" : 156 | this.annotations.designateSelected(); 157 | break; 158 | case "annotations-link" : 159 | this.annotations.linkSelected(context); 160 | break; 161 | case "annotations-position" : 162 | this.annotations.updatePosition(context); 163 | break; 164 | case "annotations-update" : 165 | this.annotations.updateAnnotations(context); 166 | break; 167 | case "annotations-settings" : 168 | this.annotations.settings(context); 169 | break; 170 | case "wireframes-add" : 171 | this.wireframes.addNew(context); 172 | break; 173 | case "wireframes-include" : 174 | this.wireframes.include(context); 175 | break; 176 | case "wireframes-preclude" : 177 | this.wireframes.preclude(context); 178 | break; 179 | case "wireframes-export" : 180 | this.wireframes.export(context); 181 | break; 182 | } 183 | } 184 | } 185 | } 186 | 187 | sncr.annotations = { 188 | annotateSelected: function(context) { 189 | // This function can be called by AddFlow.finish, which is only triggered if user applies a flow directly to an 190 | // object (not by updating the flow of a nested hotspot). In this case, context.actionContext will be present. 191 | if (context.actionContext) { 192 | if (sncr.annotations.settings(context,"create").autoAnnotate == 1) { 193 | // Wait a couple seconds for processing to complete 194 | COScript.currentCOScript().scheduleWithInterval_jsFunction(2, function() { 195 | annotateSelections(context.actionContext.document.selectedLayers().layers()); 196 | }); 197 | } else return; 198 | } else { 199 | var selections = sncr.selection; 200 | 201 | if (!selections.length) { 202 | sketch.UI.message("Nothing is selected"); 203 | 204 | return; 205 | } 206 | 207 | annotateSelections(selections); 208 | } 209 | 210 | function annotateSelections(layers) { 211 | var updatedArtboards = NSMutableArray.array(); 212 | 213 | layers.forEach(layer => { 214 | var artboardID = layer.parentArtboard().objectID(); 215 | 216 | if (layer.flow() && layer.flow().destinationArtboardID()) { 217 | sncr.annotations.createAnnotation(layer,layer.flow().destinationArtboardID()); 218 | 219 | if (!updatedArtboards.containsObject(artboardID)) { 220 | updatedArtboards.addObject(artboardID); 221 | } 222 | } 223 | 224 | // If context.actionContext present (called by AddFlow.finish), don't bother drilling deeper as the 225 | // action is not triggered by updating the flow of a nested hotspot. 226 | if (!context.actionContext) { 227 | if (layer instanceof MSSymbolInstance && layer.overrides()) { 228 | var overrides = sketch.fromNative(layer).overrides.filter(o => o.editable && o.property === 'flowDestination' && !['','null'].includes(o.value)); 229 | 230 | overrides.forEach(o => { 231 | let value = o.value; 232 | let path = (o.path.includes('/')) ? o.path.substr(0,o.path.indexOf('/')) : o.path; 233 | 234 | sncr.annotations.createAnnotation(layer,value,path); 235 | 236 | if (!updatedArtboards.containsObject(artboardID)) { 237 | updatedArtboards.addObject(artboardID); 238 | } 239 | }); 240 | } 241 | } 242 | }); 243 | 244 | updatedArtboards.forEach(artboardID => { 245 | sncr.annotations.updateAnnotations(context,artboardID); 246 | }); 247 | 248 | if (!updatedArtboards.length) sketch.UI.message('The selected layer(s) have nothing to annotate'); 249 | } 250 | }, 251 | designateSelected: function(annotation) { 252 | if (annotation) { 253 | sncr.command.setValue_forKey_onLayer(sncr.annotations.config.annotationLinkTypeValue,sncr.annotations.config.annotationLinkTypeKey,annotation); 254 | 255 | var annotationName = annotation.name().split(/\r\n|\r|\n/g)[0]; 256 | 257 | log(annotationName + sncr.strings['annotation-designate-complete']); 258 | 259 | sketch.UI.message(annotationName + sncr.strings['annotation-designate-complete']); 260 | } else { 261 | if (!sncr.selection) { 262 | sketch.UI.alert(sncr.strings['annotation-designate-plugin'],sncr.strings['annotation-designate-problem']); 263 | 264 | return; 265 | } 266 | 267 | var selectionLoop = sncr.selection.objectEnumerator(), 268 | selection, 269 | annotationName, 270 | count = 0; 271 | 272 | while (selection = selectionLoop.nextObject()) { 273 | sncr.command.setValue_forKey_onLayer(sncr.annotations.config.annotationLinkTypeValue,sncr.annotations.config.annotationLinkTypeKey,selection); 274 | 275 | annotationName = selection.name().split(/\r\n|\r|\n/g)[0]; 276 | 277 | log(annotationName + sncr.strings['annotation-designate-complete']); 278 | 279 | count++; 280 | } 281 | 282 | sketch.UI.message(((count == 1) ? annotationName : count) + sncr.strings['annotation-designate-complete']); 283 | } 284 | }, 285 | createAnnotation: function(linkedObject,destinationArtboardID,flowObjectID) { 286 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName); 287 | var noteGroup = getChildGroup(parentGroup,sncr.artboardNoteGroupName); 288 | 289 | var destinationArtboardName; 290 | 291 | if (destinationArtboardID == "back") { 292 | destinationArtboardName = "Back to originating screen"; 293 | } else if (sncr.data.layerWithID(destinationArtboardID)) { 294 | destinationArtboardName = sncr.data.layerWithID(destinationArtboardID).name(); 295 | } else { 296 | destinationArtboardName = "Unknown"; 297 | } 298 | 299 | var artboardAnnotations = sncr.annotations.getAnnotations(linkedObject.parentArtboard().objectID()); 300 | 301 | var predicate; 302 | 303 | if (flowObjectID) { 304 | predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkKey + " == '" + linkedObject.objectID() + "' && function(userInfo,'valueForKeyPath:',%@).flowObjectID == '" + flowObjectID + "'",sncr.pluginDomain,sncr.pluginDomain); 305 | } else { 306 | predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkKey + " == '" + linkedObject.objectID() + "' && function(userInfo,'valueForKeyPath:',%@).flowObjectID == nil",sncr.pluginDomain,sncr.pluginDomain); 307 | } 308 | 309 | var existingNote = artboardAnnotations.filteredArrayUsingPredicate(predicate).firstObject(); 310 | 311 | if (!existingNote) { 312 | var annotation = MSTextLayer.new(); 313 | annotation.setStringValue(destinationArtboardName); 314 | annotation.setName(destinationArtboardName); 315 | annotation.setFont(NSFont.fontWithName_size(sncr.annotations.config.annotationStyleData.fontFace + " Bold",sncr.annotations.config.annotationStyleData.fontSize)); 316 | annotation.setLineHeight(sncr.annotations.config.annotationStyleData.lineHeight); 317 | annotation.setTextColor(MSImmutableColor.colorWithSVGString(sncr.annotations.config.annotationStyleData.fontColor)); 318 | annotation.setTextBehaviour(1); 319 | annotation.frame().setWidth(sncr.annotations.config.annotationWidth); 320 | 321 | noteGroup.addLayers([annotation]); 322 | 323 | sncr.command.setValue_forKey_onLayer(linkedObject.objectID(),sncr.annotations.config.annotationLinkKey,annotation); 324 | sncr.command.setValue_forKey_onLayer(sncr.annotations.config.annotationLinkTypeValue,sncr.annotations.config.annotationLinkTypeKey,annotation); 325 | sncr.command.setValue_forKey_onLayer(linkedObject.parentArtboard().objectID(),sncr.annotations.config.annotationParentKey,annotation); 326 | 327 | if (flowObjectID) { 328 | sncr.command.setValue_forKey_onLayer(flowObjectID,'flowObjectID',annotation); 329 | } 330 | } else { 331 | var existingString = existingNote.stringValue(); 332 | 333 | var noteTitleFont = NSFont.fontWithName_size(sncr.annotations.config.annotationStyleData.fontFace + " Bold",sncr.annotations.config.annotationStyleData.fontSize); 334 | var noteDescFont = NSFont.fontWithName_size(sncr.annotations.config.annotationStyleData.fontFace,sncr.annotations.config.annotationStyleData.fontSize); 335 | 336 | if (existingString.indexOf("\n") != -1 || existingString.indexOf("\r") != -1) { 337 | var n = existingString.indexOf("\n"); 338 | var r = existingString.indexOf("\r"); 339 | var returnIndex; 340 | var returnType; 341 | 342 | if (n != -1 && r != -1) { 343 | if (n < r) { 344 | returnIndex = n; 345 | returnType = "\n"; 346 | } else { 347 | returnIndex = r; 348 | returnType = "\r"; 349 | } 350 | } else if (n != -1) { 351 | returnIndex = n; 352 | returnType = "\n"; 353 | } else { 354 | returnIndex = r; 355 | returnType = "\r"; 356 | } 357 | 358 | var rangeBegin = returnIndex + 1; 359 | var rangeEnd = existingString.length() - rangeBegin; 360 | var newString = destinationArtboardName + "\n" + existingString.substr(rangeBegin,rangeEnd); 361 | 362 | existingNote.setStringValue(newString); 363 | existingNote.setFont(noteTitleFont); 364 | 365 | var rangeBegin = newString.indexOf("\n") + 1; 366 | var rangeEnd = newString.length - rangeBegin; 367 | var range = NSMakeRange(rangeBegin,rangeEnd); 368 | 369 | existingNote.addAttribute_value_forRange(NSFontAttributeName,noteDescFont,range); 370 | } else { 371 | existingNote.setStringValue(destinationArtboardName); 372 | existingNote.setFont(noteTitleFont); 373 | } 374 | 375 | existingNote.setName(destinationArtboardName); 376 | 377 | existingNote.setLineHeight(sncr.annotations.config.annotationStyleData.lineHeight); 378 | existingNote.setTextColor(MSImmutableColor.colorWithSVGString(sncr.annotations.config.annotationStyleData.fontColor)); 379 | existingNote.setTextBehaviour(1); 380 | existingNote.frame().setWidth(sncr.annotations.config.annotationWidth); 381 | } 382 | }, 383 | getAnnotations: function(artboardID) { 384 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName); 385 | var noteGroup = getChildGroup(parentGroup,sncr.artboardNoteGroupName); 386 | 387 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkKey + " != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkTypeKey + " == " + sncr.annotations.config.annotationLinkTypeValue,sncr.pluginDomain); 388 | var annotations = noteGroup.children().filteredArrayUsingPredicate(predicate); 389 | 390 | var annotationArray = NSMutableArray.array(); 391 | 392 | annotations.forEach(annotation => { 393 | var linkedObjectID = sncr.command.valueForKey_onLayer(sncr.annotations.config.annotationLinkKey,annotation); 394 | var linkedObject = sncr.data.layerWithID(linkedObjectID); 395 | 396 | if (linkedObject && linkedObject.parentArtboard().objectID() == artboardID) { 397 | annotationArray.addObject(annotation); 398 | } 399 | }); 400 | 401 | return annotationArray; 402 | }, 403 | linkSelected: function(context) { 404 | // If nothing is selected, or two objects are not selected... 405 | if (!sncr.selection || sncr.selection.count() != 2) { 406 | // Display feedback 407 | sketch.UI.alert(sncr.strings["annotation-link-plugin"],sncr.strings["annotation-link-problem-selection"]); 408 | 409 | return; 410 | } 411 | 412 | // Selection variables 413 | var firstObject = sncr.selection.firstObject(); 414 | var firstObjectClass = firstObject.class(); 415 | var firstObjectAnnotation = sncr.command.valueForKey_onLayer(sncr.annotations.config.annotationLinkTypeKey,firstObject); 416 | var lastObject = sncr.selection.lastObject(); 417 | var lastObjectClass = lastObject.class(); 418 | var lastObjectAnnotation = sncr.command.valueForKey_onLayer(sncr.annotations.config.annotationLinkTypeKey,lastObject); 419 | var annotation; 420 | var source; 421 | 422 | // If only the first object is a text layer... 423 | if (firstObjectClass == "MSTextLayer" && lastObjectClass != "MSTextLayer") { 424 | annotation = firstObject; 425 | source = lastObject; 426 | } 427 | // If only the second object is a text layer... 428 | else if (firstObjectClass != "MSTextLayer" && lastObjectClass == "MSTextLayer") { 429 | annotation = lastObject; 430 | source = firstObject; 431 | } 432 | // If the first object is the annotation... 433 | else if (firstObjectAnnotation == 'annotation' && !lastObjectAnnotation) { 434 | annotation = firstObject; 435 | source = lastObject; 436 | } 437 | // If the second object is the annotation... 438 | else if (!firstObjectAnnotation && lastObjectAnnotation == 'annotation') { 439 | annotation = lastObject; 440 | source = firstObject; 441 | } 442 | // If neither object is a text layer, or both are... 443 | else { 444 | // Display feedback 445 | sketch.UI.alert(sncr.strings["annotation-link-plugin"],sncr.strings["annotation-link-problem-textlayer"]); 446 | 447 | return; 448 | } 449 | 450 | // Set values on annotation 451 | sncr.command.setValue_forKey_onLayer(source.objectID(),sncr.annotations.config.annotationLinkKey,annotation); 452 | sncr.command.setValue_forKey_onLayer(sncr.annotations.config.annotationLinkTypeValue,sncr.annotations.config.annotationLinkTypeKey,annotation); 453 | sncr.command.setValue_forKey_onLayer(source.parentArtboard().objectID(),sncr.annotations.config.annotationParentKey,annotation); 454 | 455 | // Update annotations for source parent artboard 456 | sncr.annotations.updateAnnotations(context,source.parentArtboard().objectID()); 457 | 458 | // Determine annotation name 459 | var annotationName = sncr.annotations.config.annotationLinkPrefix + annotation.name().split(/\r\n|\r|\n/g)[0].replace(sncr.annotations.config.annotationLinkPrefix,""); 460 | 461 | // Create a log event 462 | log(annotationName + sncr.strings["annotation-link-complete"] + source.name()); 463 | 464 | // Display feedback 465 | sketch.UI.message(annotationName + sncr.strings["annotation-link-complete"] + source.name()); 466 | }, 467 | updateAnnotations: function(context,artboardID) { 468 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkKey + " != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkTypeKey + " == " + sncr.annotations.config.annotationLinkTypeValue,sncr.pluginDomain); 469 | var annotations = sncr.page.children().filteredArrayUsingPredicate(predicate); 470 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName); 471 | var noteGroup = getChildGroup(parentGroup,sncr.artboardNoteGroupName); 472 | var updateCount = 0; 473 | var removeCount = 0; 474 | var artboardsWithAnnotations = NSMutableArray.array(); 475 | 476 | // If there are no annotations... 477 | if (annotations.count() == 0) { 478 | // Display feedback if the function was not invoked by an action... 479 | if (!context.actionContext) sketch.UI.message(updateCount + sncr.strings['annotation-update-complete']); 480 | } 481 | 482 | // Iterate through annotations... 483 | annotations.forEach(annotation => { 484 | if (debug) log('Processing annotation ' + annotation.name() + ' (' + annotation.objectID() + ')'); 485 | 486 | // Get linked object 487 | var linkedObjectID = sncr.command.valueForKey_onLayer(sncr.annotations.config.annotationLinkKey,annotation); 488 | var linkedObject = sncr.data.layerWithID(linkedObjectID); 489 | 490 | var annotationPosition = sncr.command.valueForKey_onLayer('position',annotation); 491 | annotationPosition = (annotationPosition) ? annotationPosition : 0; 492 | 493 | // If linked object exists on current page... 494 | if (linkedObject && linkedObject.parentPage() == sncr.page) { 495 | var targetRect = linkedObject.absoluteRect().rect(); 496 | 497 | // Get ID of parent artboard of linked object 498 | var artboardWithAnnotation = linkedObject.parentArtboard().objectID(); 499 | 500 | // If no artboardID was passed, or if artboardID was passed and it matches ID of parent artboard of linked object 501 | if (!artboardID || artboardID == artboardWithAnnotation) { 502 | // Get the flowObjectID, if one exists (used if annotation links to a flow layer) 503 | var flowObjectID = sncr.command.valueForKey_onLayer_forPluginIdentifier('flowObjectID',annotation,sncr.pluginDomain); 504 | 505 | // If a flowObjectID exists... 506 | if (flowObjectID) { 507 | // If the flow object still exists... 508 | if (linkedObject.symbolMaster().layerWithID(flowObjectID)) { 509 | // Get destinationArtboardID in order to update linked annotation destination 510 | var destinationArtboardID = sketch.fromNative(linkedObject).overrides.filter(o => o.property === 'flowDestination' && o.path.includes(flowObjectID))[0].value; 511 | 512 | // Update annotation destination 513 | sncr.annotations.createAnnotation(linkedObject,destinationArtboardID,flowObjectID); 514 | 515 | // Determine annotation Y position 516 | var flowLayerRect = createNewRectForFlowLayer(linkedObject,flowObjectID); 517 | targetRect = flowLayerRect; 518 | var annotationY = flowLayerRect.origin.y + flowLayerRect.size.height/2 + sncr.annotations.config.annotationYOffset - sncr.annotations.config.annotationStyleData.lineHeight/2; 519 | } 520 | // Otherwise... 521 | else { 522 | // Remove stored values for linked artboard 523 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationLinkKey,annotation); 524 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationLinkTypeKey,annotation); 525 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationParentKey,annotation); 526 | sncr.command.setValue_forKey_onLayer(nil,'flowObjectID',annotation); 527 | 528 | // Create annotation name 529 | var annotationName = annotation.name().replace(sncr.annotations.config.annotationLinkPrefix,""); 530 | 531 | // Update annotation layer name 532 | annotation.setName(annotationName); 533 | 534 | // Iterate counters 535 | updateCount++; 536 | removeCount++; 537 | 538 | // Create a log event 539 | log(annotationName + sncr.strings["annotation-unlink-complete"] + linkedObjectID); 540 | 541 | return false; 542 | } 543 | } 544 | // If annotation links to an object... 545 | else { 546 | // If object has a flow destination... 547 | if (linkedObject.flow()) { 548 | // Get destinationArtboardID in order to update linked annotation destination 549 | var destinationArtboardID = linkedObject.flow().destinationArtboardID(); 550 | 551 | // Update annotation destination 552 | sncr.annotations.createAnnotation(linkedObject,destinationArtboardID); 553 | } 554 | 555 | // Determine annotation Y position 556 | var annotationY = linkedObject.absoluteRect().y() + linkedObject.frame().height()/2 + sncr.annotations.config.annotationYOffset - sncr.annotations.config.annotationStyleData.lineHeight/2; 557 | } 558 | 559 | // Get max X of artboard, or parent artboard 560 | var artboardMaxX = CGRectGetMaxX(linkedObject.parentArtboard().rect()); 561 | var artboardMidX = CGRectGetMidX(linkedObject.parentArtboard().rect()); 562 | var artboardMinX = CGRectGetMinX(linkedObject.parentArtboard().rect()); 563 | 564 | if (CGRectGetMidX(targetRect) < artboardMidX && annotation.class() != "MSSymbolInstance" && annotationPosition != 1) { 565 | // Update annotation x position (left of artboard) 566 | annotation.absoluteRect().setX(artboardMinX - sncr.annotations.config.annotationWidth - sncr.annotations.config.annotationXOffset); 567 | } else { 568 | // Update annotation x position (right of artboard) 569 | annotation.absoluteRect().setX(artboardMaxX + sncr.annotations.config.annotationXOffset); 570 | } 571 | 572 | // Update annotation y position 573 | annotation.absoluteRect().setY(annotationY); 574 | 575 | // Update annotation width 576 | annotation.frame().setWidth(sncr.annotations.config.annotationWidth); 577 | 578 | // If annotation is a text layer... 579 | if (annotation.class() == "MSTextLayer") { 580 | // Update annotation style 581 | annotation.setFontSize(sncr.annotations.config.annotationStyleData.fontSize); 582 | annotation.setLineHeight(sncr.annotations.config.annotationStyleData.lineHeight); 583 | 584 | if (CGRectGetMidX(targetRect) < artboardMidX && annotationPosition != 1) { 585 | annotation.setTextAlignment(1); 586 | } else { 587 | annotation.setTextAlignment(sncr.annotations.config.annotationStyleData.textAlignment); 588 | } 589 | } 590 | 591 | // If annotation is a symbol instance... 592 | if (annotation.class() == "MSSymbolInstance") { 593 | //log(`Working on ${annotation.name()}…`); 594 | 595 | // Get overrides in annotation... 596 | let overrides = sketch.fromNative(annotation).overrides.filter(o => o.editable); 597 | 598 | // Iterate through overrides... 599 | overrides.forEach(override => { 600 | if (override.property === 'flowDestination' && override.value) { 601 | let group = override.path.substr(0,override.path.indexOf('/')); 602 | let text = overrides.find(o => o.id.substr(0,o.id.indexOf('/')) == group && o.property == 'stringValue'); 603 | let destination = sketch.getSelectedDocument().getLayerWithID(override.value); 604 | 605 | if (destination) { 606 | let name = destination.name.substr(0,destination.name.indexOf(' ')); 607 | 608 | text.value = name; 609 | } else { 610 | // Display feedback 611 | sketch.UI.message(`There is a problematic hotspot override on the annotation for ${annotation.name()}…`); 612 | } 613 | } 614 | }); 615 | 616 | // Adjust annotation size 617 | sketch.fromNative(annotation).resizeWithSmartLayout(); 618 | } 619 | 620 | // Update annotation layer name 621 | annotation.setName(sncr.annotations.config.annotationLinkPrefix + linkedObject.name()); 622 | 623 | // If the annotation is not in the annotation group... 624 | if (annotation.parentGroup() != noteGroup) { 625 | // Move the annotation to the annotation group 626 | annotation.moveToLayer_beforeLayer(noteGroup,nil); 627 | } 628 | 629 | // Select the annotation 630 | annotation.select_byExtendingSelection(1,1); 631 | 632 | // Move the annotation to the top of the annotation group 633 | MSLayerMovement.moveToFront([annotation]); 634 | 635 | // Deselect the annotation 636 | annotation.select_byExtendingSelection(0,1); 637 | 638 | // Iterate counter 639 | updateCount++; 640 | 641 | if (!artboardsWithAnnotations.containsObject(artboardWithAnnotation)) { 642 | artboardsWithAnnotations.addObject(artboardWithAnnotation); 643 | } 644 | } 645 | 646 | // Remove stored values no longer used 647 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationParentKey,annotation); 648 | } 649 | // If linked object does not exist... 650 | else { 651 | // Remove stored values for linked artboard 652 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationLinkKey,annotation); 653 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationLinkTypeKey,annotation); 654 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationParentKey,annotation); 655 | sncr.command.setValue_forKey_onLayer(nil,'flowObjectID',annotation); 656 | 657 | // Create annotation name 658 | var annotationName = annotation.name().replace(sncr.annotations.config.annotationLinkPrefix,""); 659 | 660 | // Update annotation layer name 661 | annotation.setName(annotationName); 662 | 663 | // Iterate counters 664 | updateCount++; 665 | removeCount++; 666 | 667 | // Create a log event 668 | log(annotationName + sncr.strings["annotation-unlink-complete"] + linkedObjectID); 669 | } 670 | }); 671 | 672 | // If annotation group is not empty... 673 | if (noteGroup.layers().count() > 0) { 674 | artboardsWithAnnotations.forEach(artboardID => { 675 | var artboard = sncr.data.layerWithID(artboardID); 676 | var siblings = sncr.annotations.getAnnotations(artboardID); 677 | 678 | if (siblings.count() > 1) { 679 | var sortByX = NSSortDescriptor.sortDescriptorWithKey_ascending("absoluteRect.x",1); 680 | var sortByY = NSSortDescriptor.sortDescriptorWithKey_ascending("absoluteRect.y",1); 681 | 682 | siblings = siblings.sortedArrayUsingDescriptors([sortByX,sortByY]); 683 | 684 | // Iterate through the siblings... 685 | for (var i = 0; i < siblings.length; i++) { 686 | // If there is a next sibling... 687 | if (i + 1 < siblings.length) { 688 | // Sibling variables 689 | var thisSibling = siblings[i]; 690 | var nextSibling = siblings[i+1]; 691 | 692 | // If this sibling and the next are on the same vertical plane, and intersect... 693 | if (CGRectGetMinX(thisSibling.rect()) == CGRectGetMinX(nextSibling.rect()) && CGRectGetMaxY(thisSibling.rect()) + sncr.annotations.config.annotationSpacing >= CGRectGetMinY(nextSibling.rect())) { 694 | // Adjust the Y coordinate of the next sibling 695 | nextSibling.frame().setY(CGRectGetMaxY(thisSibling.rect()) + sncr.annotations.config.annotationSpacing); 696 | } 697 | } 698 | } 699 | } 700 | }); 701 | 702 | // Resize annotation and parent groups to account for children 703 | if (sketch.version.sketch > 52) { 704 | noteGroup.fixGeometryWithOptions(0); 705 | parentGroup.fixGeometryWithOptions(0); 706 | } else { 707 | noteGroup.resizeToFitChildrenWithOption(0); 708 | parentGroup.resizeToFitChildrenWithOption(0); 709 | } 710 | 711 | // Redraw all connections 712 | sncr.annotations.updateConnections(); 713 | } 714 | // If annotation group is empty... 715 | else { 716 | // Remove the annotation group 717 | noteGroup.removeFromParent(); 718 | 719 | // Resize parent group to account for children 720 | if (sketch.version.sketch > 52) { 721 | parentGroup.fixGeometryWithOptions(0); 722 | } else { 723 | parentGroup.resizeToFitChildrenWithOption(0); 724 | } 725 | } 726 | 727 | // Move parent group to the top of the layer list 728 | parentGroup.moveToLayer_beforeLayer(sncr.page,nil); 729 | 730 | // Deselect parent group (moveToLayer_beforeLayer selects it) 731 | parentGroup.select_byExtendingSelection(0,1); 732 | 733 | // If the function was not invoked by action... 734 | if (!context.actionContext) { 735 | // If any annotation links were removed 736 | if (removeCount > 0) { 737 | // Display feedback 738 | sketch.UI.message(updateCount + sncr.strings["annotation-update-complete"] + ", " + removeCount + sncr.strings["annotation-update-complete-unlinked"]); 739 | } else { 740 | // Display feedback 741 | sketch.UI.message(updateCount + sncr.strings["annotation-update-complete"]); 742 | } 743 | } 744 | }, 745 | updateConnections: function() { 746 | // Connections variables 747 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.connectionsGroupKey + " == true", sncr.pluginDomain); 748 | var connectionsGroup = sncr.page.children().filteredArrayUsingPredicate(predicate).firstObject(); 749 | 750 | // Remove connections group if it exists... 751 | if (connectionsGroup) connectionsGroup.removeFromParent(); 752 | 753 | // Create annotations loop 754 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkKey + " != nil", sncr.pluginDomain); 755 | var annotations = sncr.page.children().filteredArrayUsingPredicate(predicate); 756 | 757 | // Connections variable 758 | var connections = []; 759 | 760 | // Loop through annotations... 761 | annotations.forEach(function(annotation){ 762 | // Get ID for linked object 763 | var linkedObjectID = sncr.command.valueForKey_onLayer_forPluginIdentifier(sncr.annotations.config.annotationLinkKey,annotation,sncr.pluginDomain); 764 | 765 | // Get linked object on current page 766 | var predicate = NSPredicate.predicateWithFormat("objectID == %@",linkedObjectID); 767 | var linkedObject = sncr.page.children().filteredArrayUsingPredicate(predicate).firstObject(); 768 | 769 | // If linked object exists... 770 | if (linkedObject) { 771 | var flowObjectID = sncr.command.valueForKey_onLayer_forPluginIdentifier('flowObjectID',annotation,sncr.pluginDomain); 772 | 773 | if (linkedObject.class() == 'MSSymbolInstance' && flowObjectID) { 774 | // Create connection object 775 | var connection = { 776 | linkID : annotation.objectID(), 777 | linkRect : createNewRectForFlowLayer(linkedObject,flowObjectID), 778 | endPoint : { 779 | x : annotation.absoluteRect().x(), 780 | y : annotation.absoluteRect().y() 781 | } 782 | } 783 | } else { 784 | // Create connection object 785 | var connection = { 786 | linkID : annotation.objectID(), 787 | linkRect : linkedObject.parentArtboard() ? CGRectIntersection(linkedObject.absoluteRect().rect(),linkedObject.parentArtboard().absoluteRect().rect()) : linkedObject.absoluteRect().rect(), 788 | endPoint : { 789 | x : annotation.absoluteRect().x(), 790 | y : annotation.absoluteRect().y() 791 | } 792 | } 793 | } 794 | 795 | // Add connection object to connections array 796 | connections.push(connection); 797 | } 798 | }); 799 | 800 | // Set parent group 801 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName); 802 | 803 | // Set annotation group 804 | var noteGroup = getChildGroup(parentGroup,sncr.artboardNoteGroupName); 805 | 806 | // Create new connections group 807 | connectionsGroup = MSLayerGroup.new(); 808 | 809 | // Iterate through the connections... 810 | connections.forEach(function(connection,i){ 811 | var linkRect = connection.linkRect; 812 | var startPoint; 813 | var startPointX; 814 | var startPointY; 815 | var endPoint; 816 | var endPointX; 817 | var endPointY; 818 | 819 | if (CGRectGetMinX(linkRect) < connection.endPoint.x) { 820 | startPointX = CGRectGetMaxX(linkRect) + sncr.annotations.config.annotationArrowStartXOffset; 821 | startPointY = CGRectGetMidY(linkRect) + sncr.annotations.config.annotationArrowStartYOffset; 822 | endPointX = connection.endPoint.x + sncr.annotations.config.annotationArrowEndXOffset; 823 | endPointY = connection.endPoint.y + sncr.annotations.config.annotationArrowEndYOffset; 824 | } else { 825 | startPointX = CGRectGetMinX(linkRect) - sncr.annotations.config.annotationArrowStartXOffset; 826 | startPointY = CGRectGetMidY(linkRect) + sncr.annotations.config.annotationArrowStartYOffset; 827 | endPointX = connection.endPoint.x + sncr.annotations.config.annotationWidth - sncr.annotations.config.annotationArrowEndXOffset; 828 | endPointY = connection.endPoint.y + sncr.annotations.config.annotationArrowEndYOffset; 829 | } 830 | 831 | startPoint = NSMakePoint(Math.round(startPointX),Math.round(startPointY)); 832 | endPoint = NSMakePoint(Math.round(endPointX),Math.round(endPointY)); 833 | 834 | var linePath = NSBezierPath.bezierPath(); 835 | linePath.moveToPoint(startPoint); 836 | 837 | if (startPoint.y == endPoint.y) { 838 | linePath.lineToPoint(endPoint); 839 | } else { 840 | var controlPoint1Offset = Math.max(Math.abs(endPoint.x - startPoint.x)/2, 24); 841 | var controlPoint2Offset = Math.max(Math.abs(endPoint.x - startPoint.x)/2, 24); 842 | var controlPoint1 = NSMakePoint(startPoint.x + controlPoint1Offset, startPoint.y); 843 | var controlPoint2 = NSMakePoint(endPoint.x - controlPoint2Offset, endPoint.y); 844 | 845 | linePath.curveToPoint_controlPoint1_controlPoint2(endPoint,controlPoint1,controlPoint2); 846 | } 847 | 848 | var lineLayer = MSShapePathLayer.layerWithPath(MSPath.pathWithBezierPath(linePath)); 849 | lineLayer.setName(sncr.annotations.config.annotationArrowName + ' ' + i); 850 | lineLayer.style().setStartMarkerType(sncr.annotations.config.annotationArrowStartType); 851 | lineLayer.style().setEndMarkerType(sncr.annotations.config.annotationArrowEndType); 852 | 853 | var lineStyle = lineLayer.style().addStylePartOfType(1); 854 | lineStyle.setColor(MSImmutableColor.colorWithSVGString(sncr.annotations.config.annotationArrowStrokeColor).newMutableCounterpart()); 855 | lineStyle.setThickness(sncr.annotations.config.annotationArrowStrokeWidth); 856 | lineStyle.setPosition(0); 857 | 858 | connectionsGroup.addLayers([lineLayer]); 859 | }); 860 | 861 | // Move connections group to annotation group 862 | connectionsGroup.moveToLayer_beforeLayer(noteGroup,nil); 863 | 864 | // Resize connections group to account for children 865 | if (sketch.version.sketch > 52) { 866 | connectionsGroup.fixGeometryWithOptions(0); 867 | } else { 868 | connectionsGroup.resizeToFitChildrenWithOption(0); 869 | } 870 | 871 | // Deselection connections and annotations groups 872 | connectionsGroup.deselectLayerAndParent(); 873 | 874 | // Set connections group name 875 | connectionsGroup.setName(sncr.annotations.config.connectionsGroupName); 876 | 877 | // Lock connections group 878 | connectionsGroup.setIsLocked(1); 879 | 880 | // Set stored value on connections group 881 | sncr.command.setValue_forKey_onLayer_forPluginIdentifier(true,sncr.annotations.config.connectionsGroupKey,connectionsGroup,sncr.pluginDomain); 882 | }, 883 | updatePosition: function() { 884 | // If nothing is selected... 885 | if (!sncr.selection) { 886 | sketch.UI.alert(sncr.strings["annotation-link-plugin"],'Select an annotation.'); 887 | return; 888 | } 889 | 890 | var count = 0; 891 | 892 | sncr.selection.forEach(layer => { 893 | if (sncr.command.valueForKey_onLayer(sncr.annotations.config.annotationLinkTypeKey,layer)) { 894 | sncr.command.setValue_forKey_onLayer_forPluginIdentifier(1,'position',layer,sncr.pluginDomain); 895 | count++; 896 | } 897 | }); 898 | 899 | // Display feedback 900 | sketch.UI.message(count + ' annotation(s) updated'); 901 | }, 902 | settings: function(context,command) { 903 | // Setting variables 904 | var defaultSettings = {}; 905 | defaultSettings.autoAnnotate = 0; 906 | 907 | // Update default settings with cached settings 908 | defaultSettings = getCachedSettings(context,sncr.data,defaultSettings,sncr.pluginDomain); 909 | 910 | // If a command is not passed, operate in config mode... 911 | if (!command) { 912 | var alertWindow = COSAlertWindow.new(); 913 | 914 | alertWindow.setMessageText(sncr.strings["annotation-settings"]); 915 | 916 | var autoAnnotate = createCheckbox({ 917 | name : sncr.strings["annotation-settings-automatic"], 918 | value: 1 919 | },defaultSettings.autoAnnotate,NSMakeRect(0,0,300,54)); 920 | 921 | alertWindow.addAccessoryView(autoAnnotate); 922 | 923 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]); 924 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]); 925 | 926 | // Set key order and first responder 927 | setKeyOrder(alertWindow,[ 928 | autoAnnotate, 929 | buttonOK 930 | ]); 931 | 932 | var responseCode = alertWindow.runModal(); 933 | 934 | if (responseCode == 1000) { 935 | try { 936 | sncr.command.setValue_forKey_onLayer(autoAnnotate.state(),"autoAnnotate",sncr.data); 937 | } 938 | catch(err) { 939 | log(sncr.strings["general-save-failed"]); 940 | } 941 | } else return false; 942 | } 943 | // Otherwise operate in run mode... 944 | else { 945 | // Return updated settings 946 | return { 947 | autoAnnotate : defaultSettings.autoAnnotate 948 | } 949 | } 950 | } 951 | } 952 | 953 | sncr.common = { 954 | linkObject : function(layer,destination,type) { 955 | switch (type) { 956 | case "condition" : 957 | sncr.command.setValue_forKey_onLayer(type,"linkType",layer); 958 | sncr.command.setValue_forKey_onLayer(destination.objectID(),"linkedObject",layer); 959 | sncr.command.setValue_forKey_onLayer(destination.parentArtboard().objectID(),"linkedParentArtboard",layer); 960 | 961 | break; 962 | case "description" : 963 | sncr.command.setValue_forKey_onLayer(type,"linkType",layer); 964 | sncr.command.setValue_forKey_onLayer(destination,sncr.descriptions.config.descriptionLinkKey,layer); 965 | 966 | break; 967 | case "section" : 968 | sncr.command.setValue_forKey_onLayer(type,"linkType",layer); 969 | sncr.command.setValue_forKey_onLayer(destination,sncr.sections.config.titleLinkKey,layer); 970 | 971 | break; 972 | default : 973 | log("sncr.common.linkObject: Invalid or missing link type"); 974 | 975 | break; 976 | } 977 | } 978 | } 979 | 980 | sncr.conditions = { 981 | addEdit: function(context) { 982 | var selection = sncr.selection.firstObject(); 983 | 984 | if (sncr.selection.count() != 1 || sncr.selection.count() == 1 && selection.class() == "MSArtboardGroup") { 985 | sketch.UI.message("Select one layer which is not an artboard"); 986 | 987 | return; 988 | } 989 | 990 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:','" + sncr.pluginDomain + "').linkType == 'condition' && function(userInfo,'valueForKeyPath:','" + sncr.pluginDomain + "').linkedObject == '" + selection.objectID() + "'"), 991 | conditionGroup = sncr.page.artboards().filteredArrayUsingPredicate(predicate).firstObject(); 992 | 993 | if (!conditionGroup) { 994 | var conditionGroup = MSArtboardGroup.new(), 995 | insertIndex = getLayerIndex(selection.parentArtboard()) + 1; 996 | 997 | sncr.common.linkObject(conditionGroup,selection,"condition"); 998 | selection.parentArtboard().parentGroup().insertLayer_atIndex(conditionGroup,insertIndex); 999 | } 1000 | 1001 | conditionGroup.setName("-Conditions-"); 1002 | conditionGroup.setHasBackgroundColor(1); 1003 | conditionGroup.setBackgroundColor(MSImmutableColor.colorWithSVGString("#F2F2F2")); 1004 | 1005 | sncr.layout.preclude(conditionGroup); 1006 | 1007 | conditionGroup.absoluteRect().setX(CGRectGetMaxX(selection.parentArtboard().rect()) + sncr.annotations.config.annotationXOffset); 1008 | conditionGroup.absoluteRect().setY(selection.absoluteRect().y() + selection.frame().height() / 2 - 16); 1009 | 1010 | var predicate = NSPredicate.predicateWithFormat("className == %@","MSTextLayer"), 1011 | conditions = conditionGroup.children().filteredArrayUsingPredicate(predicate); 1012 | 1013 | var alertWindow = COSAlertWindow.new(); 1014 | alertWindow.setMessageText("Conditions for " + selection.name()); 1015 | 1016 | if (conditions.count() > 0) { 1017 | for (var i = 0; i < conditions.count(); i++) { 1018 | alertWindow.addAccessoryView(createLabel("Condition " + (i + 1),NSMakeRect(0,0,160,16))); 1019 | alertWindow.addAccessoryView(createField(conditions[i].stringValue(),NSMakeRect(0,0,300,20))); 1020 | } 1021 | } 1022 | 1023 | alertWindow.addAccessoryView(createLabel("New Condition ",NSMakeRect(0,0,160,16))); 1024 | alertWindow.addAccessoryView(createField("",NSMakeRect(0,0,300,20))); 1025 | 1026 | alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]); 1027 | alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]); 1028 | alertWindow.addButtonWithTitle("Add Condition"); 1029 | 1030 | var responseCode = alertWindow.runModal(); 1031 | 1032 | if (responseCode == 1000) { 1033 | // Do something 1034 | } else if (responseCode == 1002) { 1035 | // Add new condition to dialog 1036 | } else return false; 1037 | 1038 | // Add in condition groups/texts 1039 | // Resize to fit 1040 | 1041 | conditionGroup.frame().setWidth(200); 1042 | conditionGroup.frame().setHeight(200); 1043 | } 1044 | } 1045 | 1046 | sncr.descriptions = { 1047 | addEdit : function(context) { 1048 | // If there is one artboard selected... 1049 | if (sncr.selection.count() == 1 && (sncr.selection[0] instanceof MSArtboardGroup || sncr.selection[0] instanceof MSSymbolMaster)) { 1050 | // Artboard variable 1051 | var artboard = sncr.selection[0]; 1052 | 1053 | // Get existing artboard description for selected artboard 1054 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.descriptions.config.descriptionLinkKey + " == '" + artboard.objectID() + "'",sncr.pluginDomain), 1055 | linkedDescription = sncr.page.children().filteredArrayUsingPredicate(predicate).firstObject(); 1056 | 1057 | // Determine the initial artboard description value 1058 | var artboardDescription = (linkedDescription) ? linkedDescription.stringValue() : ""; 1059 | 1060 | // Present add/edit window with artboard description value 1061 | artboardDescription = getArtboardDescription(artboard.name(),artboardDescription); 1062 | 1063 | // If artboard description value was returned 1064 | if (artboardDescription) { 1065 | // If artboard description already existed... 1066 | if (linkedDescription) { 1067 | // Update the artboard description with new value 1068 | linkedDescription.setStringValue(artboardDescription); 1069 | 1070 | // Display feedback 1071 | sketch.UI.message(sncr.strings["description-update-complete"]); 1072 | } 1073 | // If artboard description did not exist... 1074 | else { 1075 | // Set parent group 1076 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName); 1077 | 1078 | // Set annotation group 1079 | var descGroup = getChildGroup(parentGroup,sncr.descriptionsGroupName); 1080 | 1081 | // Set artboard description style 1082 | var artboardDescStyle = getTextStyle(sncr.descriptions.config.descriptionStyleName,sncr.descriptions.config.descriptionStyleData); 1083 | 1084 | // Create new artboard description text layer 1085 | var artboardDesc = MSTextLayer.new(); 1086 | artboardDesc.setStringValue(artboardDescription); 1087 | artboardDesc.setName(sncr.descriptions.config.descriptionLinkPrefix + artboard.name()); 1088 | artboardDesc.setTextBehaviour(0); 1089 | 1090 | // Apply style to artboard description 1091 | if (artboardDescStyle.newInstance) { 1092 | artboardDesc.setStyle(artboardDescStyle.newInstance()); 1093 | } else { 1094 | artboardDesc.setSharedStyle(artboardDescStyle); 1095 | } 1096 | 1097 | // Add artboard description to annotation group 1098 | descGroup.addLayers([artboardDesc]); 1099 | 1100 | // Set artboard description x/y in relation to artboard, with offsets 1101 | artboardDesc.absoluteRect().setX(artboard.frame().x() + sncr.descriptions.config.descriptionXOffset); 1102 | artboardDesc.absoluteRect().setY(artboard.frame().y() + artboard.frame().height() + sncr.descriptions.config.descriptionYOffset); 1103 | 1104 | // Set artboard description width 1105 | artboardDesc.frame().setWidth(artboard.frame().width()); 1106 | artboardDesc.setTextBehaviour(1); 1107 | 1108 | // Resize description and parent groups to account for children 1109 | if (sketch.version.sketch > 52) { 1110 | descGroup.fixGeometryWithOptions(0); 1111 | parentGroup.fixGeometryWithOptions(0); 1112 | } else { 1113 | descGroup.resizeToFitChildrenWithOption(0); 1114 | parentGroup.resizeToFitChildrenWithOption(0); 1115 | } 1116 | 1117 | // Set stored value for linked artboard 1118 | sncr.command.setValue_forKey_onLayer(artboard.objectID(),sncr.descriptions.config.descriptionLinkKey,artboardDesc); 1119 | 1120 | // Display feedback 1121 | sketch.UI.message(sncr.strings["description-set-complete"]); 1122 | } 1123 | } 1124 | 1125 | function getArtboardDescription(artboardName,descriptionVal) { 1126 | var alertWindow = COSAlertWindow.new(); 1127 | 1128 | alertWindow.setMessageText(sncr.strings["description-set-plugin"]); 1129 | 1130 | alertWindow.addTextLabelWithValue("For " + artboardName + ":"); 1131 | 1132 | var descriptionText = createField(descriptionVal,NSMakeRect(0,0,300,120)); 1133 | alertWindow.addAccessoryView(descriptionText); 1134 | 1135 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]); 1136 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]); 1137 | 1138 | setKeyOrder(alertWindow,[ 1139 | descriptionText, 1140 | buttonOK 1141 | ]); 1142 | 1143 | var responseCode = alertWindow.runModal(); 1144 | 1145 | if (responseCode == 1000) { 1146 | return descriptionText.stringValue(); 1147 | } else return false; 1148 | } 1149 | } 1150 | // If there is not one artboard selected... 1151 | else { 1152 | // Display feedback 1153 | sketch.UI.alert(sncr.strings["description-set-plugin"],sncr.strings["description-set-problem"]); 1154 | } 1155 | }, 1156 | linkSelected : function(context) { 1157 | // Validate the selections to link 1158 | var selections = sncr.descriptions.validateSelected(context); 1159 | 1160 | // If selections are valid... 1161 | if (selections) { 1162 | // Set stored value for linked artboard 1163 | sncr.common.linkObject(selections.description,selections.artboard.objectID(),"description"); 1164 | 1165 | // Set parent group 1166 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName); 1167 | 1168 | // Set annotation group 1169 | var descGroup = getChildGroup(parentGroup,sncr.descriptionsGroupName); 1170 | 1171 | // Set artboard description x/y in relation to artboard, with offsets 1172 | selections.description.absoluteRect().setX(selections.artboard.frame().x() + sncr.descriptions.config.descriptionXOffset); 1173 | selections.description.absoluteRect().setY(selections.artboard.frame().y() + selections.artboard.frame().height() + sncr.descriptions.config.descriptionYOffset); 1174 | 1175 | // Set artboard description text behavior 1176 | selections.description.setTextBehaviour(0); 1177 | 1178 | // Set artboard description width 1179 | selections.description.frame().setWidth(selections.artboard.frame().width()); 1180 | selections.description.setTextBehaviour(1); 1181 | 1182 | // If the artboard description is not in the description group... 1183 | if (selections.description.parentGroup() != descGroup) { 1184 | // Move the artboard description to the description group 1185 | selections.description.moveToLayer_beforeLayer(descGroup,nil); 1186 | 1187 | // Deselect the artboard description (moveToLayer_beforeLayer selects it) 1188 | selections.description.select_byExtendingSelection(0,1); 1189 | } 1190 | 1191 | // Deselect the artboard 1192 | selections.artboard.select_byExtendingSelection(0,1); 1193 | 1194 | // Resize description and parent groups to account for children 1195 | if (sketch.version.sketch > 52) { 1196 | descGroup.fixGeometryWithOptions(0); 1197 | parentGroup.fixGeometryWithOptions(0); 1198 | } else { 1199 | descGroup.resizeToFitChildrenWithOption(0); 1200 | parentGroup.resizeToFitChildrenWithOption(0); 1201 | } 1202 | 1203 | // Set layer name 1204 | var layerName = sncr.descriptions.config.descriptionLinkPrefix + selections.artboard.name(); 1205 | 1206 | // Update the layer name 1207 | selections.description.setName(layerName); 1208 | 1209 | // Create a log event 1210 | log(layerName + sncr.strings["description-link-complete"] + selections.artboard.name()); 1211 | 1212 | // Display feedback 1213 | sketch.UI.message(layerName + sncr.strings["description-link-complete"] + selections.artboard.name()); 1214 | } 1215 | }, 1216 | unlinkSelected : function(context) { 1217 | // If there are selections... 1218 | if (sncr.selection.count() > 0) { 1219 | // Set a counter 1220 | var count = 0; 1221 | 1222 | // Iterate through selections... 1223 | for (var i = 0; i < sncr.selection.count(); i++) { 1224 | // Get stored value for linked artboard 1225 | var linkedArtboard = sncr.command.valueForKey_onLayer(sncr.descriptions.config.descriptionLinkKey,sncr.selection[i]); 1226 | 1227 | // If selection is linked to an artboard... 1228 | if (linkedArtboard) { 1229 | // Set linked artboard value to nil 1230 | sncr.command.setValue_forKey_onLayer(nil,sncr.descriptions.config.descriptionLinkKey,sncr.selection[i]); 1231 | 1232 | // Set the layer name 1233 | var layerName = sncr.selection[i].name().replace(sncr.descriptions.config.descriptionLinkPrefix,"")); 1234 | 1235 | // Update the title name 1236 | sncr.selection[i].setName(layerName); 1237 | 1238 | // For logging purposes, get linked artboard object 1239 | var artboard = findLayerByID(sncr.selection[i].parentGroup(),linkedArtboard); 1240 | 1241 | // If artboard exists, use artboard name for name, otherwise use artboard ID 1242 | artboardName = (artboard) ? artboard.name() : linkedArtboard; 1243 | 1244 | // Create a log event 1245 | log(layerName + sncr.strings["description-unlink-complete"] + artboardName); 1246 | 1247 | // Iterate the counter 1248 | count++; 1249 | } 1250 | 1251 | // Deselect current selection 1252 | sncr.selection[i].select_byExtendingSelection(0,1); 1253 | } 1254 | 1255 | // Display feedback 1256 | sketch.UI.message(count + sncr.strings["description-unlinks-complete"]); 1257 | } 1258 | // If there are no selections... 1259 | else { 1260 | // Display feedback 1261 | sketch.UI.alert(sncr.strings["description-unlink-plugin"],sncr.strings["description-unlink-problem"]); 1262 | } 1263 | }, 1264 | updateAllOnPage: function(context,command) { 1265 | // If function was invoked by action, set command 1266 | if (!command && context.actionContext) command = "action"; 1267 | 1268 | logFunctionStart("Artboard Descriptions: Update",command); 1269 | 1270 | var settings = sncr.descriptions.settings(context,"update"); 1271 | 1272 | // Get descriptions on current page 1273 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.descriptions.config.descriptionLinkKey + " != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.descriptions.config.descriptionLinkTypeKey + " == nil",sncr.pluginDomain), 1274 | layers = sncr.page.children().filteredArrayUsingPredicate(predicate), 1275 | loop = layers.objectEnumerator(), 1276 | layer; 1277 | 1278 | // If there are descriptions... 1279 | if (layers.count() > 0) { 1280 | // Set counters 1281 | var updateCount = 0; 1282 | var removeCount = 0; 1283 | 1284 | // Set parent group 1285 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName); 1286 | 1287 | // Set annotation group 1288 | var descGroup = getChildGroup(parentGroup,sncr.descriptionsGroupName); 1289 | 1290 | // Iterate through descriptions... 1291 | while (layer = loop.nextObject()) { 1292 | sncr.command.setValue_forKey_onLayer(sncr.descriptions.config.descriptionLinkTypeValue,sncr.descriptions.config.descriptionLinkTypeKey,layer); 1293 | 1294 | // Get stored value for linked artboard 1295 | var linkedArtboard = sncr.command.valueForKey_onLayer(sncr.descriptions.config.descriptionLinkKey,layer); 1296 | 1297 | // Get linked artboard object, if it resides on the artboard description page 1298 | var predicate = NSPredicate.predicateWithFormat("objectID == %@",linkedArtboard,sncr.pluginDomain), 1299 | artboard = sncr.page.artboards().filteredArrayUsingPredicate(predicate).firstObject(); 1300 | 1301 | // If artboard object exists... 1302 | if (artboard) { 1303 | // Determine layer width 1304 | var layerWidth = (settings.descriptionWidth && settings.descriptionWidth != "") ? settings.descriptionWidth : artboard.frame().width(); 1305 | 1306 | // Set artboard description text behavior 1307 | layer.setTextBehaviour(0); 1308 | 1309 | // Set artboard description width 1310 | layer.frame().setWidth(layerWidth); 1311 | layer.setTextBehaviour(1); 1312 | 1313 | // Set artboard description x/y in relation to artboard, with offsets 1314 | if (settings.descriptionPosition == 1) { // Right 1315 | layer.absoluteRect().setX(artboard.frame().x() + artboard.frame().width() + settings.descriptionXOffset); 1316 | layer.absoluteRect().setY(artboard.frame().y() + settings.descriptionYOffset); 1317 | } else if (settings.descriptionPosition == 2) { // Bottom 1318 | layer.absoluteRect().setX(artboard.frame().x() + settings.descriptionXOffset); 1319 | layer.absoluteRect().setY(artboard.frame().y() + artboard.frame().height() + settings.descriptionYOffset); 1320 | } else if (settings.descriptionPosition == 3) { // Left 1321 | layer.absoluteRect().setX(artboard.frame().x() + settings.descriptionXOffset - layerWidth); 1322 | layer.absoluteRect().setY(artboard.frame().y() + settings.descriptionYOffset); 1323 | } else { // Top 1324 | layer.absoluteRect().setX(artboard.frame().x() + settings.descriptionXOffset); 1325 | layer.absoluteRect().setY(artboard.frame().y() + settings.descriptionYOffset - layer.frame().height()); 1326 | } 1327 | 1328 | // If the artboard description is not in the description group... 1329 | if (layer.parentGroup() != descGroup) { 1330 | // Move the artboard description to the description group 1331 | layer.moveToLayer_beforeLayer(descGroup,nil); 1332 | 1333 | // Deselect the artboard description (moveToLayer_beforeLayer selects it) 1334 | layer.select_byExtendingSelection(0,1); 1335 | } 1336 | 1337 | // Set layer name 1338 | var layerName = sncr.descriptions.config.descriptionLinkPrefix + artboard.name(); 1339 | 1340 | // Update the layer name 1341 | layer.setName(layerName); 1342 | 1343 | // Iterate counter 1344 | updateCount++; 1345 | } 1346 | // If artboard object does not exist... 1347 | else { 1348 | // Remove stored value for linked artboard 1349 | sncr.command.setValue_forKey_onLayer(nil,sncr.descriptions.config.descriptionLinkKey,layer); 1350 | 1351 | // Set layer name 1352 | var layerName = layer.name().replace(sncr.descriptions.config.descriptionLinkPrefix,"")); 1353 | 1354 | // Update the layer name 1355 | layer.setName(layerName); 1356 | 1357 | // Create a log event 1358 | log(layerName + sncr.strings["description-unlink-complete"] + linkedArtboard); 1359 | 1360 | // Iterate counters 1361 | updateCount++; 1362 | removeCount++; 1363 | } 1364 | } 1365 | 1366 | // If description group is not empty... 1367 | if (descGroup.layers().count() > 0) { 1368 | // Resize description group to account for children 1369 | if (sketch.version.sketch > 52) { 1370 | descGroup.fixGeometryWithOptions(0); 1371 | } else { 1372 | descGroup.resizeToFitChildrenWithOption(0); 1373 | } 1374 | } 1375 | // If description group is empty... 1376 | else { 1377 | // Remove the description group 1378 | descGroup.removeFromParent(); 1379 | } 1380 | 1381 | // Resize parent group to account for children 1382 | if (sketch.version.sketch > 52) { 1383 | parentGroup.fixGeometryWithOptions(0); 1384 | } else { 1385 | parentGroup.resizeToFitChildrenWithOption(0); 1386 | } 1387 | 1388 | // Move parent group to the top of the layer list 1389 | parentGroup.moveToLayer_beforeLayer(sncr.page,nil); 1390 | 1391 | // Deselect parent group 1392 | parentGroup.select_byExtendingSelection(0,1); 1393 | 1394 | // If the function was not invoked by action... 1395 | if (!context.actionContext) { 1396 | // Lock the parent group 1397 | parentGroup.setIsLocked(1); 1398 | 1399 | // If any artboard links were removed 1400 | if (removeCount > 0) { 1401 | // Display feedback 1402 | sketch.UI.message(updateCount + sncr.strings["description-updates-complete"] + ", " + removeCount + sncr.strings["description-updates-complete-unlinked"]); 1403 | } else { 1404 | // Display feedback 1405 | sketch.UI.message(updateCount + sncr.strings["description-updates-complete"]); 1406 | } 1407 | } 1408 | } 1409 | }, 1410 | settings : function(context,command) { 1411 | var descriptionPositions = ["Top","Right","Bottom","Left"]; 1412 | 1413 | // Setting variables 1414 | var settings = {}; 1415 | settings.descriptionWidth = ""; 1416 | settings.descriptionPosition = 2; 1417 | settings.descriptionXOffset = sncr.descriptions.config.descriptionXOffset; 1418 | settings.descriptionYOffset = sncr.descriptions.config.descriptionYOffset; 1419 | 1420 | settings = getCachedSettings(context,sncr.data,settings,sncr.pluginDomain); 1421 | 1422 | if (!command) { 1423 | var alertWindow = COSAlertWindow.new(); 1424 | alertWindow.setMessageText(sncr.strings["description-settings-title"]); 1425 | 1426 | alertWindow.addTextLabelWithValue(sncr.strings["description-settings-width"]); 1427 | 1428 | var descriptionWidth = createField(settings.descriptionWidth,NSMakeRect(0,0,60,24)); 1429 | alertWindow.addAccessoryView(descriptionWidth); 1430 | 1431 | alertWindow.addTextLabelWithValue(sncr.strings["description-settings-position"]); 1432 | 1433 | var descriptionPosition = createSelect(descriptionPositions,settings.descriptionPosition,NSMakeRect(0,0,80,28)); 1434 | alertWindow.addAccessoryView(descriptionPosition); 1435 | 1436 | alertWindow.addTextLabelWithValue(sncr.strings["description-settings-offsetX"]); 1437 | 1438 | var descriptionXOffset = createField(settings.descriptionXOffset,NSMakeRect(0,0,60,24)); 1439 | alertWindow.addAccessoryView(descriptionXOffset); 1440 | 1441 | alertWindow.addTextLabelWithValue(sncr.strings["description-settings-offsetY"]); 1442 | 1443 | var descriptionYOffset = createField(settings.descriptionYOffset,NSMakeRect(0,0,60,24)); 1444 | alertWindow.addAccessoryView(descriptionYOffset); 1445 | 1446 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]); 1447 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]); 1448 | 1449 | setKeyOrder(alertWindow,[ 1450 | descriptionWidth, 1451 | descriptionPosition, 1452 | descriptionXOffset, 1453 | descriptionYOffset, 1454 | buttonOK 1455 | ]); 1456 | 1457 | var responseCode = alertWindow.runModal(); 1458 | 1459 | if (responseCode == 1000) { 1460 | try { 1461 | sncr.command.setValue_forKey_onLayer(descriptionWidth.stringValue(),"descriptionWidth",sncr.data); 1462 | sncr.command.setValue_forKey_onLayer(descriptionPosition.indexOfSelectedItem(),"descriptionPosition",sncr.data); 1463 | sncr.command.setValue_forKey_onLayer(Number(descriptionXOffset.stringValue()),"descriptionXOffset",sncr.data); 1464 | sncr.command.setValue_forKey_onLayer(Number(descriptionYOffset.stringValue()),"descriptionYOffset",sncr.data); 1465 | } 1466 | catch(err) { 1467 | log(sncr.strings["general-save-failed"]); 1468 | } 1469 | 1470 | sncr.descriptions.updateAllOnPage(context,"settings"); 1471 | } else return false; 1472 | } else { 1473 | return { 1474 | descriptionWidth : settings.descriptionWidth, 1475 | descriptionPosition : settings.descriptionPosition, 1476 | descriptionXOffset : settings.descriptionXOffset, 1477 | descriptionYOffset : settings.descriptionYOffset 1478 | } 1479 | } 1480 | }, 1481 | validateSelected: function(context) { 1482 | // Get latest selections, as they may have been changed by Insert 1483 | var selections = sncr.page.selectedLayers().layers(); 1484 | 1485 | // If two objects are not selected... 1486 | if (selections.count() != 2) { 1487 | // Display feedback 1488 | sketch.UI.alert(sncr.strings["description-link-plugin"],sncr.strings["description-link-problem"]); 1489 | 1490 | return false; 1491 | } 1492 | 1493 | // Selection variables 1494 | var firstObject = selections.firstObject(), 1495 | lastObject = selections.lastObject(); 1496 | 1497 | // If the first item is not an artboard and the second item is an artboard... 1498 | if ((firstObject.class() != "MSArtboardGroup" || firstObject.class() != "MSSymbolMaster") && (lastObject.class() == "MSArtboardGroup" || lastObject.class() == "MSSymbolMaster")) { 1499 | return { 1500 | description : firstObject, 1501 | artboard : lastObject 1502 | } 1503 | } 1504 | // If the first item is an artboard and the second item is not an artboard 1505 | else if ((firstObject.class() == "MSArtboardGroup" || firstObject.class() == "MSSymbolMaster") && (lastObject.class() != "MSArtboardGroup" || lastObject.class() != "MSSymbolMaster")) { 1506 | return { 1507 | description : lastObject, 1508 | artboard : firstObject 1509 | } 1510 | } 1511 | // If the selections are two artboards... 1512 | else { 1513 | // Display feedback 1514 | sketch.UI.alert(sncr.strings["description-link-plugin"],sncr.strings["description-link-problem"]); 1515 | 1516 | return false; 1517 | } 1518 | } 1519 | } 1520 | 1521 | sncr.layout = { 1522 | update: function(context) { 1523 | if (!context.actionContext) { 1524 | sncr.layout.includePage(context,false); 1525 | } else { 1526 | sncr.layout.sanitizePages(context); 1527 | } 1528 | 1529 | if (sncr.command.valueForKey_onLayer_forPluginIdentifier(sncr.layout.config.featureKey,sncr.page,sncr.pluginDomain) != false) { 1530 | // Get artboards to layout 1531 | var predicate = NSPredicate.predicateWithFormat("userInfo == nil || function(userInfo,'valueForKeyPath:',%@)." + sncr.layout.config.featureKey + " != " + false,sncr.pluginDomain), 1532 | artboards = sncr.page.artboards().filteredArrayUsingPredicate(predicate), 1533 | artboardLoop = artboards.objectEnumerator(), 1534 | artboard; 1535 | 1536 | // If there artboards to layout... 1537 | if (artboards.count()) { 1538 | // Reset page origin 1539 | sncr.page.setRulerBase(CGPointMake(0,0)); 1540 | 1541 | // Get layout settings 1542 | var layoutSettings = sncr.layout.settings(context,"update"); 1543 | 1544 | // If artboards should be sorted... 1545 | if (layoutSettings.sortOrder != 0) { 1546 | var sortByName = NSSortDescriptor.sortDescriptorWithKey_ascending("name",1), 1547 | artboards = artboards.sortedArrayUsingDescriptors([sortByName]); 1548 | 1549 | var layoutLayers = (layoutSettings.sortOrder == 2) ? artboards.reverseObjectEnumerator().allObjects() : artboards; 1550 | 1551 | sortLayerList(layoutLayers,sncr.page); 1552 | } 1553 | 1554 | var firstBoard = artboards.objectAtIndex(0), 1555 | lastBoard = artboards.objectAtIndex(artboards.count() - 1), 1556 | lastBoardPrefix = 0, 1557 | groupType = parseInt(firstBoard.name()) == parseInt(lastBoard.name()) ? 0 : 1, 1558 | groupCount = 1, 1559 | groupLayout = []; 1560 | 1561 | while (artboard = artboardLoop.nextObject()) { 1562 | var artboardName = artboard.name(), 1563 | thisBoardPrefix = (groupType == 0) ? parseFloat(artboardName) : parseInt(artboardName); 1564 | 1565 | if (lastBoardPrefix != 0 && lastBoardPrefix != thisBoardPrefix) { 1566 | groupCount++; 1567 | } 1568 | 1569 | groupLayout.push({ 1570 | artboard : artboardName, 1571 | prefix : thisBoardPrefix, 1572 | group : groupCount, 1573 | width : artboard.frame().width() 1574 | }); 1575 | 1576 | lastBoardPrefix = thisBoardPrefix; 1577 | } 1578 | 1579 | var x = 0, 1580 | y = 0, 1581 | xPad = parseInt(layoutSettings.xPad), 1582 | yPad = parseInt(layoutSettings.yPad), 1583 | xCount = 0, 1584 | rowCount = layoutSettings.rowCount, 1585 | rowHeight = 0, 1586 | groupCount = 1; 1587 | 1588 | for (var i = 0; i < groupLayout.length; i++) { 1589 | var artboard = artboards.objectAtIndex(i), 1590 | artboardFrame = artboard.frame(); 1591 | 1592 | // If starting a new group, reset x and calculate the y position of the next row 1593 | if (groupLayout[i]['group'] != groupCount) { 1594 | var nextGroupTotal = groupCounter(groupCount + 1,groupLayout); 1595 | 1596 | if (parseInt(layoutSettings.fitWidth)) { 1597 | if (layoutSettings.rowDensity == 1 || (rowCount - (xCount + 1)) < nextGroupTotal) { 1598 | x = 0; 1599 | y += rowHeight + yPad; 1600 | rowHeight = 0; 1601 | xCount = 0; 1602 | } else { 1603 | x += artboardFrame.width() + xPad; 1604 | xCount++; 1605 | } 1606 | } else { 1607 | if (layoutSettings.rowDensity == 1 || (rowCount - (xCount + 1)) < nextGroupTotal) { 1608 | x = 0; 1609 | y += rowHeight + yPad; 1610 | rowHeight = 0; 1611 | xCount = 0; 1612 | } else { 1613 | x += artboardFrame.width() + xPad; 1614 | xCount++; 1615 | } 1616 | } 1617 | 1618 | groupCount++; 1619 | } 1620 | 1621 | // If new line is detected but is continuation of group, give smaller vertical padding 1622 | if (x == 0 && xCount != 0) { 1623 | y += yPad / 2; 1624 | } 1625 | 1626 | // Position current artboard 1627 | artboardFrame.x = x; 1628 | artboardFrame.y = y; 1629 | 1630 | // Keep track if this artboard is taller than previous artboards in row 1631 | if (artboardFrame.height() > rowHeight) { 1632 | rowHeight = artboardFrame.height(); 1633 | } 1634 | 1635 | if (parseInt(layoutSettings.fitWidth)) { 1636 | if (x + artboardFrame.width() + xPad * 2 > layoutSettings.fitWidth) { 1637 | x = 0; 1638 | y += rowHeight; 1639 | rowHeight = 0; 1640 | } else { 1641 | x += artboardFrame.width() + xPad; 1642 | } 1643 | } else { 1644 | // Determine if this is the last artboard the row, reset x and calculate the y position of the next row 1645 | if ((xCount + 1) % rowCount == 0) { 1646 | x = 0; 1647 | y += rowHeight; 1648 | rowHeight = 0; 1649 | } else { 1650 | x += artboardFrame.width() + xPad; 1651 | } 1652 | } 1653 | 1654 | xCount++; 1655 | } 1656 | 1657 | if (layoutSettings.autoSections) sncr.sections.updateAllOnPage(context,"layout"); 1658 | if (layoutSettings.autoTitles) sncr.titles.create(context,"layout"); 1659 | if (layoutSettings.autoDescriptions) sncr.descriptions.updateAllOnPage(context,"layout"); 1660 | if (layoutSettings.autoAnnotations) sncr.annotations.updateAnnotations(context); 1661 | 1662 | // Collapse everything if run manually 1663 | if (!context.actionContext) actionWithType(context,"MSCollapseAllGroupsAction").doPerformAction(nil); 1664 | 1665 | // Feedback to user 1666 | sketch.UI.message(sncr.strings["layout-artboards-complete"]); 1667 | } 1668 | 1669 | function groupCounter(group,obj) { 1670 | var count = 0; 1671 | 1672 | for (var i = 0; i < obj.length; ++i) { 1673 | if (obj[i]['group'] == group) { 1674 | count++; 1675 | } 1676 | } 1677 | 1678 | return count; 1679 | } 1680 | 1681 | function sortLayerList(artboards,output) { 1682 | var loop = artboards.objectEnumerator(), artboard; 1683 | 1684 | while (artboard = loop.nextObject()) { 1685 | artboard.moveToLayer_beforeLayer(output,nil); 1686 | artboard.select_byExtendingSelection(0,1); 1687 | } 1688 | } 1689 | } 1690 | }, 1691 | includeSelected: function(context) { 1692 | var count = 0; 1693 | 1694 | if (sncr.selection.count()) { 1695 | for (var i = 0; i < sncr.selection.count(); i++) { 1696 | if (sncr.selection[i] instanceof MSArtboardGroup) { 1697 | sncr.layout.include(sncr.selection[i]); 1698 | 1699 | count++; 1700 | 1701 | log(sncr.selection[i].name() + sncr.strings["layout-include-complete"]); 1702 | } 1703 | } 1704 | 1705 | if (sncr.selection.count() == 1) { 1706 | sketch.UI.message(sncr.selection[0].name() + sncr.strings["layout-include-complete"]); 1707 | } else { 1708 | sketch.UI.message(count + sncr.strings["layout-includes-complete"]); 1709 | } 1710 | } else { 1711 | sketch.UI.alert(sncr.strings["layout-include-plugin"],sncr.strings["layout-include-problem"]); 1712 | } 1713 | }, 1714 | include: function(object) { 1715 | sncr.command.setValue_forKey_onLayer(true,sncr.layout.config.featureKey,object); 1716 | }, 1717 | precludeSelected: function(context) { 1718 | var count = 0; 1719 | 1720 | if (sncr.selection.count()) { 1721 | for (var i = 0; i < sncr.selection.count(); i++) { 1722 | if (sncr.selection[i] instanceof MSArtboardGroup) { 1723 | sncr.layout.preclude(sncr.selection[i]); 1724 | 1725 | count++; 1726 | 1727 | log(sncr.selection[i].name() + sncr.strings["layout-preclude-complete"]); 1728 | } 1729 | } 1730 | 1731 | if (sncr.selection.count() == 1) { 1732 | sketch.UI.message(sncr.selection[0].name() + sncr.strings["layout-preclude-complete"]); 1733 | } else { 1734 | sketch.UI.message(count + sncr.strings["layout-precludes-complete"]); 1735 | } 1736 | } else { 1737 | sketch.UI.alert(sncr.strings["layout-preclude-plugin"],sncr.strings["layout-preclude-problem"]); 1738 | } 1739 | }, 1740 | preclude: function(object) { 1741 | sncr.command.setValue_forKey_onLayer(false,sncr.layout.config.featureKey,object); 1742 | }, 1743 | includePage: function(context,feedback) { 1744 | sncr.layout.include(sncr.page); 1745 | 1746 | sncr.layout.sanitizePages(context); 1747 | 1748 | if (!feedback) { 1749 | sketch.UI.message(sncr.page.name() + sncr.strings["layout-include-page-complete"]); 1750 | } 1751 | }, 1752 | precludePage: function(context) { 1753 | sncr.layout.preclude(sncr.page); 1754 | 1755 | sncr.layout.sanitizePages(context); 1756 | 1757 | sketch.UI.message(sncr.page.name() + sncr.strings["layout-preclude-page-complete"]); 1758 | }, 1759 | settings: function(context,command) { 1760 | // Type objects 1761 | var layoutTypes = ["Dense layout","Loose layout"], 1762 | sortTypes = ["Do not sort anything","Sort layers and artboards","Sort layers and artboards, reverse layer order"]; 1763 | 1764 | // Default settings 1765 | var defaultSettings = {}; 1766 | defaultSettings.rowCount = 8; 1767 | defaultSettings.fitWidth = ''; 1768 | defaultSettings.rowDensity = 0; 1769 | defaultSettings.sortOrder = 0; 1770 | defaultSettings.xPad = "400"; 1771 | defaultSettings.yPad = "600"; 1772 | defaultSettings.autoSections = 1; 1773 | defaultSettings.autoTitles = 1; 1774 | defaultSettings.autoDescriptions = 1; 1775 | defaultSettings.autoAnnotations = 1; 1776 | 1777 | // Update default settings with cached settings 1778 | defaultSettings = getCachedSettings(context,sncr.page,defaultSettings,sncr.pluginDomain); 1779 | 1780 | // Check for old artboardsPerRowDefault value 1781 | var artboardsPerRowDefault = sncr.command.valueForKey_onLayer_forPluginIdentifier("artboardsPerRowDefault",sncr.page,sncr.pluginDomain); 1782 | 1783 | // If artboardsPerRowDefault value exists... 1784 | if (artboardsPerRowDefault) { 1785 | // Old artboardsPerRow object 1786 | var artboardsPerRow = ["4","6","8","10","12","14","100"]; 1787 | 1788 | // Overwrite default value with old artboardsPerRow value 1789 | defaultSettings.rowCount = artboardsPerRow[artboardsPerRowDefault]; 1790 | } 1791 | 1792 | // If a command is not passed, operate in config mode... 1793 | if (!command) { 1794 | var alertWindow = COSAlertWindow.new(); 1795 | alertWindow.setMessageText(sncr.strings["layout-settings-title"]); 1796 | 1797 | alertWindow.addTextLabelWithValue(sncr.strings["layout-settings-artboard-count"]); 1798 | 1799 | var rowCount = createField(defaultSettings.rowCount,NSMakeRect(0,0,60,22)); 1800 | alertWindow.addAccessoryView(rowCount); 1801 | 1802 | alertWindow.addTextLabelWithValue('Fit pixel width:'); 1803 | 1804 | var fitWidth = createField(defaultSettings.fitWidth,NSMakeRect(0,0,60,22)); 1805 | alertWindow.addAccessoryView(fitWidth); 1806 | 1807 | var fitWidthDelegate = new MochaJSDelegate({ 1808 | "controlTextDidChange:" : (function() { 1809 | if (fitWidth.stringValue() != '') { 1810 | rowCount.setEnabled(0); 1811 | } else { 1812 | rowCount.setEnabled(1); 1813 | } 1814 | }) 1815 | }); 1816 | 1817 | fitWidth.setDelegate(fitWidthDelegate.getClassInstance()); 1818 | 1819 | alertWindow.addTextLabelWithValue(sncr.strings["layout-settings-layout-type"]); 1820 | 1821 | var rowDensity = createRadioButtons(layoutTypes,defaultSettings.rowDensity); 1822 | alertWindow.addAccessoryView(rowDensity); 1823 | 1824 | alertWindow.addTextLabelWithValue(sncr.strings["layout-settings-sort-type"]); 1825 | 1826 | var sortOrder = createRadioButtons(sortTypes,defaultSettings.sortOrder); 1827 | alertWindow.addAccessoryView(sortOrder); 1828 | 1829 | alertWindow.addTextLabelWithValue(sncr.strings["layout-settings-spacing-horizontal"]); 1830 | 1831 | var xPad = createField(defaultSettings.xPad,NSMakeRect(0,0,60,22)); 1832 | alertWindow.addAccessoryView(xPad); 1833 | 1834 | alertWindow.addTextLabelWithValue(sncr.strings["layout-settings-spacing-vertical"]); 1835 | 1836 | var yPad = createField(defaultSettings.yPad,NSMakeRect(0,0,60,22)); 1837 | alertWindow.addAccessoryView(yPad); 1838 | 1839 | alertWindow.addTextLabelWithValue(""); 1840 | 1841 | var autoSections = createCheckbox({ 1842 | name : "Automatically adjust section titles", 1843 | value: 1 1844 | },defaultSettings.autoSections,NSMakeRect(0,0,300,18)); 1845 | 1846 | alertWindow.addAccessoryView(autoSections); 1847 | 1848 | var autoTitles = createCheckbox({ 1849 | name : "Automatically adjust artboard titles", 1850 | value: 1 1851 | },defaultSettings.autoTitles,NSMakeRect(0,0,300,18)); 1852 | 1853 | alertWindow.addAccessoryView(autoTitles); 1854 | 1855 | var autoDescriptions = createCheckbox({ 1856 | name : "Automatically adjust artboard descriptions", 1857 | value: 1 1858 | },defaultSettings.autoDescriptions,NSMakeRect(0,0,300,18)); 1859 | 1860 | alertWindow.addAccessoryView(autoDescriptions); 1861 | 1862 | var autoAnnotations = createCheckbox({ 1863 | name : "Automatically adjust layer annotations", 1864 | value: 1 1865 | },defaultSettings.autoAnnotations,NSMakeRect(0,0,300,18)); 1866 | 1867 | alertWindow.addAccessoryView(autoAnnotations); 1868 | 1869 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]); 1870 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]); 1871 | 1872 | // Set key order and first responder 1873 | setKeyOrder(alertWindow,[ 1874 | rowCount, 1875 | fitWidth, 1876 | rowDensity, 1877 | sortOrder, 1878 | xPad, 1879 | yPad, 1880 | autoSections, 1881 | autoTitles, 1882 | autoDescriptions, 1883 | autoAnnotations, 1884 | buttonOK 1885 | ]); 1886 | 1887 | var responseCode = alertWindow.runModal(); 1888 | 1889 | if (responseCode == 1000) { 1890 | try { 1891 | if (artboardsPerRowDefault) sncr.command.setValue_forKey_onLayer(nil,"artboardsPerRowDefault",sncr.page); 1892 | sncr.command.setValue_forKey_onLayer(rowCount.stringValue(),"rowCount",sncr.page); 1893 | sncr.command.setValue_forKey_onLayer(fitWidth.stringValue(),"fitWidth",sncr.page); 1894 | sncr.command.setValue_forKey_onLayer(rowDensity.selectedCell().tag(),"rowDensity",sncr.page); 1895 | sncr.command.setValue_forKey_onLayer(sortOrder.selectedCell().tag(),"sortOrder",sncr.page); 1896 | sncr.command.setValue_forKey_onLayer(xPad.stringValue(),"xPad",sncr.page); 1897 | sncr.command.setValue_forKey_onLayer(yPad.stringValue(),"yPad",sncr.page); 1898 | sncr.command.setValue_forKey_onLayer(autoSections.state(),"autoSections",sncr.page); 1899 | sncr.command.setValue_forKey_onLayer(autoTitles.state(),"autoTitles",sncr.page); 1900 | sncr.command.setValue_forKey_onLayer(autoDescriptions.state(),"autoDescriptions",sncr.page); 1901 | sncr.command.setValue_forKey_onLayer(autoAnnotations.state(),"autoAnnotations",sncr.page); 1902 | } 1903 | catch(err) { 1904 | log(sncr.strings["general-save-failed"]); 1905 | } 1906 | 1907 | sncr.layout.update(context); 1908 | } else return false; 1909 | } 1910 | // Otherwise operate in run mode... 1911 | else { 1912 | // Return settings 1913 | return { 1914 | rowCount : defaultSettings.rowCount, 1915 | fitWidth : defaultSettings.fitWidth, 1916 | rowDensity : defaultSettings.rowDensity, 1917 | sortOrder : defaultSettings.sortOrder, 1918 | xPad : defaultSettings.xPad, 1919 | yPad : defaultSettings.yPad, 1920 | autoSections : defaultSettings.autoSections, 1921 | autoTitles : defaultSettings.autoTitles, 1922 | autoDescriptions : defaultSettings.autoDescriptions, 1923 | autoAnnotations : defaultSettings.autoAnnotations 1924 | } 1925 | } 1926 | }, 1927 | sanitizePages: function(context) { 1928 | var loop = sncr.pages.objectEnumerator(), 1929 | page, 1930 | pageName; 1931 | 1932 | while (page = loop.nextObject()) { 1933 | if (!sncr.command.valueForKey_onLayer(sncr.layout.config.featureKey,page)) { 1934 | sncr.command.setValue_forKey_onLayer(false,sncr.layout.config.featureKey,page); 1935 | } else { 1936 | pageName = (sncr.command.valueForKey_onLayer(sncr.layout.config.featureKey,page) == true) ? sncr.layout.config.pageNamePrefix + page.name().replace(sncr.layout.config.pageNamePrefix,"") : page.name().replace(sncr.layout.config.pageNamePrefix,""); 1937 | 1938 | page.setName(pageName); 1939 | } 1940 | } 1941 | } 1942 | } 1943 | 1944 | sncr.sections = { 1945 | linkSelected: function(context,command) { 1946 | // Validate the selections to link 1947 | var selections = sncr.sections.validateSelected(context); 1948 | 1949 | // If selections are valid... 1950 | if (selections) { 1951 | // Set stored value for linked artboard 1952 | sncr.common.linkObject(selections.title,selections.artboard.objectID(),"section"); 1953 | 1954 | // Get layer name 1955 | var layerName = sncr.sections.getName(selections.title); 1956 | 1957 | // Update the layer name 1958 | selections.title.setName(layerName); 1959 | 1960 | // Create a log event 1961 | log(layerName + sncr.strings["section-link-complete"] + selections.artboard.name()); 1962 | 1963 | // Update all section titles on the page 1964 | sncr.sections.updateAllOnPage(context,"link"); 1965 | 1966 | // Display feedback 1967 | sketch.UI.message(layerName + sncr.strings["section-link-complete"] + selections.artboard.name()); 1968 | } 1969 | }, 1970 | unlinkSelected: function(context) { 1971 | // If there are selections... 1972 | if (sncr.selection.count() > 0) { 1973 | // Iterative variables 1974 | var titleName, 1975 | artboardName, 1976 | count = 0; 1977 | 1978 | // Iterate through selections... 1979 | for (var i = 0; i < sncr.selection.count(); i++) { 1980 | // Get stored value for linked artboard 1981 | var linkedArtboard = sncr.command.valueForKey_onLayer(sncr.sections.config.titleLinkKey,sncr.selection[i]); 1982 | 1983 | // If selection is linked to an artboard... 1984 | if (linkedArtboard) { 1985 | // Set linked artboard value to nil 1986 | sncr.command.setValue_forKey_onLayer(nil,sncr.sections.config.titleLinkKey,sncr.selection[i]); 1987 | 1988 | // Set the title name 1989 | titleName = sncr.selection[i].name().replace(sncr.sections.config.titleLinkPrefix,"")); 1990 | 1991 | // Update the title name 1992 | sncr.selection[i].setName(titleName); 1993 | 1994 | // Unlock the section title 1995 | sncr.selection[i].setIsLocked(0); 1996 | 1997 | // For logging purposes, get linked artboard object 1998 | var artboard = findLayerByID(sncr.selection[i].parentGroup(),linkedArtboard); 1999 | 2000 | // If artboard exists, use artboard name for name, otherwise use artboard ID 2001 | artboardName = (artboard) ? artboard.name() : linkedArtboard; 2002 | 2003 | // Create a log event 2004 | log(titleName + sncr.strings["section-unlink-complete"] + artboardName); 2005 | 2006 | // Iterate the counter 2007 | count++; 2008 | } 2009 | 2010 | // Deselect current selection 2011 | sncr.selection[i].select_byExtendingSelection(0,1); 2012 | } 2013 | 2014 | // If there is only one selection... 2015 | if (sncr.selection.count() == 1) { 2016 | // Display feedback 2017 | sketch.UI.message(titleName + sncr.strings["section-unlink-complete"] + artboardName); 2018 | } 2019 | // If there is more than one selection... 2020 | else { 2021 | // Display feedback 2022 | sketch.UI.message(count + sncr.strings["section-unlinks-complete"]); 2023 | } 2024 | } 2025 | // If there are no selections... 2026 | else { 2027 | // Display feedback 2028 | sketch.UI.alert(sncr.strings["section-unlink-plugin"],sncr.strings["section-unlink-problem"]); 2029 | } 2030 | }, 2031 | updateAllOnPage: function(context,command) { 2032 | // If function was invoked by action, set command 2033 | if (!command && context.actionContext) command = "action"; 2034 | 2035 | logFunctionStart("Section Titles: Update",command); 2036 | 2037 | var titleSettings = sncr.sections.settings(context,"update"); 2038 | 2039 | // Set remove counter 2040 | var removeCount = 0; 2041 | 2042 | // Get the section titles and construct a loop 2043 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.sections.config.titleLinkKey + " != nil",sncr.pluginDomain), 2044 | layers = sncr.page.children().filteredArrayUsingPredicate(predicate), 2045 | loop = layers.objectEnumerator(), 2046 | layer; 2047 | 2048 | // Iterate through section titles... 2049 | while (layer = loop.nextObject()) { 2050 | // Get stored value for linked artboard 2051 | var linkedArtboard = sncr.command.valueForKey_onLayer(sncr.sections.config.titleLinkKey,layer); 2052 | 2053 | // Get linked artboard object, if it resides on the artboard description page 2054 | var predicate = NSPredicate.predicateWithFormat("objectID == %@",linkedArtboard,sncr.pluginDomain), 2055 | artboard = sncr.page.artboards().filteredArrayUsingPredicate(predicate).firstObject(); 2056 | 2057 | // If artboard object exists... 2058 | if (artboard) { 2059 | // Set screen title x/y in relation to artboard, with offsets 2060 | layer.frame().setX(artboard.frame().x() + titleSettings.titleXOffset); 2061 | layer.frame().setY(artboard.frame().y() + titleSettings.titleYOffset - layer.frame().height()); 2062 | 2063 | if (titleSettings.titleWidth != "") { 2064 | layer.frame().setWidth(titleSettings.titleWidth); 2065 | } 2066 | 2067 | // Get layer name 2068 | var layerName = sncr.sections.getName(layer); 2069 | 2070 | // Update the layer name 2071 | layer.setName(layerName); 2072 | 2073 | // Lock the section title 2074 | layer.setIsLocked(1); 2075 | } 2076 | // If artboard object does not exist... 2077 | else { 2078 | // Remove stored value for linked artboard 2079 | sncr.command.setValue_forKey_onLayer(nil,sncr.sections.config.titleLinkKey,layer); 2080 | 2081 | // Set layer name 2082 | var layerName = layer.name().replace(sncr.sections.config.titleLinkPrefix,""); 2083 | 2084 | // Update the layer name 2085 | layer.setName(layerName); 2086 | 2087 | // Unlock the section title 2088 | layer.setIsLocked(0); 2089 | 2090 | // Create a log event 2091 | log(layerName + sncr.strings["section-unlink-complete"] + linkedArtboard); 2092 | 2093 | // Increment remove counter 2094 | removeCount++; 2095 | } 2096 | } 2097 | 2098 | // Switch message and handling per method the function was invoked 2099 | switch (command) { 2100 | case "action": 2101 | break; 2102 | case "link": 2103 | break; 2104 | case "settings": 2105 | // Display feedback 2106 | sketch.UI.message(sncr.strings["section-titles-updated"]); 2107 | 2108 | break; 2109 | default: 2110 | // If any artboard links were removed 2111 | if (removeCount > 0) { 2112 | // Display feedback 2113 | sketch.UI.message(sncr.strings["section-titles-updated"] + ", " + removeCount + sncr.strings["section-titles-updated-unlinked"]); 2114 | } else { 2115 | // Display feedback 2116 | sketch.UI.message(sncr.strings["section-titles-updated"]); 2117 | } 2118 | 2119 | break; 2120 | } 2121 | }, 2122 | settings: function(context,command) { 2123 | var defaultSettings = {}; 2124 | defaultSettings.sectionTitleWidth = ""; 2125 | defaultSettings.sectionTitleXOffset = 0; 2126 | defaultSettings.sectionTitleYOffset = -108; 2127 | 2128 | defaultSettings = getCachedSettings(context,sncr.data,defaultSettings,sncr.pluginDomain); 2129 | 2130 | if (!command) { 2131 | var alertWindow = COSAlertWindow.new(); 2132 | alertWindow.setMessageText(sncr.strings["section-settings-plugin"]); 2133 | 2134 | alertWindow.addTextLabelWithValue(sncr.strings["section-settings-width"]); 2135 | 2136 | var titleWidth = createField(defaultSettings.sectionTitleWidth,NSMakeRect(0,0,60,24)); 2137 | alertWindow.addAccessoryView(titleWidth); 2138 | 2139 | alertWindow.addTextLabelWithValue(sncr.strings["section-settings-offsetX"]); 2140 | 2141 | var titleXOffset = createField(defaultSettings.sectionTitleXOffset,NSMakeRect(0,0,60,24)); 2142 | alertWindow.addAccessoryView(titleXOffset); 2143 | 2144 | alertWindow.addTextLabelWithValue(sncr.strings["section-settings-offsetY"]); 2145 | 2146 | var titleYOffset = createField(defaultSettings.sectionTitleYOffset,NSMakeRect(0,0,60,24)); 2147 | alertWindow.addAccessoryView(titleYOffset); 2148 | 2149 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]); 2150 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]); 2151 | 2152 | setKeyOrder(alertWindow,[ 2153 | titleWidth, 2154 | titleXOffset, 2155 | titleYOffset, 2156 | buttonOK 2157 | ]); 2158 | 2159 | var responseCode = alertWindow.runModal(); 2160 | 2161 | if (responseCode == 1000) { 2162 | try { 2163 | sncr.command.setValue_forKey_onLayer(titleWidth.stringValue(),"sectionTitleWidth",sncr.data); 2164 | sncr.command.setValue_forKey_onLayer(Number(titleXOffset.stringValue()),"sectionTitleXOffset",sncr.data); 2165 | sncr.command.setValue_forKey_onLayer(Number(titleYOffset.stringValue()),"sectionTitleYOffset",sncr.data); 2166 | } 2167 | catch(err) { 2168 | log(sncr.strings["general-save-failed"]); 2169 | } 2170 | 2171 | sncr.sections.updateAllOnPage(context,"settings"); 2172 | } else return false; 2173 | } 2174 | // Otherwise operate in run mode... 2175 | else { 2176 | // Return updated settings 2177 | return { 2178 | titleWidth : defaultSettings.sectionTitleWidth, 2179 | titleXOffset : defaultSettings.sectionTitleXOffset, 2180 | titleYOffset : defaultSettings.sectionTitleYOffset 2181 | } 2182 | } 2183 | }, 2184 | validateSelected: function(context) { 2185 | // Get latest selections, as they may have been changed by Insert 2186 | let selections = sketch.getSelectedDocument().selectedLayers 2187 | 2188 | // If two objects are not selected... 2189 | if (selections.length != 2) { 2190 | // Display feedback 2191 | sketch.UI.alert(sncr.strings["section-link-plugin"],sncr.strings["section-link-problem"]) 2192 | 2193 | return false 2194 | } 2195 | 2196 | // Selection variables 2197 | let firstObject = selections.layers[0].sketchObject 2198 | let lastObject = selections.layers[1].sketchObject 2199 | 2200 | // If the first item is not an artboard and the second item is an artboard... 2201 | if ((firstObject.class() != "MSArtboardGroup" || firstObject.class() != "MSSymbolMaster") && (lastObject.class() == "MSArtboardGroup" || lastObject.class() == "MSSymbolMaster")) { 2202 | return { 2203 | title : firstObject, 2204 | artboard : lastObject 2205 | } 2206 | } 2207 | // If the first item is an artboard and the second item is not an artboard 2208 | else if ((firstObject.class() == "MSArtboardGroup" || firstObject.class() == "MSSymbolMaster") && (lastObject.class() != "MSArtboardGroup" || lastObject.class() != "MSSymbolMaster")) { 2209 | return { 2210 | title : lastObject, 2211 | artboard : firstObject 2212 | } 2213 | } 2214 | // If the selections are two artboards... 2215 | else { 2216 | // Display feedback 2217 | sketch.UI.alert(sncr.strings["section-link-plugin"],sncr.strings["section-link-problem"]) 2218 | 2219 | return false 2220 | } 2221 | }, 2222 | getName: function(object) { 2223 | var name; 2224 | 2225 | if (object.class() == "MSSymbolInstance" && object.overrides().allValues().firstObject()) { 2226 | name = sncr.sections.config.titleLinkPrefix + object.overrides().allValues().firstObject(); 2227 | } else if (object.class() == "MSTextLayer") { 2228 | name = sncr.sections.config.titleLinkPrefix + object.stringValue(); 2229 | } else { 2230 | name = sncr.sections.config.titleLinkPrefix + object.name().replace(sncr.sections.config.titleLinkPrefix,""); 2231 | } 2232 | 2233 | return name; 2234 | }, 2235 | 2236 | } 2237 | 2238 | sncr.titles = { 2239 | create: function(context,command) { 2240 | // If function was invoked by action, set command 2241 | if (!command && context.actionContext) command = "action"; 2242 | 2243 | logFunctionStart("Artboard Titles: Create",command); 2244 | 2245 | var titleSettings = sncr.titles.settings(context,"create"); 2246 | 2247 | // Set parent group 2248 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName); 2249 | 2250 | // Find and remove screen titles group if it exists on the page (the old location) 2251 | sncr.page.removeLayer(findLayerByName(sncr.page,sncr.titlesGroupName)); 2252 | 2253 | // Find and remove screen titles group if it exists in the parent group (the new location) 2254 | parentGroup.removeLayer(findLayerByName(parentGroup,sncr.titlesGroupName)); 2255 | 2256 | // Get a filtered list of artboards 2257 | var predicate = NSPredicate.predicateWithFormat("userInfo == nil || function(userInfo,'valueForKeyPath:',%@)." + sncr.titles.config.featureKey + " != " + false,sncr.pluginDomain), 2258 | artboards = sncr.page.artboards().filteredArrayUsingPredicate(predicate), 2259 | loop = artboards.objectEnumerator(), 2260 | artboard; 2261 | 2262 | // If artboards exist on the page... 2263 | if (artboards.length) { 2264 | // Screen title style 2265 | var screenTitleStyleName = "Wireframe/Screen Title"; 2266 | var screenTitleStyleData = { 2267 | fontFace : "Neue Haas Grotesk Text Std 75 Bold", 2268 | fontSize : 18, 2269 | lineHeight : 48, 2270 | textAlignment : 0 2271 | } 2272 | 2273 | // Screen title settings 2274 | var screenTitleOffset = parseInt(titleSettings.titleOffset); 2275 | 2276 | // Remove screen title style (the old style) 2277 | deleteTextStyle('Layout/Screen Title'); 2278 | 2279 | // Get screen title style (will add style if it doesn't exist) (the new style) 2280 | var screenTitleStyle = getTextStyle(screenTitleStyleName,screenTitleStyleData); 2281 | 2282 | // Create new screen title group 2283 | var titleGroup = MSLayerGroup.new(); 2284 | titleGroup.setName(sncr.titlesGroupName); 2285 | titleGroup.frame().setX(0 - parentGroup.frame().x()); 2286 | titleGroup.frame().setY(0 - parentGroup.frame().y()); 2287 | titleGroup.setHasClickThrough(true); 2288 | 2289 | // Iterate through the artboards... 2290 | while (artboard = loop.nextObject()) { 2291 | // Create a screen title 2292 | var screenTitle = MSTextLayer.new(); 2293 | screenTitle.setStringValue(artboard.name()); 2294 | screenTitle.setName(artboard.name()); 2295 | 2296 | // Apply style to screen title 2297 | if (screenTitle.newInstance) { 2298 | screenTitle.setStyle(screenTitleStyle.newInstance()); 2299 | } else { 2300 | screenTitle.setSharedStyle(screenTitleStyle); 2301 | } 2302 | 2303 | // Set screen title x/y position 2304 | screenTitle.frame().setX(artboard.frame().x()); 2305 | screenTitle.frame().setY(artboard.frame().y() + artboard.frame().height() + screenTitleOffset); 2306 | 2307 | // If user wants screen title below artboards... 2308 | if (titleSettings.titleType == 0) { 2309 | // Adjust screen title y position 2310 | screenTitle.frame().setY(artboard.frame().y() - (screenTitleStyleData.lineHeight + screenTitleOffset)); 2311 | } 2312 | 2313 | // Add screen title to title group 2314 | titleGroup.addLayers([screenTitle]); 2315 | } 2316 | 2317 | // Add title group to parent group 2318 | parentGroup.addLayers([titleGroup]); 2319 | 2320 | // Resize title and parents groups to account for children 2321 | if (sketch.version.sketch > 52) { 2322 | titleGroup.fixGeometryWithOptions(0); 2323 | parentGroup.fixGeometryWithOptions(0); 2324 | } else { 2325 | titleGroup.resizeToFitChildrenWithOption(0); 2326 | parentGroup.resizeToFitChildrenWithOption(0); 2327 | } 2328 | 2329 | // Collapse the parent group 2330 | parentGroup.setLayerListExpandedType(0); 2331 | 2332 | // Move parent group to the top of the layer list 2333 | parentGroup.moveToLayer_beforeLayer(sncr.page,nil); 2334 | 2335 | // Deselect parent group (moveToLayer_beforeLayer selects it) 2336 | parentGroup.select_byExtendingSelection(0,1); 2337 | 2338 | // Switch message and handling per method the function was invoked 2339 | switch (command) { 2340 | case "include": 2341 | break; 2342 | case "preclude": 2343 | break; 2344 | case "action": 2345 | break; 2346 | case "settings": 2347 | break; 2348 | case "layout": 2349 | break; 2350 | default: 2351 | // Lock the parent group 2352 | parentGroup.setIsLocked(1); 2353 | 2354 | // Display feedback 2355 | sketch.UI.message(sncr.strings["title-create-complete"]); 2356 | 2357 | break; 2358 | } 2359 | } 2360 | // If no artboards exist on the page... 2361 | else { 2362 | // Switch message and handling per method the function was invoked 2363 | switch (command) { 2364 | case "include": 2365 | break; 2366 | case "preclude": 2367 | break; 2368 | case "action": 2369 | break; 2370 | case "settings": 2371 | break; 2372 | case "layout": 2373 | break; 2374 | default: 2375 | // Display feedback 2376 | sketch.UI.alert(sncr.strings["title-create-plugin"],sncr.strings["title-create-problem"]); 2377 | 2378 | break; 2379 | } 2380 | } 2381 | }, 2382 | include: function(context) { 2383 | var count = 0; 2384 | 2385 | if (sncr.selection.count()) { 2386 | for (var i = 0; i < sncr.selection.count(); i++) { 2387 | if (sncr.selection[i] instanceof MSArtboardGroup) { 2388 | sncr.command.setValue_forKey_onLayer(true,sncr.titles.config.featureKey,sncr.selection[i]); 2389 | 2390 | count++; 2391 | 2392 | log(sncr.selection[i].name() + sncr.strings["title-include-complete"]); 2393 | } 2394 | } 2395 | 2396 | sncr.titles.create(context,"include"); 2397 | 2398 | if (sncr.selection.count() == 1) { 2399 | sketch.UI.message(sncr.selection[0].name() + sncr.strings["title-include-complete"]); 2400 | } else { 2401 | sketch.UI.message(count + sncr.strings["title-includes-complete"]); 2402 | } 2403 | } else { 2404 | sketch.UI.alert(sncr.strings["title-include-plugin"],sncr.strings["title-include-problem"]); 2405 | } 2406 | }, 2407 | preclude: function(context) { 2408 | var count = 0; 2409 | 2410 | if (sncr.selection.count()) { 2411 | for (var i = 0; i < sncr.selection.count(); i++) { 2412 | if (sncr.selection[i] instanceof MSArtboardGroup) { 2413 | sncr.command.setValue_forKey_onLayer(false,sncr.titles.config.featureKey,sncr.selection[i]); 2414 | 2415 | count++; 2416 | 2417 | log(sncr.selection[i].name() + sncr.strings["title-preclude-complete"]); 2418 | } 2419 | } 2420 | 2421 | sncr.titles.create(context,"preclude"); 2422 | 2423 | if (sncr.selection.count() == 1) { 2424 | sketch.UI.message(sncr.selection[0].name() + sncr.strings["title-preclude-complete"]); 2425 | } else { 2426 | sketch.UI.message(count + sncr.strings["title-precludes-complete"]); 2427 | } 2428 | } else { 2429 | sketch.UI.alert(sncr.strings["title-preclude-plugin"],sncr.strings["title-preclude-problem"]); 2430 | } 2431 | }, 2432 | settings: function(context,command) { 2433 | // Setting variables 2434 | var defaultSettings = {}; 2435 | defaultSettings.artboardTitleType = 0; 2436 | defaultSettings.artboardTitleOffset = 0; 2437 | defaultSettings.artboardTitleAuto = 0; 2438 | 2439 | // Update default settings with cached settings 2440 | defaultSettings = getCachedSettings(context,sncr.data,defaultSettings,sncr.pluginDomain); 2441 | 2442 | // If a command is not passed, operate in config mode... 2443 | if (!command) { 2444 | var alertWindow = COSAlertWindow.new(); 2445 | alertWindow.setMessageText('Create Artboard Titles'); 2446 | 2447 | var titleType = createRadioButtons(["Above artboards","Below artboards"],defaultSettings.artboardTitleType); 2448 | alertWindow.addAccessoryView(titleType); 2449 | 2450 | alertWindow.addTextLabelWithValue('Vertical offset:'); 2451 | 2452 | var titleOffset = createField(defaultSettings.artboardTitleOffset,NSMakeRect(0,0,60,24)); 2453 | alertWindow.addAccessoryView(titleOffset); 2454 | 2455 | // Buttons 2456 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]); 2457 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]); 2458 | 2459 | // Set key order and first responder 2460 | setKeyOrder(alertWindow,[ 2461 | titleType, 2462 | titleOffset, 2463 | buttonOK 2464 | ]); 2465 | 2466 | var responseCode = alertWindow.runModal(); 2467 | 2468 | if (responseCode == 1000) { 2469 | try { 2470 | // Purge old settings in old location 2471 | sncr.command.setValue_forKey_onLayer(nil,"titleType",sncr.page); 2472 | sncr.command.setValue_forKey_onLayer(nil,"titleOffset",sncr.page); 2473 | 2474 | // Save new settings in new location 2475 | sncr.command.setValue_forKey_onLayer(titleType.selectedCell().tag(),"artboardTitleType",sncr.data); 2476 | sncr.command.setValue_forKey_onLayer(Number(titleOffset.stringValue()),"artboardTitleOffset",sncr.data); 2477 | } 2478 | catch(err) { 2479 | log(sncr.strings["general-save-failed"]); 2480 | } 2481 | 2482 | sncr.titles.create(context,"settings"); 2483 | } else return false; 2484 | } 2485 | // Otherwise operate in run mode... 2486 | else { 2487 | // Return updated settings 2488 | return { 2489 | titleType : defaultSettings.artboardTitleType, 2490 | titleOffset : defaultSettings.artboardTitleOffset 2491 | } 2492 | } 2493 | } 2494 | } 2495 | 2496 | sncr.wireframes = { 2497 | addNew: function(context) { 2498 | var predicate = NSPredicate.predicateWithFormat("userInfo == nil || function(userInfo,'valueForKeyPath:',%@)." + sncr.layout.config.featureKey + " != " + false,sncr.pluginDomain), 2499 | artboards = sncr.page.artboards().filteredArrayUsingPredicate(predicate), 2500 | selectionSize = getSelectionSize(artboards); 2501 | 2502 | var margin = 400, 2503 | sliceX = 400, 2504 | sliceY = 1000, 2505 | sliceWidth = selectionSize.width + sliceX + margin, 2506 | sliceHeight = selectionSize.height + sliceY + margin, 2507 | minWidth = 6680, 2508 | minHeight = 4520; 2509 | 2510 | sliceWidth = (sliceWidth < minWidth) ? minWidth : sliceWidth; 2511 | sliceHeight = (sliceHeight < minHeight) ? minHeight : sliceHeight; 2512 | 2513 | var sliceLayer = [MSSliceLayer new]; 2514 | sliceLayer.setName(sncr.page.name().replace(sncr.layout.config.pageNamePrefix,"")); 2515 | sliceLayer.setBackgroundColor(MSColor.colorWithRed_green_blue_alpha(239/255,239/255,239/255,1.0)); 2516 | sliceLayer.setIsLocked(1); 2517 | sliceLayer.hasBackgroundColor = true; 2518 | sliceLayer.frame().setX(-sliceX); 2519 | sliceLayer.frame().setY(-sliceY); 2520 | sliceLayer.frame().setWidth(sliceWidth); 2521 | sliceLayer.frame().setHeight(sliceHeight); 2522 | 2523 | sncr.page.addLayers([sliceLayer]); 2524 | 2525 | sliceLayer.select_byExtendingSelection(1,0); 2526 | actionWithType(context,"MSMoveToBackAction").doPerformAction(nil); 2527 | 2528 | var format = sliceLayer.exportOptions().addExportFormat(); 2529 | format.setScale(".5"); 2530 | format.setFileFormat("pdf"); 2531 | 2532 | sncr.wireframes.include(context); 2533 | 2534 | sketch.UI.message(sncr.strings["wireframe-add-complete"]); 2535 | 2536 | function getSelectionSize(selection) { 2537 | var minX,minY,maxX,maxY; 2538 | minX=minY=Number.MAX_VALUE; 2539 | maxX=maxY=-0xFFFFFFFF; 2540 | 2541 | for (var i = 0; i < selection.count(); i++) { 2542 | var frame = selection.objectAtIndex(i).frame(); 2543 | 2544 | minX = Math.min(minX,frame.minX()); 2545 | minY = Math.min(minY,frame.minY()); 2546 | maxX = Math.max(maxX,frame.maxX()); 2547 | maxY = Math.max(maxY,frame.maxY()); 2548 | } 2549 | 2550 | return { 2551 | width: maxX-minX, 2552 | height: maxY-minY, 2553 | minX: minX, 2554 | minY: minY 2555 | }; 2556 | } 2557 | }, 2558 | include: function(context) { 2559 | var selection = sncr.page.selectedLayers().layers(); 2560 | 2561 | if (selection.count() == 1 && selection[0] instanceof MSSliceLayer) { 2562 | sncr.command.setValue_forKey_onLayer(true,sncr.wireframes.config.featureKey,selection[0]); 2563 | 2564 | sketch.UI.message(selection[0].name() + sncr.strings["wireframe-include-complete"]); 2565 | } else { 2566 | sketch.UI.alert(sncr.strings["wireframe-include-plugin"],sncr.strings["wireframe-include-problem"]); 2567 | } 2568 | }, 2569 | preclude: function(context) { 2570 | if (sncr.selection.count() == 1 && sncr.selection[0] instanceof MSSliceLayer) { 2571 | sncr.command.setValue_forKey_onLayer(false,sncr.wireframes.config.featureKey,sncr.selection[0]); 2572 | 2573 | sketch.UI.message(sncr.selection[0].name() + sncr.strings["wireframe-preclude-complete"]); 2574 | } else { 2575 | sketch.UI.alert(sncr.strings["wireframe-preclude-plugin"],sncr.strings["wireframe-preclude-problem"]); 2576 | } 2577 | }, 2578 | export: function(context) { 2579 | var loop = sncr.pages.objectEnumerator(), 2580 | page, 2581 | wireframes = [], 2582 | count = 1; 2583 | 2584 | while (page = loop.nextObject()) { 2585 | var predicate = NSPredicate.predicateWithFormat("className == 'MSSliceLayer' && userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.wireframes.config.featureKey + " == " + true,sncr.pluginDomain), 2586 | slices = page.children().filteredArrayUsingPredicate(predicate), 2587 | loop2 = slices.objectEnumerator(), 2588 | slice; 2589 | 2590 | while (slice = loop2.nextObject()) { 2591 | var filePrefix = "W", 2592 | filename = filePrefix + count + " " + slice.name(), 2593 | filepath = [@"~/Downloads" stringByExpandingTildeInPath] + "/" + filename + ".pdf"; 2594 | 2595 | wireframes.push({ 2596 | source : slice, 2597 | name : filename, 2598 | path : filepath 2599 | }); 2600 | 2601 | count++; 2602 | } 2603 | } 2604 | 2605 | if (wireframes.length) { 2606 | var exportList = sncr.wireframes.confirm(wireframes); 2607 | 2608 | if (exportList.length) { 2609 | var doc = sncr.document; 2610 | 2611 | for (var i = 0; i < exportList.length; i++) { 2612 | [doc saveArtboardOrSlice:exportList[i]['source'] toFile:exportList[i]['path']]; 2613 | } 2614 | 2615 | sketch.UI.message(exportList.length + sncr.strings["wireframe-export-complete"]); 2616 | } 2617 | } else { 2618 | sketch.UI.alert(sncr.strings["wireframe-export-plugin"],sncr.strings["wireframe-export-problem"]); 2619 | } 2620 | }, 2621 | confirm: function(wireframes) { 2622 | var alertWindow = COSAlertWindow.new(); 2623 | 2624 | alertWindow.setMessageText(sncr.strings["wireframe-export-plugin"]); 2625 | 2626 | alertWindow.addTextLabelWithValue(sncr.strings["wireframe-export-intro"]); 2627 | 2628 | var wireframeListHeader = NSView.alloc().initWithFrame(NSMakeRect(0,0,300,18)), 2629 | wireframeListCheckbox = createCheckbox({name:"",value:1},1,NSMakeRect(0,0,18,18)), 2630 | wireframeListLabel = createBoldLabel("Wireframes (" + wireframes.length + ")",12,NSMakeRect(22,0,300-22,18)); 2631 | 2632 | wireframeListCheckbox.setAction("callAction:"); 2633 | wireframeListCheckbox.setCOSJSTargetFunction(function(sender) { 2634 | for (var i = 0; i < wireframeListCheckboxes.length; i++) { 2635 | wireframeListCheckboxes[i].state = sender.state(); 2636 | } 2637 | }); 2638 | 2639 | wireframeListHeader.addSubview(wireframeListCheckbox); 2640 | wireframeListHeader.addSubview(wireframeListLabel); 2641 | 2642 | alertWindow.addAccessoryView(wireframeListHeader); 2643 | 2644 | var wireframeListItem = 24, 2645 | wireframeListContent = NSView.alloc().initWithFrame(NSMakeRect(0,0,300,wireframes.length*wireframeListItem)), 2646 | wireframeListCheckboxes = [], 2647 | count = 0; 2648 | 2649 | wireframeListContent.setFlipped(1); 2650 | wireframeListContent.setWantsLayer(1); 2651 | wireframeListContent.layer().setBackgroundColor(CGColorCreateGenericRGB(255,255,255,1)); 2652 | 2653 | for (var i = 0; i < wireframes.length; i++) { 2654 | var wireframeCheckbox = createCheckbox({name:wireframes[i]['name'],value:i},1,NSMakeRect(4,count*wireframeListItem,300,wireframeListItem)); 2655 | 2656 | wireframeListCheckboxes.push(wireframeCheckbox); 2657 | wireframeListContent.addSubview(wireframeCheckbox); 2658 | 2659 | count++; 2660 | } 2661 | 2662 | alertWindow.addAccessoryView(wireframeListContent); 2663 | 2664 | alertWindow.addTextLabelWithValue(sncr.strings["wireframe-export-outro"]); 2665 | 2666 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]); 2667 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]); 2668 | 2669 | var responseCode = alertWindow.runModal(); 2670 | 2671 | if (responseCode == 1000) { 2672 | var slicesToRemove = []; 2673 | 2674 | for (var i = 0; i < wireframeListCheckboxes.length; i++) { 2675 | if (wireframeListCheckboxes[i].state() == 0) { 2676 | slicesToRemove.push(wireframeListCheckboxes[i].tag()); 2677 | } 2678 | } 2679 | 2680 | slicesToRemove.sort(function(a,b){ return b-a; }); 2681 | 2682 | for (var i = 0; i < slicesToRemove.length; i++) { 2683 | wireframes.splice(slicesToRemove[i],1); 2684 | } 2685 | 2686 | return wireframes; 2687 | } else return false; 2688 | } 2689 | } 2690 | 2691 | function createBoldLabel(text,size,frame) { 2692 | var label = NSTextField.alloc().initWithFrame(frame); 2693 | 2694 | label.setStringValue(text); 2695 | label.setFont(NSFont.boldSystemFontOfSize(size)); 2696 | label.setBezeled(0); 2697 | label.setDrawsBackground(0); 2698 | label.setEditable(0); 2699 | label.setSelectable(0); 2700 | 2701 | return label; 2702 | } 2703 | 2704 | var createCheckbox = function(item,flag,frame) { 2705 | flag = ( flag == false ) ? NSOffState : NSOnState; 2706 | var checkbox = [[NSButton alloc] initWithFrame:frame]; 2707 | [checkbox setButtonType: NSSwitchButton]; 2708 | [checkbox setBezelStyle: 0]; 2709 | [checkbox setTitle: item.name]; 2710 | [checkbox setTag: item.value]; 2711 | [checkbox setState: flag]; 2712 | 2713 | return checkbox; 2714 | } 2715 | 2716 | function createSelect(items,selectedItemIndex,frame) { 2717 | var comboBox = NSComboBox.alloc().initWithFrame(frame), 2718 | selectedItemIndex = (selectedItemIndex > -1) ? selectedItemIndex : 0; 2719 | 2720 | comboBox.addItemsWithObjectValues(items); 2721 | comboBox.selectItemAtIndex(selectedItemIndex); 2722 | comboBox.setNumberOfVisibleItems(16); 2723 | 2724 | return comboBox; 2725 | } 2726 | 2727 | var createField = function(value,size) { 2728 | var size = (size) ? size : NSMakeRect(0,0,100,20); 2729 | var field = [[NSTextField alloc] initWithFrame:size]; 2730 | [field setStringValue:value]; 2731 | 2732 | return field; 2733 | } 2734 | 2735 | var createLabel = function(text,frame) { 2736 | var label = [[NSTextField alloc] initWithFrame:frame]; 2737 | [label setStringValue:text]; 2738 | [label setFont:[NSFont boldSystemFontOfSize:12]]; 2739 | [label setBezeled:false]; 2740 | [label setDrawsBackground:false]; 2741 | [label setEditable:false]; 2742 | [label setSelectable:false]; 2743 | 2744 | return label; 2745 | } 2746 | 2747 | function actionWithType(context,type) { 2748 | var controller = context.document.actionsController(); 2749 | 2750 | if (controller.actionWithName) { 2751 | return controller.actionWithName(type); 2752 | } else if (controller.actionWithID) { 2753 | return controller.actionWithID(type); 2754 | } else { 2755 | return controller.actionForID(type); 2756 | } 2757 | } 2758 | 2759 | function createRadioButtons(options,selected) { 2760 | // Set number of rows and columns 2761 | var rows = options.length; 2762 | var columns = 1; 2763 | 2764 | // Make a prototype cell 2765 | var buttonCell = [[NSButtonCell alloc] init]; 2766 | [buttonCell setButtonType:NSRadioButton] 2767 | 2768 | // Make a matrix to contain the radio cells 2769 | var buttonMatrix = [[NSMatrix alloc] initWithFrame: NSMakeRect(20,20,300,rows*25) mode:NSRadioModeMatrix prototype:buttonCell numberOfRows:rows numberOfColumns:columns]; 2770 | [buttonMatrix setCellSize: NSMakeSize(300,20)]; 2771 | 2772 | // Create a cell for each option 2773 | for (i = 0; i < options.length; i++) { 2774 | [[[buttonMatrix cells] objectAtIndex: i] setTitle: options[i]]; 2775 | [[[buttonMatrix cells] objectAtIndex: i] setTag: i]; 2776 | } 2777 | 2778 | // Select the default cell 2779 | [buttonMatrix selectCellAtRow:selected column:0] 2780 | 2781 | // Return the matrix 2782 | return buttonMatrix; 2783 | } 2784 | 2785 | function findLayerByID(scope,layerID,type) { 2786 | var scope = scope.layers(); 2787 | 2788 | if (scope) { 2789 | for (var i = 0; i < scope.count(); i++) { 2790 | var layer = scope.objectAtIndex(i); 2791 | 2792 | if ((!type && layer.objectID() == layerID) || (type && layer.objectID() == layerID && layer instanceof type)) { 2793 | return layer; 2794 | } 2795 | } 2796 | } 2797 | 2798 | return false; 2799 | } 2800 | 2801 | function findLayerByName(scope,layerName,type) { 2802 | var scope = scope.layers(); 2803 | 2804 | if (scope) { 2805 | for (var i = 0; i < scope.count(); i++) { 2806 | var name = scope.objectAtIndex(i).name().trim(); 2807 | 2808 | if ((!type && name == layerName) || (type && name == layerName && scope.objectAtIndex(i) instanceof type)) { 2809 | return scope.objectAtIndex(i); 2810 | } 2811 | } 2812 | } 2813 | 2814 | return false; 2815 | } 2816 | 2817 | function getLayerIndex(layer) { 2818 | var layers = layer.parentGroup().layers(); 2819 | 2820 | for (var i = 0; i < layers.count(); i++) { 2821 | if (layers.objectAtIndex(i) == layer) return i; 2822 | } 2823 | 2824 | return false; 2825 | } 2826 | 2827 | function getObjectByName(haystack,needle) { 2828 | for (var i = 0; i < haystack.count(); i++) { 2829 | var objectName = haystack.objectAtIndex(i).name(); 2830 | 2831 | if (objectName && objectName.isEqualToString(needle)) { 2832 | return haystack.objectAtIndex(i); 2833 | } 2834 | } 2835 | 2836 | return false; 2837 | } 2838 | 2839 | function getTextStyle(styleName,styleData) { 2840 | var textStyles = MSDocument.currentDocument().documentData().layerTextStyles(), 2841 | textStyle = getObjectByName(textStyles.objects(),styleName); 2842 | 2843 | if (!textStyle) { 2844 | var textLayer = MSTextLayer.alloc().initWithFrame(nil); 2845 | textLayer.setFontSize(styleData.fontSize); 2846 | textLayer.setLineHeight(styleData.lineHeight); 2847 | textLayer.setTextAlignment(styleData.textAlignment); 2848 | textLayer.setFontPostscriptName(styleData.fontFace); 2849 | 2850 | if (textStyles.addSharedStyleWithName_firstInstance) { 2851 | textStyle = textStyles.addSharedStyleWithName_firstInstance(styleName,textLayer.style()); 2852 | } else if (textStyles.initWithName_firstInstance) { 2853 | textStyle = MSSharedStyle.alloc().initWithName_firstInstance(styleName,textLayer.style()); 2854 | 2855 | textStyles.addSharedObject(textStyle); 2856 | } else { 2857 | textStyle = MSSharedStyle.alloc().initWithName_style(styleName,textLayer.style()); 2858 | 2859 | textStyles.addSharedObject(textStyle); 2860 | } 2861 | } 2862 | 2863 | return textStyle; 2864 | } 2865 | 2866 | function deleteTextStyle(styleName) { 2867 | var textStyles = MSDocument.currentDocument().documentData().layerTextStyles(), 2868 | textStyle = getObjectByName(textStyles.objects(),styleName); 2869 | 2870 | if (textStyle) textStyles.removeSharedStyle(textStyle); 2871 | } 2872 | 2873 | function getParentGroup(scope,name) { 2874 | var group = findLayerByName(scope,name); 2875 | 2876 | if (!group) { 2877 | var group = MSLayerGroup.new(); 2878 | group.setName(name); 2879 | group.frame().setX(0); 2880 | group.frame().setY(0); 2881 | group.setIsLocked(1); 2882 | 2883 | scope.addLayers([group]); 2884 | } 2885 | 2886 | group.setHasClickThrough(true); 2887 | 2888 | return group; 2889 | } 2890 | 2891 | function getChildGroup(scope,name) { 2892 | var group = findLayerByName(scope,name); 2893 | 2894 | if (!group) { 2895 | var group = MSLayerGroup.new(); 2896 | group.setName(name); 2897 | group.frame().setX(0 - scope.frame().x()); 2898 | group.frame().setY(0 - scope.frame().y()); 2899 | 2900 | scope.addLayers([group]); 2901 | } 2902 | 2903 | group.setHasClickThrough(true); 2904 | 2905 | return group; 2906 | } 2907 | 2908 | function setKeyOrder(alert,order) { 2909 | for (var i = 0; i < order.length; i++) { 2910 | var thisItem = order[i]; 2911 | var nextItem = order[i+1]; 2912 | 2913 | if (nextItem) thisItem.setNextKeyView(nextItem); 2914 | } 2915 | 2916 | alert.alert().window().setInitialFirstResponder(order[0]); 2917 | } 2918 | 2919 | function getCachedSettings(context,location,settings,domain) { 2920 | try { 2921 | for (i in settings) { 2922 | var value = sncr.command.valueForKey_onLayer_forPluginIdentifier(i,location,domain); 2923 | if (value) settings[i] = value; 2924 | } 2925 | 2926 | return settings; 2927 | } catch(err) { 2928 | log("Unable to fetch settings"); 2929 | } 2930 | } 2931 | 2932 | function logFunctionStart(output,command) { 2933 | if (!command) command = "user"; 2934 | 2935 | log(output + " - Initiated by " + command); 2936 | } 2937 | 2938 | function createNewRectForFlowLayer(linkedObject,flowObjectID) { 2939 | var objectRect = linkedObject.absoluteRect().rect(); 2940 | 2941 | var master = linkedObject.symbolMaster(); 2942 | var masterRect = master.absoluteRect().rect(); 2943 | 2944 | var flowRect = master.layerWithID(flowObjectID).absoluteRect().rect(); 2945 | 2946 | 2947 | 2948 | 2949 | 2950 | 2951 | // log(flowRect) 2952 | // 2953 | // var overrides = linkedObject.overrideContainer().flattenedChildren(); 2954 | // 2955 | // overrides.forEach(o => { 2956 | // let value = String(o.availableOverride().internalOverrideValue()); 2957 | // 2958 | // if (value.includes('flowDestination')) { 2959 | // let path = (value.includes('/')) ? value.substr(0,value.indexOf('/')) : value; 2960 | // 2961 | // if (path == flowObjectID) { 2962 | // var foo = CGPathGetPathBoundingBox(o.pathInInstance()); 2963 | // 2964 | // var parentRect = linkedObject.absoluteRect().rect(); 2965 | // 2966 | // let w = parentRect.origin.x + foo.origin.x; 2967 | // let x = parentRect.origin.y + foo.origin.y; 2968 | // let y = foo.size.width; 2969 | // let z = foo.size.height; 2970 | // 2971 | // return NSMakeRect(w,x,y,z); 2972 | // } 2973 | // } 2974 | // }); 2975 | 2976 | 2977 | 2978 | 2979 | 2980 | 2981 | var offsetX = flowRect.origin.x - masterRect.origin.x; 2982 | var offsetY = flowRect.origin.y - masterRect.origin.y; 2983 | var offsetWidth = objectRect.size.width - masterRect.size.width; 2984 | var offsetHeight = objectRect.size.height - masterRect.size.height; 2985 | 2986 | var newX = objectRect.origin.x + offsetX + offsetWidth; 2987 | var newY = objectRect.origin.y + offsetY + offsetHeight; 2988 | 2989 | return NSMakeRect(newX,newY,flowRect.size.width,flowRect.size.height); 2990 | } 2991 | --------------------------------------------------------------------------------