<commit_before>angular.module('starter.controllers', [])

    .controller('SecureStorageCtrl', function ($scope) {
        var self = this;

        self.loading = true;

        self.storeFile = function () {
            self.imgSrc = null;
            self.loading = true;
            appworks.storage.storeFile('file.jpg', 'http://thecatapi.com/api/images/get', function () {
                self.loading = false;
                $scope.$apply();
            });
        };

        self.getFile = function () {
            appworks.storage.getFile('file.jpg', function (dataUrl) {
                self.imgSrc = dataUrl;
                $scope.$apply();
            });
        }
    })

    .controller('CacheCtrl', function () {

        var self = this;

        self.key = 'myKey';
        self.data = 1234;

        self.setItem = function (key, data) {
            self.data = null;
            self.cacheData = null;
            appworks.cache.setItem(key, data);
        };

        self.getItem = function (key) {
            appworks.cache.getItem(key, function (data) {
                if (!data) {
                    alert('This key does not exist in the cache');
                }
                self.cacheData = data;
            });
        };

        self.clear = function () {
            self.cacheData = null;
            self.key = null;
            self.data = null;
            appworks.cache.clear();
            self.$applyAsync();
        };
    })

    .controller('OfflineCtrl', function ($scope, $timeout) {
        var self = this,
            url = 'https://randomuser.me/api/',
            options1 = {eventListener: 'myEvent1', method: 'GET'},
            options2 = {eventListener: 'myEvent2', method: 'GET'},
            options3 = {eventListener: 'myEvent3', method: 'GET'};

        self.users = [];

        self.queue = function (options) {
            self.err = null;
            if (options) {
                appworks.offline.queue(options.url, options.options).then(options.success, options.error);
            } else {
                appworks.offline.queue(url, options1).then(successFn, errorFn);
            }
        };

        self.queueMultiple = function () {
            self.users = [];
            $timeout(function () {
                self.queue({url: url, options: options1, success: successFn, error: errorFn});
            }, 1000);
            $timeout(function () {
                self.queue({url: url, options: options2, success: successFn2, error: errorFn});
            }, 1000);
            $timeout(function () {
                self.queue({url: url, options: options3, success: successFn3, error: errorFn});
            }, 1000);
        };

        function addUser(user) {
            $scope.$apply(self.users.push(user));
        }

        function successFn(res) {
            res = JSON.parse(res.data);
            addUser(res.results[0]);
            appworks.offline.removeEventHandler('myEvent1', successFn);
        }

        function successFn2(res) {
            res = JSON.parse(res.data);
            addUser(res.results[0]);
            appworks.offline.removeEventHandler('myEvent2', successFn2);
        }

        function successFn3(res) {
            res = JSON.parse(res.data);
            addUser(res.results[0]);
            appworks.offline.removeEventHandler('myEvent3', successFn3);
        }

        function errorFn(err) {
            $scope.$apply(self.err = err);
        }
    })

    .controller('DeviceCtrl', function ($scope) {
        var self = this;

        self.notifications = [];
        self.notificationsEnabled = false;

        $scope.$watch('device.notificationsEnabled', function (on) {
            if (on) {
                self.enableNotifications();
            } else {
                self.stopNotifications();
            }
        });

        self.openCamera = function () {
            self.err = null;
            self.imgSrc = null;
            appworks.camera.takePicture(function (dataUrl) {
                $scope.$apply(self.imgSrc = dataUrl);
            }, function (err) {
                $scope.$apply(self.err = err);
            });
        };

        self.openGallery = function () {
            self.err = null;
            self.imgSrc = null;
            appworks.camera.chooseFromLibrary(function (dataUrl) {
                $scope.$apply(self.imgSrc = dataUrl);
            }, function (err) {
                $scope.$apply(self.err = err);
            });
        };

        self.clearNotifications = function () {
            self.notifications = [];
        };

        self.stopNotifications = function () {
            appworks.notifications.off();
        };

        self.enableNotifications = function () {
            appworks.notifications.on();
        };

        self.getNotifications = function () {
            self.syncNotifications();
        };

        self.syncNotifications = function () {
            $scope.$applyAsync(self.notifications = appworks.notifications.get());
        };

    });
<commit_msg>notifications enabled by default<commit_after>angular.module('starter.controllers', [])

    .controller('SecureStorageCtrl', function ($scope) {
        var self = this;

        self.loading = true;

        self.storeFile = function () {
            self.imgSrc = null;
            self.loading = true;
            appworks.storage.storeFile('file.jpg', 'http://thecatapi.com/api/images/get', function () {
                self.loading = false;
                $scope.$apply();
            });
        };

        self.getFile = function () {
            appworks.storage.getFile('file.jpg', function (dataUrl) {
                self.imgSrc = dataUrl;
                $scope.$apply();
            });
        }
    })

    .controller('CacheCtrl', function () {

        var self = this;

        self.key = 'myKey';
        self.data = 1234;

        self.setItem = function (key, data) {
            self.data = null;
            self.cacheData = null;
            appworks.cache.setItem(key, data);
        };

        self.getItem = function (key) {
            appworks.cache.getItem(key, function (data) {
                if (!data) {
                    alert('This key does not exist in the cache');
                }
                self.cacheData = data;
            });
        };

        self.clear = function () {
            self.cacheData = null;
            self.key = null;
            self.data = null;
            appworks.cache.clear();
            self.$applyAsync();
        };
    })

    .controller('OfflineCtrl', function ($scope, $timeout) {
        var self = this,
            url = 'https://randomuser.me/api/',
            options1 = {eventListener: 'myEvent1', method: 'GET'},
            options2 = {eventListener: 'myEvent2', method: 'GET'},
            options3 = {eventListener: 'myEvent3', method: 'GET'};

        self.users = [];

        self.queue = function (options) {
            self.err = null;
            if (options) {
                appworks.offline.queue(options.url, options.options).then(options.success, options.error);
            } else {
                appworks.offline.queue(url, options1).then(successFn, errorFn);
            }
        };

        self.queueMultiple = function () {
            self.users = [];
            $timeout(function () {
                self.queue({url: url, options: options1, success: successFn, error: errorFn});
            }, 1000);
            $timeout(function () {
                self.queue({url: url, options: options2, success: successFn2, error: errorFn});
            }, 1000);
            $timeout(function () {
                self.queue({url: url, options: options3, success: successFn3, error: errorFn});
            }, 1000);
        };

        function addUser(user) {
            $scope.$apply(self.users.push(user));
        }

        function successFn(res) {
            res = JSON.parse(res.data);
            addUser(res.results[0]);
            appworks.offline.removeEventHandler('myEvent1', successFn);
        }

        function successFn2(res) {
            res = JSON.parse(res.data);
            addUser(res.results[0]);
            appworks.offline.removeEventHandler('myEvent2', successFn2);
        }

        function successFn3(res) {
            res = JSON.parse(res.data);
            addUser(res.results[0]);
            appworks.offline.removeEventHandler('myEvent3', successFn3);
        }

        function errorFn(err) {
            $scope.$apply(self.err = err);
        }
    })

    .controller('DeviceCtrl', function ($scope) {
        var self = this;

        self.notifications = [];
        self.notificationsEnabled = true;

        $scope.$watch('device.notificationsEnabled', function (on) {
            if (on) {
                self.enableNotifications();
            } else {
                self.stopNotifications();
            }
        });

        self.openCamera = function () {
            self.err = null;
            self.imgSrc = null;
            appworks.camera.takePicture(function (dataUrl) {
                $scope.$apply(self.imgSrc = dataUrl);
            }, function (err) {
                $scope.$apply(self.err = err);
            });
        };

        self.openGallery = function () {
            self.err = null;
            self.imgSrc = null;
            appworks.camera.chooseFromLibrary(function (dataUrl) {
                $scope.$apply(self.imgSrc = dataUrl);
            }, function (err) {
                $scope.$apply(self.err = err);
            });
        };

        self.clearNotifications = function () {
            self.notifications = [];
        };

        self.stopNotifications = function () {
            appworks.notifications.off();
        };

        self.enableNotifications = function () {
            appworks.notifications.on();
        };

        self.getNotifications = function () {
            self.syncNotifications();
        };

        self.syncNotifications = function () {
            $scope.$applyAsync(self.notifications = appworks.notifications.get());
        };

    });
<|endoftext|>


<commit_before>angular.module('sheHacksApp.controllers', [])

    .controller('MenuController', function ($scope, Platform, MenuService) {
        $scope.list = MenuService.all();

        $scope.$on('$viewContentLoaded', function () {
            $scope.noNetwork = !hasNetworkConnectivity();
        });

        $scope.openLeft = function () {
            $scope.sideMenuController.toggleLeft();
        };

        $scope.closeMenu = function () {
            $scope.sideMenuController.close();
        }

        Platform.ready(function () {
            // hide the status bar using the StatusBar plugin
            StatusBar.hide();
        });

    })

    .controller('VenueController', function ($scope) {
        $scope.title = "Venue & Map";
        $scope.noNetwork = false;

        $scope.$on('$viewContentLoaded', function () {
            // We need to check for connection status before attempting to connect to the google maps API otherwise bad things happen
            if (hasNetworkConnectivity()) {
                init();
            } else {
                $scope.noNetwork = true;
                console.log("No network detected, cannot initialize map")
            }
        });

        function init() {
            console.log("initializing google maps")

            var googlePos = new google.maps.LatLng(-33.8665433, 151.1956316);

            //TODO: fix map centering to allow for infoWindow
            var mapOptions = {
                center: googlePos,
                zoom: 16,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            };

            var map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);

            var marker = new google.maps.Marker({
                position: googlePos,
                map: map,
                title: 'Google Sydney'
            });

            var infowindow = new google.maps.InfoWindow({
                content: "
Level 5, Google Sydney
"
            });

            // Stop the side bar from dragging when mousedown/tapdown on the map
            google.maps.event.addDomListener(document.getElementById('map-canvas'), 'mousedown', function (e) {
                e.preventDefault();
                return false;
            });

            $scope.map = map;
            infowindow.open(map, marker);
        };

        $scope.centerOnMe = function () {
            if (!$scope.map) {
                console.log("cannot find map")
                return;
            }

            navigator.geolocation.getCurrentPosition(function (pos) {
                $scope.map.setCenter(new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude));
            });
        };

    })

    .controller('RegistrationController', function ($scope, SponsorsService) {
        $scope.title = "Registration";
    })

    .controller('SponsorsController', function ($scope, SponsorsService) {
        $scope.title = "Sponsors";

        SponsorsService.getSponsors().then(function (data) {
            $scope.sponsors = data;
        });

        // opens links in browser instead of on top of app
        $scope.openLink = function (link) {
            console.log(link);
            window.open('http://' + link, '_system');
        }
    })

    .controller('ProgramController', function ($scope, ProgramService) {
        $scope.title = "Program";

        ProgramService.getProgram().then(function (data) {
            $scope.schedule = data;
        });
    })

    .controller('PrizesController', function ($scope, PrizesService) {
        $scope.title = "Prize Categories";
        PrizesService.getPrizes().then(function (data) {
            $scope.prizes = data;
        });
    })

    .controller('AboutController', function ($scope, CreditsService) {
        $scope.title = "About";

        CreditsService.getCredits().then(function (data) {
            $scope.people = data;
        })
    });

// TODO: FIX THIS :'(
// Unfortunately navigator.onLine is not implemented properly in many browsers. So basically it lies to you...
// I have tested it on iOS and it still returns TRUE for airplane mode.
// The PhoneGap APIs navigator.connection.type is also faulty for airplane mode (returns "cellular")
// So it seems there is no great way of detecting no network access and this solution doesn't work for me on airplane mode still...
function hasNetworkConnectivity() {
    console.log("navigator.onLine " + navigator.onLine);
    return navigator.onLine;
}
<commit_msg>removed info window from maps<commit_after>angular.module('sheHacksApp.controllers', [])

    .controller('MenuController', function ($scope, Platform, MenuService) {
        $scope.list = MenuService.all();

        $scope.$on('$viewContentLoaded', function () {
            $scope.noNetwork = !hasNetworkConnectivity();
        });

        $scope.openLeft = function () {
            $scope.sideMenuController.toggleLeft();
        };

        $scope.closeMenu = function () {
            $scope.sideMenuController.close();
        }

        Platform.ready(function () {
            // hide the status bar using the StatusBar plugin
            StatusBar.hide();
        });

    })

    .controller('VenueController', function ($scope) {
        $scope.title = "Venue & Map";
        $scope.noNetwork = false;

        $scope.$on('$viewContentLoaded', function () {
            // We need to check for connection status before attempting to connect to the google maps API otherwise bad things happen
            if (hasNetworkConnectivity()) {
                init();
            } else {
                $scope.noNetwork = true;
                console.log("No network detected, cannot initialize map")
            }
        });

        function init() {
            console.log("initializing google maps")

            var googlePos = new google.maps.LatLng(-33.8665433, 151.1956316);

            //TODO: fix map centering to allow for infoWindow
            var mapOptions = {
                center: googlePos,
                zoom: 16,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            };

            var map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);

            var marker = new google.maps.Marker({
                position: googlePos,
                map: map,
                title: 'Google Sydney'
            });

            var infowindow = new google.maps.InfoWindow({
                content: "
Google Sydney
"
            });

            // Stop the side bar from dragging when mousedown/tapdown on the map
            google.maps.event.addDomListener(document.getElementById('map-canvas'), 'mousedown', function (e) {
                e.preventDefault();
                return false;
            });

            $scope.map = map;
//            infowindow.open(map, marker);
        };

        $scope.centerOnMe = function () {
            if (!$scope.map) {
                console.log("cannot find map")
                return;
            }

            navigator.geolocation.getCurrentPosition(function (pos) {
                $scope.map.setCenter(new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude));
            });
        };

    })

    .controller('RegistrationController', function ($scope, SponsorsService) {
        $scope.title = "Registration";
    })

    .controller('SponsorsController', function ($scope, SponsorsService) {
        $scope.title = "Sponsors";

        SponsorsService.getSponsors().then(function (data) {
            $scope.sponsors = data;
        });

        // opens links in browser instead of on top of app
        $scope.openLink = function (link) {
            console.log(link);
            window.open('http://' + link, '_system');
        }
    })

    .controller('ProgramController', function ($scope, ProgramService) {
        $scope.title = "Program";

        ProgramService.getProgram().then(function (data) {
            $scope.schedule = data;
        });
    })

    .controller('PrizesController', function ($scope, PrizesService) {
        $scope.title = "Prize Categories";
        PrizesService.getPrizes().then(function (data) {
            $scope.prizes = data;
        });
    })

    .controller('AboutController', function ($scope, CreditsService) {
        $scope.title = "About";

        CreditsService.getCredits().then(function (data) {
            $scope.people = data;
        })
    });

// TODO: FIX THIS :'(
// Unfortunately navigator.onLine is not implemented properly in many browsers. So basically it lies to you...
// I have tested it on iOS and it still returns TRUE for airplane mode.
// The PhoneGap APIs navigator.connection.type is also faulty for airplane mode (returns "cellular")
// So it seems there is no great way of detecting no network access and this solution doesn't work for me on airplane mode still...
function hasNetworkConnectivity() {
    console.log("navigator.onLine " + navigator.onLine);
    return navigator.onLine;
}
<|endoftext|>


<commit_before>/* Copyright 2009-2011 Hewlett-Packard Development Company, L.P. All rights reserved. */

enyo.kind({
	name: "EditorPanel",
	kind: enyo.HFlexBox,
	components: [
		{name: "slidingPane", kind: "HSlidingPane", flex: 1, multiViewMinHeight: 0,
			onSlideComplete: "barMoved",
			components: [
			{name: "top", kind:"HSlidingView",
				components: [
					{kind: "HFlexBox", pack: "center", components: [
						{className: "editor-left", flex: 1, overflow: "hidden" },
						{kind: "BasicScroller",
							name:"editorScroller",
							height: "760px",
							width: "760px",
							autoHorizontal: false,
							horizontal: false,
							className: "editor-scroller",
							onmouseup: "scrollerClicked",
							components: [
								{kind: "RichText",
									name: "editor",
									richContent: true,
									className: "editor-input",
									onblur: "syncViews",
									onfocus: "editorFocussed",
									onmouseup: "syncViews",
									hint: "",
									spellcheck: true,
									autocorrect: false,
									autoCapitalize: false,
									styled: false
								}
							]
						},
						{className: "editor-right", flex: 1, overflow: "hidden" }
					]}
			]},
			{name: "bottom", kind:"HSlidingView", height: "100px", fixedHeight: true, pack:"end", style:"overflow: hidden;",
				applySlideToNode: function(inSlide) {
					if (inSlide != this.slidePosition && this.index) {
						inSlide = Math.min(0,inSlide);
						this.lastSlidePosition = this.slidePosition;
						this.slidePosition = inSlide;
						if (this.hasNode()) {
							//this.log(this.id, inSlide);
							var t = inSlide !== null ? "translate3d(0," + inSlide + "px,0)" : "";
							this.domStyles["-webkit-transform"] = this.node.style.webkitTransform = t;
							var s = inSlide !== null ? "translate3d(0,"+ (-enyo.fetchControlSize(this).h-inSlide+55) + "px,0)" : "";
							this.$.client.children[1].node.style.webkitTransform = s;
						}
					}
				},
				resizeHandler: function() {
					//var s = "translate3d(0,1px,0)";
					var s = "translate3d(0," + (55-enyo.fetchControlSize(this).h) + "px,0)";
					this.node.style.webkitTransform = s;
				},
				components: [
					{kind: "Header", name: "header", height: "55px",className: "enyo-toolbar fake-toolbar", style:"z-index: 1000;",
						onmousedown: "sliderclicked",
						onmouseup: "sliderreleased",
						components: [
						{kind: "GrabButton", className: "HGrabButton"},
						{kind: "Spacer", style:"width: 1px;"},
						{content: "", name:"filename"},
						//{kind: "Image", src:"images/title.png", style:"margin-top: 4px;"},
						{kind: "Spacer", flex: 25},
						//{content: "Typewriter"},
						{kind: "Spacer", style:"width: 1px;"},
						{kind: "GrabButton", className: "HGrabButton Right"}
					]},
					{kind: "VFlexBox", flex: 1,
						components: [
						{kind: "HFlexBox", flex: 1, pack: "center", components: [
							{className: "preview-left", flex: 1, overflow: "hidden" },
							{kind: "BasicScroller",
								name:"previewScroller",
								width: "760px",
								autoHorizontal: false,
								horizontal: false,
								className: "output-scroller",
								components: [
									{kind: "HtmlContent",
										name: "preview",
										className: "output-preview",
										onLinkClick: "htmlContentLinkClick",
										onmouseup: "syncViews"
									}
								]
							},
							{className: "preview-right", flex: 1, overflow: "hidden" }
						]}
					]}
			]}
		]},
		{name:'filePicker', kind: "FilePicker", fileType:["image"], allowMultiSelect:false, onPickFile: "handleFile"},
		{kind: "PrintDialog",
			duplexOption: true,
			frameToPrint: {name:"preview", landscape:false},
			appName: "Typewriter"
		}
	],

	/* PREVIEW HANDLING */
	synccount: 0,

	barBeingDragged: false,

	sliderclicked: function() {
		this.barBeingDragged = true;
	},

	sliderreleased: function() {
		this.barBeingDragged = false;
	},

	barMoved: function(event) {
		if(event.view == this.$.top) {
			this.position = "down";
			//enyo.keyboard.show();
		}
		else {
			this.position = "up";
			//enyo.keyboard.hide();
		}

		//this.generateMarkdown();
	},
	htmlContentLinkClick: function(sender, url) {
		var splits = url.split(/#/).slice(-1).pop();
		switch(splits) {
			case "clear":
				this.$.editor.setValue("");
			case "editor":
				this.$.slidingPane.selectView(this.$.top);
				return false;
				break;
			case "preview":
				this.$.slidingPane.selectView(this.$.bottom);
				return false;
				break;
			default:
				var r = new enyo.PalmService();
				r.service = "palm://com.palm.applicationManager/";
				r.method = "open";
				r.call({target: url});
		}
	},

	/* PRINT HANDLING */

	printDocument: function() {
		this.$.slidingPane.selectViewByIndex(1);
		this.$.printDialog.openAtCenter();
	},
	/* EDITOR CALLBACKS */
	editorFocussed: function() {
		enyo.keyboard.show();
	},

	scrollerClicked: function() {
		this.$.editor.forceFocus();
	},

	syncViews: function(inSender, inEvent) {

		var eb = this.$.editorScroller.getBoundaries().bottom;
		var et = this.$.editorScroller.getBoundaries().top;
		var ec = this.$.editorScroller.scrollTop;
		var ep = eb!=0 ? ec/eb : 0;
		ep = Math.max(0,ep);
		ep = Math.min(1,ep);

		if(this.position == "up")
			var pb = this.$.previewScroller.getBoundaries().bottom;
		else // weirdly enough the preview box appears to be bigger if the bar is down. we'll simply substract the height added here.
			var pb = this.$.previewScroller.getBoundaries().bottom - enyo.fetchControlSize(this).h+55;
		var pt = this.$.previewScroller.getBoundaries().top;
		var pc = this.$.previewScroller.scrollTop;
		var pp = pb!=0 ? pc/pb : 0;
		pp = Math.max(0,pp);
		pp = Math.min(1,pp);

		if(!this.barBeingDragged && (this.$.bottom.slidePosition == 0 || -enyo.fetchControlSize(this).h == this.$.bottom.slidePosition-55)) {
			if(this.position == "up") {
				//enyo.keyboard.hide();
				//this.$.editorScroller.setScrollTop(pp * eb); // TODO: das kann sau nervig sein
			}
			else {
				//enyo.keyboard.show();
				this.$.previewScroller.setScrollTop(ep * pb);
				//this.$.editor.forceFocus(); // buggy with on screen keyboard
			}
		}

		//console.log(pp + " " + ep);

		if(this.synccount == 0 || inSender.name != "schedule") {
			this.makePreview();
		}
		this.synccount++;
		this.synccount = this.synccount % 3;
	},

	makePreview: function() {
		//console.log("generating Markdown");
		var converter = new Showdown.converter();
		var value = this.$.editor.getText();
		this.$.preview.setContent(converter.makeHtml(value));
	},

	setSchedule: function() {
		this.scheduleID = setInterval(enyo.bind(this,function() {
			this.syncViews({name:"schedule"});
		}), 5000);
	},
	clearSchedule: function() {
		clearInterval(this.scheduleID);
	},

	/* CONSTRUCTOR */

	ready: function() {
		this.position = "down";
		enyo.keyboard.setManualMode(true);
		//enyo.keyboard.setResizesWindow(false);
		enyo.keyboard.show();

		this.syncViews({name:"startup"});
	},

	rendered: function() {
		this.inherited(arguments);
		this.adjustSlidingSize();
		this.setSchedule();
	},
	resizeHandler: function() {
		this.adjustSlidingSize();
		var v = this.$.slidingPane.view;
		if (v && v.resizeHandler) {
			v.resizeHandler();
		}
	},
	adjustSlidingSize: function() {
		var s = enyo.fetchControlSize(this);
		var pcs = enyo.fetchControlSize(this.$.bottom.$.client);
		var kh = enyo.keyboard.isShowing() ? enyo.keyboard.height : 0;

		this.$.editorScroller.node.style.height = (s.h - 55 - kh) + "px";

		this.$.top.node.style.height = (s.h - 55 - kh) + "px";
		this.$.bottom.node.style.height = (s.h - kh) + "px";
		this.$.bottom.setPeekHeight(s.h - pcs.h);

		if(this.position == "up")
			this.$.bottom.slidePosition = this.$.bottom.lastSlidePosition = -(enyo.fetchControlSize(this.$.bottom).h-56);
	},

	getContent: function() {
		return this.$.editor.getText();
	},

	setContent: function(inContent, inFilename) {
		var inFilename = inFilename || "";
		this.$.editor.setValue(inContent);
		this.$.filename.setContent(inFilename.basename(".md"));
		this.syncViews({name:"setcontent"});
	},

	setActive: function(inToggle) {
		if(inToggle == true) {
			this.$.editorScroller.removeClass("disabled");
			this.$.editor.setDisabled(false);
		}
		else {
			this.$.editorScroller.addClass("disabled");
			this.$.editor.setDisabled(true);
		}
	}
});
<commit_msg>Linkklickproblem behoben<commit_after>/* Copyright 2009-2011 Hewlett-Packard Development Company, L.P. All rights reserved. */

enyo.kind({
	name: "EditorPanel",
	kind: enyo.HFlexBox,
	components: [
		{name: "slidingPane", kind: "HSlidingPane", flex: 1, multiViewMinHeight: 0,
			onSlideComplete: "barMoved",
			components: [
			{name: "top", kind:"HSlidingView",
				components: [
					{kind: "HFlexBox", pack: "center", components: [
						{className: "editor-left", flex: 1, overflow: "hidden" },
						{kind: "BasicScroller",
							name:"editorScroller",
							height: "760px",
							width: "760px",
							autoHorizontal: false,
							horizontal: false,
							className: "editor-scroller",
							onmouseup: "scrollerClicked",
							components: [
								{kind: "RichText",
									name: "editor",
									richContent: true,
									className: "editor-input",
									onblur: "syncViews",
									onfocus: "editorFocussed",
									onmouseup: "syncViews",
									hint: "",
									spellcheck: true,
									autocorrect: false,
									autoCapitalize: false,
									styled: false
								}
							]
						},
						{className: "editor-right", flex: 1, overflow: "hidden" }
					]}
			]},
			{name: "bottom", kind:"HSlidingView", height: "100px", fixedHeight: true, pack:"end", style:"overflow: hidden;",
				applySlideToNode: function(inSlide) {
					if (inSlide != this.slidePosition && this.index) {
						inSlide = Math.min(0,inSlide);
						this.lastSlidePosition = this.slidePosition;
						this.slidePosition = inSlide;
						if (this.hasNode()) {
							//this.log(this.id, inSlide);
							var t = inSlide !== null ? "translate3d(0," + inSlide + "px,0)" : "";
							this.domStyles["-webkit-transform"] = this.node.style.webkitTransform = t;
							var s = inSlide !== null ? "translate3d(0,"+ (-enyo.fetchControlSize(this).h-inSlide+55) + "px,0)" : "";
							this.$.client.children[1].node.style.webkitTransform = s;
						}
					}
				},
				resizeHandler: function() {
					//var s = "translate3d(0,1px,0)";
					var s = "translate3d(0," + (55-enyo.fetchControlSize(this).h) + "px,0)";
					this.node.style.webkitTransform = s;
				},
				components: [
					{kind: "Header", name: "header", height: "55px",className: "enyo-toolbar fake-toolbar", style:"z-index: 1000;",
						onmousedown: "sliderclicked",
						onmouseup: "sliderreleased",
						components: [
						{kind: "GrabButton", className: "HGrabButton"},
						{kind: "Spacer", style:"width: 1px;"},
						{content: "", name:"filename"},
						//{kind: "Image", src:"images/title.png", style:"margin-top: 4px;"},
						{kind: "Spacer", flex: 25},
						//{content: "Typewriter"},
						{kind: "Spacer", style:"width: 1px;"},
						{kind: "GrabButton", className: "HGrabButton Right"}
					]},
					{kind: "VFlexBox", flex: 1,
						components: [
						{kind: "HFlexBox", flex: 1, pack: "center", components: [
							{className: "preview-left", flex: 1, overflow: "hidden" },
							{kind: "BasicScroller",
								name:"previewScroller",
								width: "760px",
								autoHorizontal: false,
								horizontal: false,
								className: "output-scroller",
								components: [
									{kind: "HtmlContent",
										name: "preview",
										className: "output-preview",
										onLinkClick: "htmlContentLinkClick"
									}
								]
							},
							{className: "preview-right", flex: 1, overflow: "hidden" }
						]}
					]}
			]}
		]},
		{name:'filePicker', kind: "FilePicker", fileType:["image"], allowMultiSelect:false, onPickFile: "handleFile"},
		{kind: "PrintDialog",
			duplexOption: true,
			frameToPrint: {name:"preview", landscape:false},
			appName: "Typewriter"
		}
	],

	/* PREVIEW HANDLING */
	synccount: 0,

	barBeingDragged: false,

	sliderclicked: function() {
		this.barBeingDragged = true;
	},

	sliderreleased: function() {
		this.barBeingDragged = false;
	},

	barMoved: function(event) {
		if(event.view == this.$.top) {
			this.position = "down";
			//enyo.keyboard.show();
		}
		else {
			this.position = "up";
			//enyo.keyboard.hide();
		}

		//this.generateMarkdown();
	},
	htmlContentLinkClick: function(inSender, url) {
		var splits = url.split(/#/).slice(-1).pop();
		switch(splits) {
			case "clear":
				this.$.editor.setValue("");
			case "editor":
				this.$.slidingPane.selectView(this.$.top);
				return false;
				break;
			case "preview":
				this.$.slidingPane.selectView(this.$.bottom);
				return false;
				break;
			default:
				var r = new enyo.PalmService();
				r.service = "palm://com.palm.applicationManager/";
				r.method = "open";
				r.call({target: url});
		}
	},

	/* PRINT HANDLING */

	printDocument: function() {
		this.$.slidingPane.selectViewByIndex(1);
		this.$.printDialog.openAtCenter();
	},
	/* EDITOR CALLBACKS */
	editorFocussed: function() {
		enyo.keyboard.show();
	},

	scrollerClicked: function() {
		this.$.editor.forceFocus();
	},

	syncViews: function(inSender, inEvent) {

		var eb = this.$.editorScroller.getBoundaries().bottom;
		var et = this.$.editorScroller.getBoundaries().top;
		var ec = this.$.editorScroller.scrollTop;
		var ep = eb!=0 ? ec/eb : 0;
		ep = Math.max(0,ep);
		ep = Math.min(1,ep);

		if(this.position == "up")
			var pb = this.$.previewScroller.getBoundaries().bottom;
		else // weirdly enough the preview box appears to be bigger if the bar is down. we'll simply substract the height added here.
			var pb = this.$.previewScroller.getBoundaries().bottom - enyo.fetchControlSize(this).h+55;
		var pt = this.$.previewScroller.getBoundaries().top;
		var pc = this.$.previewScroller.scrollTop;
		var pp = pb!=0 ? pc/pb : 0;
		pp = Math.max(0,pp);
		pp = Math.min(1,pp);

		if(!this.barBeingDragged && (this.$.bottom.slidePosition == 0 || -enyo.fetchControlSize(this).h == this.$.bottom.slidePosition-55)) {
			if(this.position == "up") {
				//enyo.keyboard.hide();
				//this.$.editorScroller.setScrollTop(pp * eb); // TODO: das kann sau nervig sein
			}
			else {
				//enyo.keyboard.show();
				this.$.previewScroller.setScrollTop(ep * pb);
				//this.$.editor.forceFocus(); // buggy with on screen keyboard
			}
		}

		//console.log(pp + " " + ep);

		if(this.synccount == 0 || inSender.name != "schedule") {
			this.makePreview();
		}
		this.synccount++;
		this.synccount = this.synccount % 3;
	},

	makePreview: function() {
		//console.log("generating Markdown");
		var converter = new Showdown.converter();
		var value = this.$.editor.getText();
		this.$.preview.setContent(converter.makeHtml(value));
	},

	setSchedule: function() {
		this.scheduleID = setInterval(enyo.bind(this,function() {
			this.syncViews({name:"schedule"});
		}), 5000);
	},
	clearSchedule: function() {
		clearInterval(this.scheduleID);
	},

	/* CONSTRUCTOR */

	ready: function() {
		this.position = "down";
		enyo.keyboard.setManualMode(true);
		//enyo.keyboard.setResizesWindow(false);
		enyo.keyboard.show();

		this.syncViews({name:"startup"});
	},

	rendered: function() {
		this.inherited(arguments);
		this.adjustSlidingSize();
		this.setSchedule();
	},
	resizeHandler: function() {
		this.adjustSlidingSize();
		var v = this.$.slidingPane.view;
		if (v && v.resizeHandler) {
			v.resizeHandler();
		}
	},
	adjustSlidingSize: function() {
		var s = enyo.fetchControlSize(this);
		var pcs = enyo.fetchControlSize(this.$.bottom.$.client);
		var kh = enyo.keyboard.isShowing() ? enyo.keyboard.height : 0;

		this.$.editorScroller.node.style.height = (s.h - 55 - kh) + "px";

		this.$.top.node.style.height = (s.h - 55 - kh) + "px";
		this.$.bottom.node.style.height = (s.h - kh) + "px";
		this.$.bottom.setPeekHeight(s.h - pcs.h);

		if(this.position == "up")
			this.$.bottom.slidePosition = this.$.bottom.lastSlidePosition = -(enyo.fetchControlSize(this.$.bottom).h-56);
	},

	getContent: function() {
		return this.$.editor.getText();
	},

	setContent: function(inContent, inFilename) {
		var inFilename = inFilename || "";
		this.$.editor.setValue(inContent);
		this.$.filename.setContent(inFilename.basename(".md"));
		this.syncViews({name:"setcontent"});
	},

	setActive: function(inToggle) {
		if(inToggle == true) {
			this.$.editorScroller.removeClass("disabled");
			this.$.editor.setDisabled(false);
		}
		else {
			this.$.editorScroller.addClass("disabled");
			this.$.editor.setDisabled(true);
		}
	}
});
<|endoftext|>


<commit_before>define(['./getputdelete', './util'], function (getputdelete, util) {

  "use strict";

  var prefix = 'remote_storage_wire_';

  var events = util.getEventEmitter('connected', 'error');

  var state = 'anonymous';

  function setSetting(key, value) {
    localStorage.setItem(prefix+key, JSON.stringify(value));

    calcState();

    if(state == 'connected') {
      events.emit('connected');
    }
  }

  function removeSetting(key) {
    localStorage.removeItem(prefix+key);
  }

  function getSetting(key) {
    var valStr = localStorage.getItem(prefix+key);
    if(typeof(valStr) == 'string') {
      try {
        return JSON.parse(valStr);
      } catch(e) {
        localStorage.removeItem(prefix+key);
      }
    }
    return null;
  }

  function disconnectRemote() {
    util.grepLocalStorage(new RegExp('^' + prefix), function(key) {
      localStorage.removeItem(key);
    });
  }

  function getState() {
    return state;
  }

  function calcState() {
    if(getSetting('storageType') && getSetting('storageHref')) {
      if(getSetting('bearerToken')) {
        state = 'connected';
      } else {
        state = 'authing';
      }
    } else {
      state = 'anonymous';
    }
    return state;
  }

  function on(eventType, cb) {
    events.on(eventType, cb);
  }

  function resolveKey(path) {
    var storageHref = getSetting('storageHref');
    return storageHref + path;
  }

  var foreignKeyRE = /^([^\/][^:]+):(\/.*)$/;

  function isForeign(path) {
    return foreignKeyRE.test(path);
  }

  // Namespace: wireClient
  //
  // The wireClient stores the user's storage information and controls getputdelete accordingly.
  //
  // Event: connected
  //
  // Fired once everything is configured.

  // Method: get
  //
  // Get data from given path from remotestorage
  //
  // Parameters:
  //   path     - absolute path (starting from storage root)
  //   callback - see <getputdelete.get> for details on the callback parameters
  function get(path, cb) {
    if(isForeign(path)) {
      return getForeign(path, cb);
    } else if(state != 'connected') {
      cb(new Error('not-connected'));
    }
    var token = getSetting('bearerToken');
    if(typeof(path) != 'string') {
      cb(new Error('argument "path" should be a string'));
    } else {
      getputdelete.get(resolveKey(path), token, cb);
    }
  }

  function getForeign(fullPath, cb) {
    var md = fullPath.match(foreignKeyRE);
    var userAddress = md[1];
    var path = md[2];
    var base = getStorageHrefForUser(userAddress);
    getputdelete.get(base + path, null, cb);
  }

  // Method: set
  //
  // Write data to given path in remotestorage
  //
  // Parameters:
  //   path     - absolute path (starting from storage root)
  //   valueStr - raw data to write
  //   mimeType - MIME type to set as Content-Type header
  //   callback - see <getputdelete.set> for details on the callback parameters.
  function set(path, valueStr, mimeType, cb) {
    if(isForeign(path)) {
      return cb(new Error("Foreign storage is read-only"));
    } else if(state != 'connected') {
      cb(new Error('not-connected'));
    }
    var token = getSetting('bearerToken');
    if(typeof(path) != 'string') {
      cb(new Error('argument "path" should be a string'));
    } else {
      if(valueStr && typeof(valueStr) != 'string' &&
         !(typeof(valueStr) == 'object' && valueStr instanceof ArrayBuffer)) {
        valueStr = JSON.stringify(valueStr);
      }
      getputdelete.set(resolveKey(path), valueStr, mimeType, token, cb);
    }
  }

  // Method: remove
  //
  // Remove data at given path from remotestorage
  //
  // Parameters:
  //   path     - absolute path (starting from storage root)
  //   callback - see <getputdelete.set> for details on the callback parameters.
  function remove(path, cb) {
    if(isForeign(path)) {
      return cb(new Error("Foreign storage is read-only"));
    }
    var token = getSetting('bearerToken');
    getputdelete.set(resolveKey(path), undefined, undefined, token, cb);
  }

  function getStorageHrefForUser(userAddress) {
    var info = getSetting('storageInfo:' + userAddress);
    if(! info) {
      throw new Error("userAddress unknown to wireClient: " + userAddress);
    }
    return info.href;
  }

  function addStorageInfo(userAddress, storageInfo) {
    setSetting('storageInfo:' + userAddress, storageInfo);
  }

  function hasStorageInfo(userAddress) {
    return !! getSetting('storageInfo:' + userAddress);
  }

  return {

    get: get,
    set: set,
    remove: remove,

    // Method: setStorageInfo
    //
    // Configure wireClient.
    //
    // Parameters:
    //   type - the storage type (see specification)
    //   href - base URL of the storage server
    //
    // Fires:
    //   configured - if wireClient is now fully configured
    //
    setStorageInfo   : function(type, href) {
      setSetting('storageType', type);
      setSetting('storageHref', href);
    },

    // Method: getStorageHref
    //
    // Get base URL of the user's remotestorage.
    getStorageHref   : function() {
      return getSetting('storageHref');
    },

    // Method: SetBearerToken
    //
    // Set the bearer token for authorization
    //
    // Parameters:
    //   bearerToken - token to use
    //
    // Fires:
    //   configured - if wireClient is now fully configured.
    //
    setBearerToken   : function(bearerToken) {
      setSetting('bearerToken', bearerToken);
    },

    getBearerToken   : function(bearerToken) {
      return getSetting('bearerToken');
    },

    // Method: addStorageInfo
    //
    // Add another user's storage info.
    // After calling this, keys in the form userAddress:path can be resolved.
    //
    // Parameters:
    //   userAddress - a user address in the form user@host
    //   storageInfo - an object, with at least an 'href' attribute
    addStorageInfo: addStorageInfo,

    // Method: hasStorageInfo
    //
    // Find out if the wireClient has cached storageInfo for the given userAddress.
    //
    // Parameters:
    //   userAddress - a user address to look up
    hasStorageInfo: hasStorageInfo,

    // Method: disconnectRemote
    //
    // Clear the wireClient configuration
    disconnectRemote : disconnectRemote,

    // Method: on
    //
    // Install an event handler
    //
    //
    on               : on,

    // Method: getState
    //
    // Get current state.
    //
    // Possible states are:
    //   anonymous - no information set
    //   authing   - storage's type & href set, but no token received yet
    //   connected - all information present.
    getState         : getState,
    calcState: calcState
  };
});
<commit_msg>wireClient: re-calculate state when disconnecting<commit_after>define(['./getputdelete', './util'], function (getputdelete, util) {

  "use strict";

  var prefix = 'remote_storage_wire_';

  var events = util.getEventEmitter('connected', 'error');

  var state = 'anonymous';

  function setSetting(key, value) {
    localStorage.setItem(prefix+key, JSON.stringify(value));

    calcState();

    if(state == 'connected') {
      events.emit('connected');
    }
  }

  function removeSetting(key) {
    localStorage.removeItem(prefix+key);
  }

  function getSetting(key) {
    var valStr = localStorage.getItem(prefix+key);
    if(typeof(valStr) == 'string') {
      try {
        return JSON.parse(valStr);
      } catch(e) {
        localStorage.removeItem(prefix+key);
      }
    }
    return null;
  }

  function disconnectRemote() {
    util.grepLocalStorage(new RegExp('^' + prefix), function(key) {
      localStorage.removeItem(key);
    });
    calcState();
  }

  function getState() {
    return state;
  }

  function calcState() {
    if(getSetting('storageType') && getSetting('storageHref')) {
      if(getSetting('bearerToken')) {
        state = 'connected';
      } else {
        state = 'authing';
      }
    } else {
      state = 'anonymous';
    }
    return state;
  }

  function on(eventType, cb) {
    events.on(eventType, cb);
  }

  function resolveKey(path) {
    var storageHref = getSetting('storageHref');
    return storageHref + path;
  }

  var foreignKeyRE = /^([^\/][^:]+):(\/.*)$/;

  function isForeign(path) {
    return foreignKeyRE.test(path);
  }

  // Namespace: wireClient
  //
  // The wireClient stores the user's storage information and controls getputdelete accordingly.
  //
  // Event: connected
  //
  // Fired once everything is configured.

  // Method: get
  //
  // Get data from given path from remotestorage
  //
  // Parameters:
  //   path     - absolute path (starting from storage root)
  //   callback - see <getputdelete.get> for details on the callback parameters
  function get(path, cb) {
    if(isForeign(path)) {
      return getForeign(path, cb);
    } else if(state != 'connected') {
      cb(new Error('not-connected'));
    }
    var token = getSetting('bearerToken');
    if(typeof(path) != 'string') {
      cb(new Error('argument "path" should be a string'));
    } else {
      getputdelete.get(resolveKey(path), token, cb);
    }
  }

  function getForeign(fullPath, cb) {
    var md = fullPath.match(foreignKeyRE);
    var userAddress = md[1];
    var path = md[2];
    var base = getStorageHrefForUser(userAddress);
    getputdelete.get(base + path, null, cb);
  }

  // Method: set
  //
  // Write data to given path in remotestorage
  //
  // Parameters:
  //   path     - absolute path (starting from storage root)
  //   valueStr - raw data to write
  //   mimeType - MIME type to set as Content-Type header
  //   callback - see <getputdelete.set> for details on the callback parameters.
  function set(path, valueStr, mimeType, cb) {
    if(isForeign(path)) {
      return cb(new Error("Foreign storage is read-only"));
    } else if(state != 'connected') {
      cb(new Error('not-connected'));
    }
    var token = getSetting('bearerToken');
    if(typeof(path) != 'string') {
      cb(new Error('argument "path" should be a string'));
    } else {
      if(valueStr && typeof(valueStr) != 'string' &&
         !(typeof(valueStr) == 'object' && valueStr instanceof ArrayBuffer)) {
        valueStr = JSON.stringify(valueStr);
      }
      getputdelete.set(resolveKey(path), valueStr, mimeType, token, cb);
    }
  }

  // Method: remove
  //
  // Remove data at given path from remotestorage
  //
  // Parameters:
  //   path     - absolute path (starting from storage root)
  //   callback - see <getputdelete.set> for details on the callback parameters.
  function remove(path, cb) {
    if(isForeign(path)) {
      return cb(new Error("Foreign storage is read-only"));
    }
    var token = getSetting('bearerToken');
    getputdelete.set(resolveKey(path), undefined, undefined, token, cb);
  }

  function getStorageHrefForUser(userAddress) {
    var info = getSetting('storageInfo:' + userAddress);
    if(! info) {
      throw new Error("userAddress unknown to wireClient: " + userAddress);
    }
    return info.href;
  }

  function addStorageInfo(userAddress, storageInfo) {
    setSetting('storageInfo:' + userAddress, storageInfo);
  }

  function hasStorageInfo(userAddress) {
    return !! getSetting('storageInfo:' + userAddress);
  }

  return {

    get: get,
    set: set,
    remove: remove,

    // Method: setStorageInfo
    //
    // Configure wireClient.
    //
    // Parameters:
    //   type - the storage type (see specification)
    //   href - base URL of the storage server
    //
    // Fires:
    //   configured - if wireClient is now fully configured
    //
    setStorageInfo   : function(type, href) {
      setSetting('storageType', type);
      setSetting('storageHref', href);
    },

    // Method: getStorageHref
    //
    // Get base URL of the user's remotestorage.
    getStorageHref   : function() {
      return getSetting('storageHref');
    },

    // Method: SetBearerToken
    //
    // Set the bearer token for authorization
    //
    // Parameters:
    //   bearerToken - token to use
    //
    // Fires:
    //   configured - if wireClient is now fully configured.
    //
    setBearerToken   : function(bearerToken) {
      setSetting('bearerToken', bearerToken);
    },

    getBearerToken   : function(bearerToken) {
      return getSetting('bearerToken');
    },

    // Method: addStorageInfo
    //
    // Add another user's storage info.
    // After calling this, keys in the form userAddress:path can be resolved.
    //
    // Parameters:
    //   userAddress - a user address in the form user@host
    //   storageInfo - an object, with at least an 'href' attribute
    addStorageInfo: addStorageInfo,

    // Method: hasStorageInfo
    //
    // Find out if the wireClient has cached storageInfo for the given userAddress.
    //
    // Parameters:
    //   userAddress - a user address to look up
    hasStorageInfo: hasStorageInfo,

    // Method: disconnectRemote
    //
    // Clear the wireClient configuration
    disconnectRemote : disconnectRemote,

    // Method: on
    //
    // Install an event handler
    //
    //
    on               : on,

    // Method: getState
    //
    // Get current state.
    //
    // Possible states are:
    //   anonymous - no information set
    //   authing   - storage's type & href set, but no token received yet
    //   connected - all information present.
    getState         : getState,
    calcState: calcState
  };
});
<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import {baseUrl} from "./utils/permalinks/SpecPermalinkConstructor";
import {tryTransformEntityToPermalink, tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks";

function matrixLinkify(linkify) {
    // Text tokens
    const TT = linkify.scanner.TOKENS;
    // Multi tokens
    const MT = linkify.parser.TOKENS;
    const MultiToken = MT.Base;
    const S_START = linkify.parser.start;

    if (TT.UNDERSCORE === undefined) {
        throw new Error("linkify-matrix requires linkifyjs 2.1.1: this version is too old.");
    }

    const ROOMALIAS = function(value) {
        MultiToken.call(this, value);
        this.type = 'roomalias';
        this.isLink = true;
    };
    ROOMALIAS.prototype = new MultiToken();

    const S_HASH = S_START.jump(TT.POUND);
    const S_HASH_NAME = new linkify.parser.State();
    const S_HASH_NAME_COLON = new linkify.parser.State();
    const S_HASH_NAME_COLON_DOMAIN = new linkify.parser.State();
    const S_HASH_NAME_COLON_DOMAIN_DOT = new linkify.parser.State();
    const S_ROOMALIAS = new linkify.parser.State(ROOMALIAS);
    const S_ROOMALIAS_COLON = new linkify.parser.State();
    const S_ROOMALIAS_COLON_NUM = new linkify.parser.State(ROOMALIAS);

    const roomnameTokens = [
        TT.DOT,
        TT.PLUS,
        TT.NUM,
        TT.DOMAIN,
        TT.TLD,
        TT.UNDERSCORE,
        TT.POUND,

        // because 'localhost' is tokenised to the localhost token,
        // usernames @localhost:foo.com are otherwise not matched!
        TT.LOCALHOST,
    ];

    S_HASH.on(roomnameTokens, S_HASH_NAME);
    S_HASH_NAME.on(roomnameTokens, S_HASH_NAME);
    S_HASH_NAME.on(TT.DOMAIN, S_HASH_NAME);

    S_HASH_NAME.on(TT.COLON, S_HASH_NAME_COLON);

    S_HASH_NAME_COLON.on(TT.DOMAIN, S_HASH_NAME_COLON_DOMAIN);
    S_HASH_NAME_COLON.on(TT.LOCALHOST, S_ROOMALIAS); // accept #foo:localhost
    S_HASH_NAME_COLON.on(TT.TLD, S_ROOMALIAS); // accept #foo:com (mostly for (TLD|DOMAIN)+ mixing)
    S_HASH_NAME_COLON_DOMAIN.on(TT.DOT, S_HASH_NAME_COLON_DOMAIN_DOT);
    S_HASH_NAME_COLON_DOMAIN_DOT.on(TT.DOMAIN, S_HASH_NAME_COLON_DOMAIN);
    S_HASH_NAME_COLON_DOMAIN_DOT.on(TT.TLD, S_ROOMALIAS);

    S_ROOMALIAS.on(TT.DOT, S_HASH_NAME_COLON_DOMAIN_DOT); // accept repeated TLDs (e.g .org.uk)
    S_ROOMALIAS.on(TT.COLON, S_ROOMALIAS_COLON); // do not accept trailing `:`
    S_ROOMALIAS_COLON.on(TT.NUM, S_ROOMALIAS_COLON_NUM); // but do accept :NUM (port specifier)

    const USERID = function(value) {
        MultiToken.call(this, value);
        this.type = 'userid';
        this.isLink = true;
    };
    USERID.prototype = new MultiToken();

    const S_AT = S_START.jump(TT.AT);
    const S_AT_NAME = new linkify.parser.State();
    const S_AT_NAME_COLON = new linkify.parser.State();
    const S_AT_NAME_COLON_DOMAIN = new linkify.parser.State();
    const S_AT_NAME_COLON_DOMAIN_DOT = new linkify.parser.State();
    const S_USERID = new linkify.parser.State(USERID);
    const S_USERID_COLON = new linkify.parser.State();
    const S_USERID_COLON_NUM = new linkify.parser.State(USERID);

    const usernameTokens = [
        TT.DOT,
        TT.UNDERSCORE,
        TT.PLUS,
        TT.NUM,
        TT.DOMAIN,
        TT.TLD,

        // as in roomnameTokens
        TT.LOCALHOST,
    ];

    S_AT.on(usernameTokens, S_AT_NAME);
    S_AT_NAME.on(usernameTokens, S_AT_NAME);
    S_AT_NAME.on(TT.DOMAIN, S_AT_NAME);

    S_AT_NAME.on(TT.COLON, S_AT_NAME_COLON);

    S_AT_NAME_COLON.on(TT.DOMAIN, S_AT_NAME_COLON_DOMAIN);
    S_AT_NAME_COLON.on(TT.LOCALHOST, S_USERID); // accept @foo:localhost
    S_AT_NAME_COLON.on(TT.TLD, S_USERID); // accept @foo:com (mostly for (TLD|DOMAIN)+ mixing)
    S_AT_NAME_COLON_DOMAIN.on(TT.DOT, S_AT_NAME_COLON_DOMAIN_DOT);
    S_AT_NAME_COLON_DOMAIN_DOT.on(TT.DOMAIN, S_AT_NAME_COLON_DOMAIN);
    S_AT_NAME_COLON_DOMAIN_DOT.on(TT.TLD, S_USERID);

    S_USERID.on(TT.DOT, S_AT_NAME_COLON_DOMAIN_DOT); // accept repeated TLDs (e.g .org.uk)
    S_USERID.on(TT.COLON, S_USERID_COLON); // do not accept trailing `:`
    S_USERID_COLON.on(TT.NUM, S_USERID_COLON_NUM); // but do accept :NUM (port specifier)

    const GROUPID = function(value) {
        MultiToken.call(this, value);
        this.type = 'groupid';
        this.isLink = true;
    };
    GROUPID.prototype = new MultiToken();

    const S_PLUS = S_START.jump(TT.PLUS);
    const S_PLUS_NAME = new linkify.parser.State();
    const S_PLUS_NAME_COLON = new linkify.parser.State();
    const S_PLUS_NAME_COLON_DOMAIN = new linkify.parser.State();
    const S_PLUS_NAME_COLON_DOMAIN_DOT = new linkify.parser.State();
    const S_GROUPID = new linkify.parser.State(GROUPID);
    const S_GROUPID_COLON = new linkify.parser.State();
    const S_GROUPID_COLON_NUM = new linkify.parser.State(GROUPID);

    const groupIdTokens = [
        TT.DOT,
        TT.UNDERSCORE,
        TT.PLUS,
        TT.NUM,
        TT.DOMAIN,
        TT.TLD,

        // as in roomnameTokens
        TT.LOCALHOST,
    ];

    S_PLUS.on(groupIdTokens, S_PLUS_NAME);
    S_PLUS_NAME.on(groupIdTokens, S_PLUS_NAME);
    S_PLUS_NAME.on(TT.DOMAIN, S_PLUS_NAME);

    S_PLUS_NAME.on(TT.COLON, S_PLUS_NAME_COLON);

    S_PLUS_NAME_COLON.on(TT.DOMAIN, S_PLUS_NAME_COLON_DOMAIN);
    S_PLUS_NAME_COLON.on(TT.LOCALHOST, S_GROUPID); // accept +foo:localhost
    S_PLUS_NAME_COLON.on(TT.TLD, S_GROUPID); // accept +foo:com (mostly for (TLD|DOMAIN)+ mixing)
    S_PLUS_NAME_COLON_DOMAIN.on(TT.DOT, S_PLUS_NAME_COLON_DOMAIN_DOT);
    S_PLUS_NAME_COLON_DOMAIN_DOT.on(TT.DOMAIN, S_PLUS_NAME_COLON_DOMAIN);
    S_PLUS_NAME_COLON_DOMAIN_DOT.on(TT.TLD, S_GROUPID);

    S_GROUPID.on(TT.DOT, S_PLUS_NAME_COLON_DOMAIN_DOT); // accept repeated TLDs (e.g .org.uk)
    S_GROUPID.on(TT.COLON, S_GROUPID_COLON); // do not accept trailing `:`
    S_GROUPID_COLON.on(TT.NUM, S_GROUPID_COLON_NUM); // but do accept :NUM (port specifier)
}

// stubs, overwritten in MatrixChat's componentDidMount
matrixLinkify.onUserClick = function(e, userId) { e.preventDefault(); };
matrixLinkify.onAliasClick = function(e, roomAlias) { e.preventDefault(); };
matrixLinkify.onGroupClick = function(e, groupId) { e.preventDefault(); };

const escapeRegExp = function(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
};

// Recognise URLs from both our local vector and official vector as vector.
// anyone else really should be using matrix.to.
matrixLinkify.VECTOR_URL_PATTERN = "^(?:https?://)?(?:"
    + escapeRegExp(window.location.host + window.location.pathname) + "|"
    + "(?:www\\.)?(?:riot|vector)\\.im/(?:app|beta|staging|develop)/"
    + ")(#.*)";

matrixLinkify.MATRIXTO_URL_PATTERN = "^(?:https?://)?(?:www\\.)?matrix\\.to/#/(([#@!+]).*)";
matrixLinkify.MATRIXTO_MD_LINK_PATTERN =
    '\\[([^\\]]*)\\]\\((?:https?://)?(?:www\\.)?matrix\\.to/#/([#@!+][^\\)]*)\\)';
matrixLinkify.MATRIXTO_BASE_URL= baseUrl;

matrixLinkify.options = {
    events: function(href, type) {
        switch (type) {
            case "userid":
                return {
                    click: function(e) {
                        matrixLinkify.onUserClick(e, href);
                    },
                };
            case "roomalias":
                return {
                    click: function(e) {
                        matrixLinkify.onAliasClick(e, href);
                    },
                };
            case "groupid":
                return {
                    click: function(e) {
                        matrixLinkify.onGroupClick(e, href);
                    },
                };
        }
    },

    formatHref: function(href, type) {
        switch (type) {
            case 'roomalias':
            case 'userid':
            case 'groupid':
            default: {
                return tryTransformEntityToPermalink(href);
            }
        }
    },

    linkAttributes: {
        rel: 'noreferrer noopener',
    },

    target: function(href, type) {
        if (type === 'url') {
            const transformed = tryTransformPermalinkToLocalHref(href);
            if (transformed !== href || href.match(matrixLinkify.VECTOR_URL_PATTERN)) {
                return null;
            } else {
                return '_blank';
            }
        }
        return null;
    },
};

export default matrixLinkify;
<commit_msg>Handle matrix.to user permalink in-room rather than solo<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import {baseUrl} from "./utils/permalinks/SpecPermalinkConstructor";
import {
    parsePermalink,
    tryTransformEntityToPermalink,
    tryTransformPermalinkToLocalHref
} from "./utils/permalinks/Permalinks";

function matrixLinkify(linkify) {
    // Text tokens
    const TT = linkify.scanner.TOKENS;
    // Multi tokens
    const MT = linkify.parser.TOKENS;
    const MultiToken = MT.Base;
    const S_START = linkify.parser.start;

    if (TT.UNDERSCORE === undefined) {
        throw new Error("linkify-matrix requires linkifyjs 2.1.1: this version is too old.");
    }

    const ROOMALIAS = function(value) {
        MultiToken.call(this, value);
        this.type = 'roomalias';
        this.isLink = true;
    };
    ROOMALIAS.prototype = new MultiToken();

    const S_HASH = S_START.jump(TT.POUND);
    const S_HASH_NAME = new linkify.parser.State();
    const S_HASH_NAME_COLON = new linkify.parser.State();
    const S_HASH_NAME_COLON_DOMAIN = new linkify.parser.State();
    const S_HASH_NAME_COLON_DOMAIN_DOT = new linkify.parser.State();
    const S_ROOMALIAS = new linkify.parser.State(ROOMALIAS);
    const S_ROOMALIAS_COLON = new linkify.parser.State();
    const S_ROOMALIAS_COLON_NUM = new linkify.parser.State(ROOMALIAS);

    const roomnameTokens = [
        TT.DOT,
        TT.PLUS,
        TT.NUM,
        TT.DOMAIN,
        TT.TLD,
        TT.UNDERSCORE,
        TT.POUND,

        // because 'localhost' is tokenised to the localhost token,
        // usernames @localhost:foo.com are otherwise not matched!
        TT.LOCALHOST,
    ];

    S_HASH.on(roomnameTokens, S_HASH_NAME);
    S_HASH_NAME.on(roomnameTokens, S_HASH_NAME);
    S_HASH_NAME.on(TT.DOMAIN, S_HASH_NAME);

    S_HASH_NAME.on(TT.COLON, S_HASH_NAME_COLON);

    S_HASH_NAME_COLON.on(TT.DOMAIN, S_HASH_NAME_COLON_DOMAIN);
    S_HASH_NAME_COLON.on(TT.LOCALHOST, S_ROOMALIAS); // accept #foo:localhost
    S_HASH_NAME_COLON.on(TT.TLD, S_ROOMALIAS); // accept #foo:com (mostly for (TLD|DOMAIN)+ mixing)
    S_HASH_NAME_COLON_DOMAIN.on(TT.DOT, S_HASH_NAME_COLON_DOMAIN_DOT);
    S_HASH_NAME_COLON_DOMAIN_DOT.on(TT.DOMAIN, S_HASH_NAME_COLON_DOMAIN);
    S_HASH_NAME_COLON_DOMAIN_DOT.on(TT.TLD, S_ROOMALIAS);

    S_ROOMALIAS.on(TT.DOT, S_HASH_NAME_COLON_DOMAIN_DOT); // accept repeated TLDs (e.g .org.uk)
    S_ROOMALIAS.on(TT.COLON, S_ROOMALIAS_COLON); // do not accept trailing `:`
    S_ROOMALIAS_COLON.on(TT.NUM, S_ROOMALIAS_COLON_NUM); // but do accept :NUM (port specifier)

    const USERID = function(value) {
        MultiToken.call(this, value);
        this.type = 'userid';
        this.isLink = true;
    };
    USERID.prototype = new MultiToken();

    const S_AT = S_START.jump(TT.AT);
    const S_AT_NAME = new linkify.parser.State();
    const S_AT_NAME_COLON = new linkify.parser.State();
    const S_AT_NAME_COLON_DOMAIN = new linkify.parser.State();
    const S_AT_NAME_COLON_DOMAIN_DOT = new linkify.parser.State();
    const S_USERID = new linkify.parser.State(USERID);
    const S_USERID_COLON = new linkify.parser.State();
    const S_USERID_COLON_NUM = new linkify.parser.State(USERID);

    const usernameTokens = [
        TT.DOT,
        TT.UNDERSCORE,
        TT.PLUS,
        TT.NUM,
        TT.DOMAIN,
        TT.TLD,

        // as in roomnameTokens
        TT.LOCALHOST,
    ];

    S_AT.on(usernameTokens, S_AT_NAME);
    S_AT_NAME.on(usernameTokens, S_AT_NAME);
    S_AT_NAME.on(TT.DOMAIN, S_AT_NAME);

    S_AT_NAME.on(TT.COLON, S_AT_NAME_COLON);

    S_AT_NAME_COLON.on(TT.DOMAIN, S_AT_NAME_COLON_DOMAIN);
    S_AT_NAME_COLON.on(TT.LOCALHOST, S_USERID); // accept @foo:localhost
    S_AT_NAME_COLON.on(TT.TLD, S_USERID); // accept @foo:com (mostly for (TLD|DOMAIN)+ mixing)
    S_AT_NAME_COLON_DOMAIN.on(TT.DOT, S_AT_NAME_COLON_DOMAIN_DOT);
    S_AT_NAME_COLON_DOMAIN_DOT.on(TT.DOMAIN, S_AT_NAME_COLON_DOMAIN);
    S_AT_NAME_COLON_DOMAIN_DOT.on(TT.TLD, S_USERID);

    S_USERID.on(TT.DOT, S_AT_NAME_COLON_DOMAIN_DOT); // accept repeated TLDs (e.g .org.uk)
    S_USERID.on(TT.COLON, S_USERID_COLON); // do not accept trailing `:`
    S_USERID_COLON.on(TT.NUM, S_USERID_COLON_NUM); // but do accept :NUM (port specifier)

    const GROUPID = function(value) {
        MultiToken.call(this, value);
        this.type = 'groupid';
        this.isLink = true;
    };
    GROUPID.prototype = new MultiToken();

    const S_PLUS = S_START.jump(TT.PLUS);
    const S_PLUS_NAME = new linkify.parser.State();
    const S_PLUS_NAME_COLON = new linkify.parser.State();
    const S_PLUS_NAME_COLON_DOMAIN = new linkify.parser.State();
    const S_PLUS_NAME_COLON_DOMAIN_DOT = new linkify.parser.State();
    const S_GROUPID = new linkify.parser.State(GROUPID);
    const S_GROUPID_COLON = new linkify.parser.State();
    const S_GROUPID_COLON_NUM = new linkify.parser.State(GROUPID);

    const groupIdTokens = [
        TT.DOT,
        TT.UNDERSCORE,
        TT.PLUS,
        TT.NUM,
        TT.DOMAIN,
        TT.TLD,

        // as in roomnameTokens
        TT.LOCALHOST,
    ];

    S_PLUS.on(groupIdTokens, S_PLUS_NAME);
    S_PLUS_NAME.on(groupIdTokens, S_PLUS_NAME);
    S_PLUS_NAME.on(TT.DOMAIN, S_PLUS_NAME);

    S_PLUS_NAME.on(TT.COLON, S_PLUS_NAME_COLON);

    S_PLUS_NAME_COLON.on(TT.DOMAIN, S_PLUS_NAME_COLON_DOMAIN);
    S_PLUS_NAME_COLON.on(TT.LOCALHOST, S_GROUPID); // accept +foo:localhost
    S_PLUS_NAME_COLON.on(TT.TLD, S_GROUPID); // accept +foo:com (mostly for (TLD|DOMAIN)+ mixing)
    S_PLUS_NAME_COLON_DOMAIN.on(TT.DOT, S_PLUS_NAME_COLON_DOMAIN_DOT);
    S_PLUS_NAME_COLON_DOMAIN_DOT.on(TT.DOMAIN, S_PLUS_NAME_COLON_DOMAIN);
    S_PLUS_NAME_COLON_DOMAIN_DOT.on(TT.TLD, S_GROUPID);

    S_GROUPID.on(TT.DOT, S_PLUS_NAME_COLON_DOMAIN_DOT); // accept repeated TLDs (e.g .org.uk)
    S_GROUPID.on(TT.COLON, S_GROUPID_COLON); // do not accept trailing `:`
    S_GROUPID_COLON.on(TT.NUM, S_GROUPID_COLON_NUM); // but do accept :NUM (port specifier)
}

// stubs, overwritten in MatrixChat's componentDidMount
matrixLinkify.onUserClick = function(e, userId) { e.preventDefault(); };
matrixLinkify.onAliasClick = function(e, roomAlias) { e.preventDefault(); };
matrixLinkify.onGroupClick = function(e, groupId) { e.preventDefault(); };

const escapeRegExp = function(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
};

// Recognise URLs from both our local vector and official vector as vector.
// anyone else really should be using matrix.to.
matrixLinkify.VECTOR_URL_PATTERN = "^(?:https?://)?(?:"
    + escapeRegExp(window.location.host + window.location.pathname) + "|"
    + "(?:www\\.)?(?:riot|vector)\\.im/(?:app|beta|staging|develop)/"
    + ")(#.*)";

matrixLinkify.MATRIXTO_URL_PATTERN = "^(?:https?://)?(?:www\\.)?matrix\\.to/#/(([#@!+]).*)";
matrixLinkify.MATRIXTO_MD_LINK_PATTERN =
    '\\[([^\\]]*)\\]\\((?:https?://)?(?:www\\.)?matrix\\.to/#/([#@!+][^\\)]*)\\)';
matrixLinkify.MATRIXTO_BASE_URL= baseUrl;

matrixLinkify.options = {
    events: function(href, type) {
        switch (type) {
            case "url": {
                // intercept local permalinks to users and show them like userids (in userinfo of current room)
                const permalink = parsePermalink(href);
                if (permalink && permalink.userId) {
                    return {
                        click: function(e) {
                            matrixLinkify.onUserClick(e, permalink.userId);
                        },
                    };
                }
                break;
            }
            case "userid":
                return {
                    click: function(e) {
                        matrixLinkify.onUserClick(e, href);
                    },
                };
            case "roomalias":
                return {
                    click: function(e) {
                        matrixLinkify.onAliasClick(e, href);
                    },
                };
            case "groupid":
                return {
                    click: function(e) {
                        matrixLinkify.onGroupClick(e, href);
                    },
                };
        }
    },

    formatHref: function(href, type) {
        switch (type) {
            case 'roomalias':
            case 'userid':
            case 'groupid':
            default: {
                return tryTransformEntityToPermalink(href);
            }
        }
    },

    linkAttributes: {
        rel: 'noreferrer noopener',
    },

    target: function(href, type) {
        if (type === 'url') {
            const transformed = tryTransformPermalinkToLocalHref(href);
            if (transformed !== href || href.match(matrixLinkify.VECTOR_URL_PATTERN)) {
                return null;
            } else {
                return '_blank';
            }
        }
        return null;
    },
};

export default matrixLinkify;
<|endoftext|>


<commit_before>/*
 * == TIDEPOOL LICENSE ==
 * Copyright (C) 2013 Tidepool Project
 *
 * This source code is subject to the terms of the Tidepool Open Data License, v. 1.0.
 * If a copy of the license was not provided with this file, you can obtain one at:
 *     http://tidepool.org/license/
 *
 * == TIDEPOOL LICENSE ==
 */

var util = require('util');
var fs = require('fs');
var path = require('path');
var child_process = require('child_process');

var shell = require('shelljs');
var async = require('async');

var fn = require('../fn.js');

var EMPTY_BUNDLE = { binary: { name: '_empty', version: '_empty'},  config: { name: '_empty', version: '_empty'} };

function AgentActions(s3, config) {
  this.s3 = s3;
  this.config = config;
  this.slots = {};

  this.bootstrap();
}

AgentActions.prototype.bootstrap = function()
{
  var deployDir = this.config.deployDir;
  shell.mkdir('-p', deployDir);

  var ids = shell.ls(deployDir);
  ids.forEach(this.addListing.bind(this));
  for (var slot in this.slots) {
    if (this.slots[slot].state === 'RUNNING') {
      var internal = this.slots[slot]._internal;
      this.watchPid(slot, internal.pid, internal.pidFile);
    }
  }
}

AgentActions.prototype.createSlot = function(params, bundle, res, cb) {
  var slot = params.slot;

  var slotDir = this.deployBase(slot);
  if (fs.existsSync(slotDir)) {
    return cb();
  }

  shell.mkdir('-p', path.join(slotDir, '0'));
  fs.writeFileSync(this.bundleFile(slot, 0), JSON.stringify(EMPTY_BUNDLE));
  this.addListing(slot);
  return cb();
}

AgentActions.prototype.deleteSlot = function(params, bundle, res, cb) {
  var slot = params.slot;

  if (this.slots[slot] != null && this.slots[slot].state == 'RUNNING') {
    return cb("Cannot delete slot with a running process, call stop first.");
  }

  var slotDir = this.deployBase(slot);
  if (fs.existsSync(slotDir)) {
    console.info("Deleting slot[%s], removing directory[%s]", slot, slotDir);
    shell.rm('-r', slotDir);
  }
  else {
    console.info("Asked to delete slot[%s], but dir[%s] wasn't there.", slot, slotDir);
  }

  return cb();
}

AgentActions.prototype.load = function(params, bundle, res, cb) {
  var self = this;
  var slot = params.slot;

  console.log("Loading [%j]", bundle);

  var deployCount = this.currDeployCount(slot);
  if (this.isRunning(slot, deployCount)) {
    return cb("Currently running, please stop first.");
  }

  var binaryFilename = util.format('%s-%s.tar.gz', bundle.binary.name, bundle.binary.version);
  var configFilename = util.format('config-%s.tar.gz', bundle.config.version);

  var binaryKeyPath = util.format('deploy/%s', bundle.binary.name);
  var configKeyPath = util.format('config/%s/%s', bundle.binary.name, bundle.config.name);

  var nextDeployCount = deployCount + 1;
  var outDir = this.deployDir(slot, nextDeployCount);

  async.series(
    [
      this.loadFile.bind(this, 'tidepool-binaries', binaryKeyPath , slot, binaryFilename),
      this.loadFile.bind(this, 'tidepool-config', configKeyPath, slot, configFilename),
      fn.asyncify(shell.mkdir.bind(shell, '-p', outDir)),
      this.expandTar.bind(this, this.tmpFile(slot, binaryFilename), outDir),
      this.expandTar.bind(this, this.tmpFile(slot, configFilename), path.join(outDir, path.basename(binaryFilename, '.tar.gz'))),
      fs.writeFile.bind(fs, this.bundleFile(slot, nextDeployCount), JSON.stringify(bundle, null, 2)),
      fn.asyncify(self.addListing.bind(self, slot))
    ],
    function(err, results) {
      var storageDir = self.tmpDir(slot);
      console.info("Removing temporary directory[%s]", storageDir)
      shell.rm('-r', storageDir);

      if (err) {
        return cb(err);
      }
      res.send(200);
      cb();
    }
  );
}

AgentActions.prototype.unload = function(params, bundle, res, cb) {
  var slot = params.slot;

  if (this.slots[slot] == null) {
    return cb();
  }

  if (this.slots[slot].state == 'RUNNING') {
    return cb("Cannot unload running process, call stop first.");
  }

  console.info("Unloading deploy at slot[%s]", slot);

  var deployCount = this.currDeployCount(slot) + 1;
  var deployDir = this.deployDir(slot, deployCount);
  shell.mkdir('-p', deployDir);
  fs.writeFileSync(this.bundleFile(slot, deployCount), JSON.stringify(EMPTY_BUNDLE));
  this.addListing(slot);
  return cb();
}

AgentActions.prototype.start = function(params, bundle, res, cb) {
  var slot = params.slot;
  var deployCount = this.currDeployCount(slot);

  if (deployCount == 0) {
    return cb(util.format("slot[%s] is not deployed.", slot));
  }

  var deployDir = this.deployDir(slot, deployCount);

  var bundle = JSON.parse(fs.readFileSync(this.bundleFile(slot, deployCount)));

  if (this.isRunning(slot, deployCount)) {
    return cb("Already running");
  }

  var workingDir = path.join(deployDir, util.format('%s-%s', bundle.binary.name, bundle.binary.version));
  var startScript = path.join(workingDir, 'start.sh');

  if (! fs.existsSync(startScript)) {
    return cb("No start.sh script, make sure your binary artifact has a start.sh script bundled.");
  }

  console.info("Starting binary[%j], config[%j] -- Running startScript[%s].", bundle.binary, bundle.config, startScript);

  out = fs.openSync(path.join(deployDir, 'out.log'), 'a');
  err = fs.openSync(path.join(deployDir, 'out.log'), 'a');
  var child = child_process.spawn(startScript, [], { cwd: workingDir, detached: true, stdio: [ 'ignore', out, err]})
  child.on('error', function(err) { console.error(err); })

  fs.writeFileSync(runningFile, String(child.pid));
  this.watchPid(slot, child.pid, runningFile);

  child.unref();

  this.slots[slot].state = 'RUNNING';
  res.send(200);
  cb();
}

AgentActions.prototype.stop = function(params, bundle, res, cb) {
  var self = this;
  var slot = params.slot;

  var pidFile = this.pidFile(slot, this.currDeployCount(slot));
  if (! fs.existsSync(pidFile)) {
    res.send(200);
    return cb();
  }

  var pid = String(fs.readFileSync(pidFile));
  console.info("Killing pid[%s].", pid);

  shell.exec(util.format("kill %s", pid), {silent:true}, function(exitCode, content) {
    if (exitCode != 0) {
      console.warn("kill pid[%s] failed with code[%s], content[%s]", pid, exitCode, content);
      return cb("Failed to kill process.");
    }

    shell.rm(pidFile);
    self.slots[slot].state = 'STOPPED';
    res.send(200);
    cb();
  });

  setTimeout(
    function(){
      if (self.processRunning(pid)) {
        console.error("pid[%s] still running after kill.  Being a little more persuasive.");
        shell.exec(util.format("kill -9 %s", pid), {silent:true});
        self.slots[slot].state = 'STOPPED';
      }
    },
    2 * 60 * 1000
  ).unref();
}

AgentActions.prototype.show = function(params, bundle, res, cb) {
  res.json(this.slots);
  return cb();
}

AgentActions.prototype.getSlots = function() {
  return this.slots;
}

AgentActions.prototype.tmpDir = function(slot) {
  return path.join(shell.tempdir(), String(slot));
}

AgentActions.prototype.tmpFile = function(slot, file) {
  return path.join(this.tmpDir(slot), file);
}

AgentActions.prototype.currDeployCount = function(slot) {
  if (typeof slot === 'undefined') {
    throw new Error("must specify slot parameter");
  }

  var deployBase = this.deployBase(slot);
  if (fs.existsSync(deployBase)) {
    shell.mkdir('-p', deployBase);
  }

  var deploys = shell.ls(deployBase);
  return Math.max.apply(null, [0].concat(deploys.map(Number)));
}

AgentActions.prototype.deployBase = function(slot) {
  return path.join(this.config.deployDir, String(slot));
}

AgentActions.prototype.deployDir = function(slot, deployCount) {
  return path.join(this.deployBase(slot), String(deployCount));
}

AgentActions.prototype.bundleFile = function(slot, deployCount) {
  return path.join(this.deployDir(slot, deployCount), 'deployed-bundle.json');
}

AgentActions.prototype.pidFile = function(slot, deployCount) {
  return path.join(this.deployDir(slot, deployCount), 'running.pid');
}

AgentActions.prototype.loadFile = function(bucket, keyBase, slot, file, cb) {
  var filename = this.tmpFile(slot, file);
  if (fs.existsSync(filename)) {
    console.info("File[%s] exists, deleting.", filename);
    shell.rm(filename);
  }

  var tmpPath = path.dirname(filename);
  if (! fs.existsSync(tmpPath)) {
    console.info("Making dir[%s].", tmpPath);
    shell.mkdir('-p', tmpPath);
  }

  var outStream = fs.createWriteStream(filename);
  var done = false;
  var s3GetObject = { Bucket: bucket, Key: keyBase + '/' + file };
  console.info("Downloading [%j] to [%s]", s3GetObject, filename);
  this.s3.getObject(s3GetObject)
    .on('httpData', function(chunk) { outStream.write(chunk); })
    .on('httpDone', function() { outStream.end(); if (!done) { done = true; cb(); } })
    .on('error', function(err) {
      outStream.end();
      if (!done) {
        done = true;
        err.message = util.format('%s: %j', err.message, s3GetObject);
        cb(err);
      }
    })
    .send();
}

AgentActions.prototype.expandTar = function(tarFile, outDir, cb) {
  console.info("Expanding [%s] to [%s]", tarFile, outDir);
  child_process.exec(util.format('tar xzf %s', tarFile), {cwd: outDir}, function(err, stdout, stdin) {
    if (err != null) {
      console.error("Unable to untar[%s]:", tarFile, err.message);
      console.log(err.stack);
    }
    return cb(err);
  });
}

AgentActions.prototype.isRunning = function(slot, deployCount) {
  var runningFile = this.pidFile(slot, deployCount);
  if (fs.existsSync(runningFile)) {
    if (! this.cleanPid(slot, String(fs.readFileSync(runningFile)), runningFile)) {
      return true
    }
  }
  return false;
}

AgentActions.prototype.processRunning = function(pid) {
  return shell.exec(util.format("ps %s", pid), {silent: true}).output.indexOf(pid) >= 0
}

AgentActions.prototype.cleanPid = function(slot, pid, pidFile) {
  if ( (!this.processRunning(pid)) && String(fs.readFileSync(pidFile)) === String(pid)) {
    console.warn("Process[%s] with pid[%s] disdappeared...", slot, pid);
    this.slots[slot].state = 'DISDAPPEARED';
    shell.rm(pidFile);
    return true;
  }
  return false;
}

AgentActions.prototype.watchPid = function(slot, pid, pidFile) {
  if (fs.existsSync(pidFile)) {
    if (! this.cleanPid(slot, pid, pidFile)) {
      return setTimeout(this.watchPid.bind(this, slot, pid, pidFile), 60 * 1000).unref();
    }
    console.info("pid[%s] disappeared!", pid);
  }
}

AgentActions.prototype.addListing = function(slot) {
  var currDeploy = this.currDeployCount(slot);

  var bundleFile = this.bundleFile(slot, currDeploy);
  while (! fs.existsSync(bundleFile)) {
    console.warn("Expected bundleFile[%s] to exist, but it didn't.  Deleting.", bundleFile);
    shell.rm('-r', this.deployDir(slot, currDeploy));

    --currDeploy;
    if (currDeploy < 0) {
      var slotDir = this.deployBase(slot);
      console.error("Unable to find usable deploy.  Deleting slot directory[%s]", slotDir);
      shell.rm('-r', slotDir);
      return;
    }
    bundleFile = this.bundleFile(slot, currDeploy);
  }

  var bundle = JSON.parse(fs.readFileSync(bundleFile));
  var _internal = {};

  var pidFile = this.pidFile(slot, currDeploy);
  if (fs.existsSync(pidFile)) {
    _internal.pidFile = pidFile;
    var pid = fs.readFileSync(pidFile)
    if (this.processRunning(pid)) {
      _internal.pid = pid;
      bundle.state = 'RUNNING';
    }
    else {
      bundle.state = 'STOPPED';
    }
  }
  else {
    bundle.state = 'STOPPED';
  }

  bundle._internal = _internal;
  this.slots[slot] = bundle;
}

module.exports = AgentActions;

<commit_msg>When will I learn?<commit_after>/*
 * == TIDEPOOL LICENSE ==
 * Copyright (C) 2013 Tidepool Project
 *
 * This source code is subject to the terms of the Tidepool Open Data License, v. 1.0.
 * If a copy of the license was not provided with this file, you can obtain one at:
 *     http://tidepool.org/license/
 *
 * == TIDEPOOL LICENSE ==
 */

var util = require('util');
var fs = require('fs');
var path = require('path');
var child_process = require('child_process');

var shell = require('shelljs');
var async = require('async');

var fn = require('../fn.js');

var EMPTY_BUNDLE = { binary: { name: '_empty', version: '_empty'},  config: { name: '_empty', version: '_empty'} };

function AgentActions(s3, config) {
  this.s3 = s3;
  this.config = config;
  this.slots = {};

  this.bootstrap();
}

AgentActions.prototype.bootstrap = function()
{
  var deployDir = this.config.deployDir;
  shell.mkdir('-p', deployDir);

  var ids = shell.ls(deployDir);
  ids.forEach(this.addListing.bind(this));
  for (var slot in this.slots) {
    if (this.slots[slot].state === 'RUNNING') {
      var internal = this.slots[slot]._internal;
      this.watchPid(slot, internal.pid, internal.pidFile);
    }
  }
}

AgentActions.prototype.createSlot = function(params, bundle, res, cb) {
  var slot = params.slot;

  var slotDir = this.deployBase(slot);
  if (fs.existsSync(slotDir)) {
    return cb();
  }

  shell.mkdir('-p', path.join(slotDir, '0'));
  fs.writeFileSync(this.bundleFile(slot, 0), JSON.stringify(EMPTY_BUNDLE));
  this.addListing(slot);
  return cb();
}

AgentActions.prototype.deleteSlot = function(params, bundle, res, cb) {
  var slot = params.slot;

  if (this.slots[slot] != null && this.slots[slot].state == 'RUNNING') {
    return cb("Cannot delete slot with a running process, call stop first.");
  }

  var slotDir = this.deployBase(slot);
  if (fs.existsSync(slotDir)) {
    console.info("Deleting slot[%s], removing directory[%s]", slot, slotDir);
    shell.rm('-r', slotDir);
  }
  else {
    console.info("Asked to delete slot[%s], but dir[%s] wasn't there.", slot, slotDir);
  }

  return cb();
}

AgentActions.prototype.load = function(params, bundle, res, cb) {
  var self = this;
  var slot = params.slot;

  console.log("Loading [%j]", bundle);

  var deployCount = this.currDeployCount(slot);
  if (this.isRunning(slot, deployCount)) {
    return cb("Currently running, please stop first.");
  }

  var binaryFilename = util.format('%s-%s.tar.gz', bundle.binary.name, bundle.binary.version);
  var configFilename = util.format('config-%s.tar.gz', bundle.config.version);

  var binaryKeyPath = util.format('deploy/%s', bundle.binary.name);
  var configKeyPath = util.format('config/%s/%s', bundle.binary.name, bundle.config.name);

  var nextDeployCount = deployCount + 1;
  var outDir = this.deployDir(slot, nextDeployCount);

  async.series(
    [
      this.loadFile.bind(this, 'tidepool-binaries', binaryKeyPath , slot, binaryFilename),
      this.loadFile.bind(this, 'tidepool-config', configKeyPath, slot, configFilename),
      fn.asyncify(shell.mkdir.bind(shell, '-p', outDir)),
      this.expandTar.bind(this, this.tmpFile(slot, binaryFilename), outDir),
      this.expandTar.bind(this, this.tmpFile(slot, configFilename), path.join(outDir, path.basename(binaryFilename, '.tar.gz'))),
      fs.writeFile.bind(fs, this.bundleFile(slot, nextDeployCount), JSON.stringify(bundle, null, 2)),
      fn.asyncify(self.addListing.bind(self, slot))
    ],
    function(err, results) {
      var storageDir = self.tmpDir(slot);
      console.info("Removing temporary directory[%s]", storageDir)
      shell.rm('-r', storageDir);

      if (err) {
        return cb(err);
      }
      res.send(200);
      cb();
    }
  );
}

AgentActions.prototype.unload = function(params, bundle, res, cb) {
  var slot = params.slot;

  if (this.slots[slot] == null) {
    return cb();
  }

  if (this.slots[slot].state == 'RUNNING') {
    return cb("Cannot unload running process, call stop first.");
  }

  console.info("Unloading deploy at slot[%s]", slot);

  var deployCount = this.currDeployCount(slot) + 1;
  var deployDir = this.deployDir(slot, deployCount);
  shell.mkdir('-p', deployDir);
  fs.writeFileSync(this.bundleFile(slot, deployCount), JSON.stringify(EMPTY_BUNDLE));
  this.addListing(slot);
  return cb();
}

AgentActions.prototype.start = function(params, bundle, res, cb) {
  var slot = params.slot;
  var deployCount = this.currDeployCount(slot);

  if (deployCount == 0) {
    return cb(util.format("slot[%s] is not deployed.", slot));
  }

  var deployDir = this.deployDir(slot, deployCount);

  var bundle = JSON.parse(fs.readFileSync(this.bundleFile(slot, deployCount)));

  if (this.isRunning(slot, deployCount)) {
    return cb("Already running");
  }

  var workingDir = path.join(deployDir, util.format('%s-%s', bundle.binary.name, bundle.binary.version));
  var startScript = path.join(workingDir, 'start.sh');

  if (! fs.existsSync(startScript)) {
    return cb("No start.sh script, make sure your binary artifact has a start.sh script bundled.");
  }

  console.info("Starting binary[%j], config[%j] -- Running startScript[%s].", bundle.binary, bundle.config, startScript);

  out = fs.openSync(path.join(deployDir, 'out.log'), 'a');
  err = fs.openSync(path.join(deployDir, 'out.log'), 'a');
  var child = child_process.spawn(startScript, [], { cwd: workingDir, detached: true, stdio: [ 'ignore', out, err]})
  child.on('error', function(err) { console.error(err); })

  var runningFile = this.pidFile(slot, deployCount);
  fs.writeFileSync(runningFile, String(child.pid));
  this.watchPid(slot, child.pid, runningFile);

  child.unref();

  this.slots[slot].state = 'RUNNING';
  res.send(200);
  cb();
}

AgentActions.prototype.stop = function(params, bundle, res, cb) {
  var self = this;
  var slot = params.slot;

  var pidFile = this.pidFile(slot, this.currDeployCount(slot));
  if (! fs.existsSync(pidFile)) {
    res.send(200);
    return cb();
  }

  var pid = String(fs.readFileSync(pidFile));
  console.info("Killing pid[%s].", pid);

  shell.exec(util.format("kill %s", pid), {silent:true}, function(exitCode, content) {
    if (exitCode != 0) {
      console.warn("kill pid[%s] failed with code[%s], content[%s]", pid, exitCode, content);
      return cb("Failed to kill process.");
    }

    shell.rm(pidFile);
    self.slots[slot].state = 'STOPPED';
    res.send(200);
    cb();
  });

  setTimeout(
    function(){
      if (self.processRunning(pid)) {
        console.error("pid[%s] still running after kill.  Being a little more persuasive.");
        shell.exec(util.format("kill -9 %s", pid), {silent:true});
        self.slots[slot].state = 'STOPPED';
      }
    },
    2 * 60 * 1000
  ).unref();
}

AgentActions.prototype.show = function(params, bundle, res, cb) {
  res.json(this.slots);
  return cb();
}

AgentActions.prototype.getSlots = function() {
  return this.slots;
}

AgentActions.prototype.tmpDir = function(slot) {
  return path.join(shell.tempdir(), String(slot));
}

AgentActions.prototype.tmpFile = function(slot, file) {
  return path.join(this.tmpDir(slot), file);
}

AgentActions.prototype.currDeployCount = function(slot) {
  if (typeof slot === 'undefined') {
    throw new Error("must specify slot parameter");
  }

  var deployBase = this.deployBase(slot);
  if (fs.existsSync(deployBase)) {
    shell.mkdir('-p', deployBase);
  }

  var deploys = shell.ls(deployBase);
  return Math.max.apply(null, [0].concat(deploys.map(Number)));
}

AgentActions.prototype.deployBase = function(slot) {
  return path.join(this.config.deployDir, String(slot));
}

AgentActions.prototype.deployDir = function(slot, deployCount) {
  return path.join(this.deployBase(slot), String(deployCount));
}

AgentActions.prototype.bundleFile = function(slot, deployCount) {
  return path.join(this.deployDir(slot, deployCount), 'deployed-bundle.json');
}

AgentActions.prototype.pidFile = function(slot, deployCount) {
  return path.join(this.deployDir(slot, deployCount), 'running.pid');
}

AgentActions.prototype.loadFile = function(bucket, keyBase, slot, file, cb) {
  var filename = this.tmpFile(slot, file);
  if (fs.existsSync(filename)) {
    console.info("File[%s] exists, deleting.", filename);
    shell.rm(filename);
  }

  var tmpPath = path.dirname(filename);
  if (! fs.existsSync(tmpPath)) {
    console.info("Making dir[%s].", tmpPath);
    shell.mkdir('-p', tmpPath);
  }

  var outStream = fs.createWriteStream(filename);
  var done = false;
  var s3GetObject = { Bucket: bucket, Key: keyBase + '/' + file };
  console.info("Downloading [%j] to [%s]", s3GetObject, filename);
  this.s3.getObject(s3GetObject)
    .on('httpData', function(chunk) { outStream.write(chunk); })
    .on('httpDone', function() { outStream.end(); if (!done) { done = true; cb(); } })
    .on('error', function(err) {
      outStream.end();
      if (!done) {
        done = true;
        err.message = util.format('%s: %j', err.message, s3GetObject);
        cb(err);
      }
    })
    .send();
}

AgentActions.prototype.expandTar = function(tarFile, outDir, cb) {
  console.info("Expanding [%s] to [%s]", tarFile, outDir);
  child_process.exec(util.format('tar xzf %s', tarFile), {cwd: outDir}, function(err, stdout, stdin) {
    if (err != null) {
      console.error("Unable to untar[%s]:", tarFile, err.message);
      console.log(err.stack);
    }
    return cb(err);
  });
}

AgentActions.prototype.isRunning = function(slot, deployCount) {
  var runningFile = this.pidFile(slot, deployCount);
  if (fs.existsSync(runningFile)) {
    if (! this.cleanPid(slot, String(fs.readFileSync(runningFile)), runningFile)) {
      return true
    }
  }
  return false;
}

AgentActions.prototype.processRunning = function(pid) {
  return shell.exec(util.format("ps %s", pid), {silent: true}).output.indexOf(pid) >= 0
}

AgentActions.prototype.cleanPid = function(slot, pid, pidFile) {
  if ( (!this.processRunning(pid)) && String(fs.readFileSync(pidFile)) === String(pid)) {
    console.warn("Process[%s] with pid[%s] disdappeared...", slot, pid);
    this.slots[slot].state = 'DISDAPPEARED';
    shell.rm(pidFile);
    return true;
  }
  return false;
}

AgentActions.prototype.watchPid = function(slot, pid, pidFile) {
  if (fs.existsSync(pidFile)) {
    if (! this.cleanPid(slot, pid, pidFile)) {
      return setTimeout(this.watchPid.bind(this, slot, pid, pidFile), 60 * 1000).unref();
    }
    console.info("pid[%s] disappeared!", pid);
  }
}

AgentActions.prototype.addListing = function(slot) {
  var currDeploy = this.currDeployCount(slot);

  var bundleFile = this.bundleFile(slot, currDeploy);
  while (! fs.existsSync(bundleFile)) {
    console.warn("Expected bundleFile[%s] to exist, but it didn't.  Deleting.", bundleFile);
    shell.rm('-r', this.deployDir(slot, currDeploy));

    --currDeploy;
    if (currDeploy < 0) {
      var slotDir = this.deployBase(slot);
      console.error("Unable to find usable deploy.  Deleting slot directory[%s]", slotDir);
      shell.rm('-r', slotDir);
      return;
    }
    bundleFile = this.bundleFile(slot, currDeploy);
  }

  var bundle = JSON.parse(fs.readFileSync(bundleFile));
  var _internal = {};

  var pidFile = this.pidFile(slot, currDeploy);
  if (fs.existsSync(pidFile)) {
    _internal.pidFile = pidFile;
    var pid = fs.readFileSync(pidFile)
    if (this.processRunning(pid)) {
      _internal.pid = pid;
      bundle.state = 'RUNNING';
    }
    else {
      bundle.state = 'STOPPED';
    }
  }
  else {
    bundle.state = 'STOPPED';
  }

  bundle._internal = _internal;
  this.slots[slot] = bundle;
}

module.exports = AgentActions;

<|endoftext|>


<commit_before><commit_msg>Update sketch.js<commit_after><|endoftext|>


<commit_before>export default {
  el: {
    colorpicker: {
      confirm: 'Confirmar',
      clear: 'Netejar'
    },
    datepicker: {
      now: 'Ara',
      today: 'Avui',
      cancel: 'Cancel·lar',
      clear: 'Netejar',
      confirm: 'Confirmar',
      selectDate: 'Seleccionar data',
      selectTime: 'Seleccionar hora',
      startDate: 'Data Inici',
      startTime: 'Hora Inici',
      endDate: 'Data Final',
      endTime: 'Hora Final',
      year: 'Any',
      month1: 'Gener',
      month2: 'Febrer',
      month3: 'Març',
      month4: 'Abril',
      month5: 'Maig',
      month6: 'Juny',
      month7: 'Juliol',
      month8: 'Agost',
      month9: 'Setembre',
      month10: 'Octubre',
      month11: 'Novembre',
      month12: 'Desembre',
      // week: 'setmana',
      weeks: {
        sun: 'Dg',
        mon: 'Dl',
        tue: 'Dt',
        wed: 'Dc',
        thu: 'Dj',
        fri: 'Dv',
        sat: 'Ds'
      },
      months: {
        jan: 'Gen',
        feb: 'Febr',
        mar: 'Març',
        apr: 'Abr',
        may: 'Maig',
        jun: 'Juny',
        jul: 'Jul',
        aug: 'Ag',
        sep: 'Set',
        oct: 'Oct',
        nov: 'Nov',
        dec: 'Des'
      }
    },
    select: {
      loading: 'Carregant',
      noMatch: 'No hi ha dades que coincideixin',
      noData: 'Sense Dades',
      placeholder: 'Seleccionar'
    },
    cascader: {
      noMatch: 'No hi ha dades que coincideixin',
      loading: 'Carregant',
      placeholder: 'Seleccionar'
    },
    pagination: {
      goto: 'Anar a',
      pagesize: '/pagina',
      total: 'Total {total}',
      pageClassifier: ''
    },
    messagebox: {
      confirm: 'Acceptar',
      cancel: 'Cancel·lar',
      error: 'Entrada invàlida'
    },
    upload: {
      delete: 'Eliminar',
      preview: 'Vista Prèvia',
      continue: 'Continuar'
    },
    table: {
      emptyText: 'Sense Dades',
      confirmFilter: 'Confirmar',
      resetFilter: 'Netejar',
      clearFilter: 'Tot',
      sumText: 'Sum' // to be translated
    },
    tree: {
      emptyText: 'Sense Dades'
    },
    transfer: {
      noMatch: 'No hi ha dades que coincideixin',
      noData: 'Sense Dades',
      titles: ['List 1', 'List 2'], // to be translated
      filterPlaceholder: 'Enter keyword', // to be translated
      noCheckedFormat: '{total} items', // to be translated
      hasCheckedFormat: '{checked}/{total} checked' // to be translated
    }
  }
};
<commit_msg>Update ca.js<commit_after>export default {
  el: {
    colorpicker: {
      confirm: 'Confirmar',
      clear: 'Netejar'
    },
    datepicker: {
      now: 'Ara',
      today: 'Avui',
      cancel: 'Cancel·lar',
      clear: 'Netejar',
      confirm: 'Confirmar',
      selectDate: 'Seleccionar data',
      selectTime: 'Seleccionar hora',
      startDate: 'Data Inici',
      startTime: 'Hora Inici',
      endDate: 'Data Final',
      endTime: 'Hora Final',
      year: 'Any',
      month1: 'Gener',
      month2: 'Febrer',
      month3: 'Març',
      month4: 'Abril',
      month5: 'Maig',
      month6: 'Juny',
      month7: 'Juliol',
      month8: 'Agost',
      month9: 'Setembre',
      month10: 'Octubre',
      month11: 'Novembre',
      month12: 'Desembre',
      // week: 'setmana',
      weeks: {
        sun: 'Dg',
        mon: 'Dl',
        tue: 'Dt',
        wed: 'Dc',
        thu: 'Dj',
        fri: 'Dv',
        sat: 'Ds'
      },
      months: {
        jan: 'Gen',
        feb: 'Febr',
        mar: 'Març',
        apr: 'Abr',
        may: 'Maig',
        jun: 'Juny',
        jul: 'Jul',
        aug: 'Ag',
        sep: 'Set',
        oct: 'Oct',
        nov: 'Nov',
        dec: 'Des'
      }
    },
    select: {
      loading: 'Carregant',
      noMatch: 'No hi ha dades que coincideixin',
      noData: 'Sense Dades',
      placeholder: 'Seleccionar'
    },
    cascader: {
      noMatch: 'No hi ha dades que coincideixin',
      loading: 'Carregant',
      placeholder: 'Seleccionar'
    },
    pagination: {
      goto: 'Anar a',
      pagesize: '/pagina',
      total: 'Total {total}',
      pageClassifier: ''
    },
    messagebox: {
      confirm: 'Acceptar',
      cancel: 'Cancel·lar',
      error: 'Entrada invàlida'
    },
    upload: {
      delete: 'Eliminar',
      preview: 'Vista Prèvia',
      continue: 'Continuar'
    },
    table: {
      emptyText: 'Sense Dades',
      confirmFilter: 'Confirmar',
      resetFilter: 'Netejar',
      clearFilter: 'Tot',
      sumText: 'Sum' // to be translated
    },
    tree: {
      emptyText: 'Sense Dades'
    },
    transfer: {
      noMatch: 'No hi ha dades que coincideixin',
      noData: 'Sense Dades',
      titles: ['Llista 1', 'Llista 2'],
      filterPlaceholder: 'Introdueix la paraula clau',
      noCheckedFormat: '{total} ítems',
      hasCheckedFormat: '{checked}/{total} seleccionats'
    }
  }
};
<|endoftext|>