diff options
| -rw-r--r-- | css/redirector.css | 87 | ||||
| -rw-r--r-- | js/organizemode.js | 42 | ||||
| -rw-r--r-- | js/redirect.js | 48 | ||||
| -rw-r--r-- | js/redirectorpage.js | 194 | ||||
| -rw-r--r-- | js/util.js | 8 | ||||
| -rw-r--r-- | redirector.html | 25 | 
6 files changed, 351 insertions, 53 deletions
| diff --git a/css/redirector.css b/css/redirector.css index 34ecaa3..71c7d74 100644 --- a/css/redirector.css +++ b/css/redirector.css @@ -114,6 +114,10 @@ input[type="radio"] {    border:solid 1px green;  } +.btn.blue.active { +  color: white; +  background:rgb(21,90,233); +}  #redirect-row-template {    display: none; @@ -235,10 +239,14 @@ button span {    color:red !important;  } -.move-up-btn, .move-down-btn { +/* nav btns */ +.move-up-btn, .move-down-btn, .move-downbottom-btn, .move-uptop-btn {    width:45px !important;  } +.move-downbottom-btn, .move-uptop-btn { +  height:25px !important; +}  .redirect-row label {    display:inline-block; @@ -258,6 +266,65 @@ a.disabled:hover, button[disabled]:hover {    background:white !important;  } +/* Toggle Grouping Checkbox */ +.toggle-container { +  display: block; +  position: absolute; +  top: 8%; +  right: 5%; +  cursor: pointer; +  font-size: 22px; +  -webkit-user-select: none; +  -moz-user-select: none; +  -ms-user-select: none; +  user-select: none; +} + +.toggle-container input { +  position: absolute; +  opacity: 0; +  cursor: pointer; +  height: 0; +  width: 0; +} + +.checkmark { +  position: absolute; +  height: 15px; +  width: 15px; +  background-color: #eee; +} + +.toggle-container:hover input ~ .checkmark { +  background-color: #ccc; +} + +.toggle-container input:checked ~ .checkMarked { +  background-color: #2196F3; +} + +.checkmark:after { +  content: ""; +  position: absolute; +  display: none; +} + +.toggle-container input:checked ~ .checkMarked:after { +  display: block; +} + +.toggle-container .checkMarked:after { +  left: 4px; +  top: -1px; +  width: 4px; +  height: 9px; +  border: solid white; +  border-width: 0 3px 3px 0; +  -webkit-transform: rotate(45deg); +  -ms-transform: rotate(45deg); +  transform: rotate(45deg); +} +  /* Popup form for deleting redirects */  #delete-redirect-form { @@ -312,8 +379,8 @@ a.disabled:hover, button[disabled]:hover {  }  .blur { -  -webkit-filter:blur(3px);  -  filter:blur(3px);  +  -webkit-filter:blur(3px); +  filter:blur(3px);  }  #edit-redirect-form { @@ -353,7 +420,7 @@ a.disabled:hover, button[disabled]:hover {  .input-cell {    padding-top:1px; -}  +}  .form-grid div input[type='text'] {    width:510px; @@ -404,7 +471,7 @@ a.disabled:hover, button[disabled]:hover {  }  ::-moz-placeholder {  /* Firefox 19+ */ -   color: #c0c0c0;   +   color: #c0c0c0;  }  .advanced { @@ -509,10 +576,10 @@ footer small a:hover {    .redirect-row:nth-child(odd) {      background: rgb(31,32,35); -  }  +  }    .redirect-row:nth-child(even) {      background: rgb(41,42,45); -  }  +  }    .btn {      background-color: rgb(32,33,36); @@ -527,6 +594,10 @@ footer small a:hover {      color: rgb(53,180,75);    } +  .toggle { +    background-color: #ccc; +  } +    #message-box.success {      background-color: rgb(53,203,75);;    } @@ -590,7 +661,7 @@ footer small a:hover {    }    .example-result-error { -    color:rgb(252,87,84) !important;  +    color:rgb(252,87,84) !important;    }    #edit-redirect-form input { diff --git a/js/organizemode.js b/js/organizemode.js new file mode 100644 index 0000000..800a53f --- /dev/null +++ b/js/organizemode.js @@ -0,0 +1,42 @@ + +function displayOrganizeModeMessage() { +    if(el('#message-box').classList.contains('visible')) { +        hideMessage(); +    } else { +        showMessage("Use ⟱ to move a redirect to the bottom, ⟰ to move to the top, and use the checkboxes to select multiple redirects and move them together.", true) +    } +} + +function organizeModeToggle(ev) { +    ev.preventDefault(); +    let organizeModes = ['.groupings', '.arrows'] +    for (let mode of organizeModes) { +        let organizeModeElms = document.querySelectorAll(mode); +        for (i = 0; i < organizeModeElms.length; ++i) { +            let elm = organizeModeElms[i]; +            let isHidden = ''; +            if(mode === '.arrows') { +                // targeting parent span for arrows +                elm = elm.parentElement; +            } +            isHidden = elm.classList.contains('hidden'); +            isHidden ? elm.classList.remove('hidden') : elm.classList.add('hidden'); +        } +    } + +    // let buttonText = el('#organize-mode').textContent; +    // buttonText.includes('Show') ? el('#organize-mode').textContent = 'Hide Organize' : el('#organize-mode').textContent = 'Show Organize'; + +    let buttonClasses = el('#organize-mode').classList; +    console.log('NSC: organizeModeToggle -> buttonClasses', buttonClasses); +    !buttonClasses.contains('active') ? el('#organize-mode').classList.add('active') : el('#organize-mode').classList.remove('active'); + +    displayOrganizeModeMessage(); +} + + +function setupOrganizeModeToggleEventListener() { +    el('#organize-mode').addEventListener('click', organizeModeToggle); +} + +setupOrganizeModeToggleEventListener();
\ No newline at end of file diff --git a/js/redirect.js b/js/redirect.js index 4ad57d5..b902da2 100644 --- a/js/redirect.js +++ b/js/redirect.js @@ -27,7 +27,7 @@ Redirect.requestTypes = {  Redirect.prototype = { -	 +  	//attributes  	description : '',  	exampleUrl : '', @@ -40,7 +40,8 @@ Redirect.prototype = {  	patternType : '',  	processMatches : 'noProcessing',  	disabled : false, -	 +	grouped: false, +  	compile : function() {  		var incPattern = this._preparePattern(this.includePattern); @@ -65,7 +66,7 @@ Redirect.prototype = {  			&& this.processMatches == redirect.processMatches  			&& this.appliesTo.toString() == redirect.appliesTo.toString();  	}, -	 +  	toObject : function() {  		return {  			description : this.description, @@ -79,6 +80,7 @@ Redirect.prototype = {  			patternType : this.patternType,  			processMatches : this.processMatches,  			disabled : this.disabled, +			grouped: this.grouped,  			appliesTo : this.appliesTo.slice(0)  		};  	}, @@ -87,10 +89,10 @@ Redirect.prototype = {  		if (!this._rxInclude) {  			this.compile();  		} -		var result = {  -			isMatch : false,  -			isExcludeMatch : false,  -			isDisabledMatch : false,  +		var result = { +			isMatch : false, +			isExcludeMatch : false, +			isDisabledMatch : false,  			redirectTo : '',  			toString : function() { return JSON.stringify(this); }  		}; @@ -106,11 +108,11 @@ Redirect.prototype = {  				result.redirectTo = redirectTo;  			}  		} -		return result;	  +		return result;  	}, -	 +  	//Updates the .exampleResult field or the .error -	//field depending on if the example url and patterns match  +	//field depending on if the example url and patterns match  	//and make a good redirect  	updateExampleResult : function() { @@ -171,25 +173,25 @@ Redirect.prototype = {  	isRegex: function() {  		return this.patternType == Redirect.REGEX;  	}, -	 +  	isWildcard : function() { -		return this.patternType == Redirect.WILDCARD;	 +		return this.patternType == Redirect.WILDCARD;  	},  	test : function() { -		return this.getMatch(this.exampleUrl);	 +		return this.getMatch(this.exampleUrl);  	}, -	//Private functions below	 +	//Private functions below  	_rxInclude : null,  	_rxExclude : null, -	 +  	_preparePattern : function(pattern) {  		if (!pattern) {  			return null;  		}  		if (this.patternType == Redirect.REGEX) { -			return pattern;  +			return pattern;  		} else { //Convert wildcard to regex pattern  			var converted = '^';  			for (var i = 0; i < pattern.length; i++) { @@ -206,7 +208,7 @@ Redirect.prototype = {  			return converted;  		}  	}, -	 +  	_init : function(o) {  		o = o || {};  		this.description = o.description || ''; @@ -240,7 +242,7 @@ Redirect.prototype = {  	get appliesToText() {  		return this.appliesTo.map(type => Redirect.requestTypes[type]).join(', ');  	}, -	 +  	get processMatchesExampleText() {  		let examples = {  			noProcessing : 'Use matches as they are', @@ -256,11 +258,11 @@ Redirect.prototype = {  	toString : function() {  		return JSON.stringify(this.toObject(), null, 2);  	}, -	 +  	_includeMatch : function(url) {  		if (!this._rxInclude) {  			return null; -		}	 +		}  		var matches = this._rxInclude.exec(url);  		if (!matches) {  			return null; @@ -285,12 +287,12 @@ Redirect.prototype = {  		this._rxInclude.lastIndex = 0;  		return resultUrl;  	}, -	 +  	_excludeMatch : function(url) {  		if (!this._rxExclude) { -			return false;	 +			return false;  		} -		var shouldExclude = this._rxExclude.test(url);	 +		var shouldExclude = this._rxExclude.test(url);  		this._rxExclude.lastIndex = 0;  		return shouldExclude;  	} diff --git a/js/redirectorpage.js b/js/redirectorpage.js index f82b0b7..959b73c 100644 --- a/js/redirectorpage.js +++ b/js/redirectorpage.js @@ -23,15 +23,15 @@ function saveChanges() {  		}  	});  } -		 +  function toggleSyncSetting() {  	chrome.runtime.sendMessage({type:"toggle-sync", isSyncEnabled: !options.isSyncEnabled}, function(response) {  		if(response.message === "sync-enabled"){  			options.isSyncEnabled = true; -			showMessage('Sync is enabled!',true); +			showMessage('Sync is enabled!', true);  		} else if(response.message === "sync-disabled"){  			options.isSyncEnabled = false; -			showMessage('Sync is disabled - local storage will be used!',true); +			showMessage('Sync is disabled - local storage will be used!', true);  		} else if(response.message.indexOf("Sync Not Possible")>-1){  			options.isSyncEnabled = false;  			chrome.storage.local.set({isSyncEnabled: $s.isSyncEnabled}, function(){ @@ -60,16 +60,16 @@ function renderRedirects() {  }  function renderSingleRedirect(node, redirect, index) { -	 +  	//Add extra props to help with rendering...  	if (index === 0) {  		redirect.$first = true;  	}  	if (index === REDIRECTS.length - 1) {  		redirect.$last = true; -	}  +	}  	redirect.$index = index; -	 +  	dataBind(node, redirect);  	node.setAttribute('data-index', index); @@ -77,6 +77,12 @@ function renderSingleRedirect(node, redirect, index) {  		btn.setAttribute('data-index', index);  	} +	let checkmark = node.querySelectorAll('.checkmark'); + +	if(checkmark.length == 1) { +		checkmark[0].setAttribute('data-index', index); +	} +  	//Remove extra props...  	delete redirect.$first;  	delete redirect.$last; @@ -117,19 +123,170 @@ function toggleDisabled(index) {  	saveChanges();  } +// function handleGroupedMove(index) { +//     console.log(`NSC`, arguments); +// 	let grouping = REDIRECTS.filter(row => row.grouped); +// 	let size = grouping.length; +// 	console.log(grouping) +// } + +// function wrap(node, elm) { +// 	node.parentNode.insertBefore(elm, node); +// 	// node.previousElementSibling.appendChild(node); +// } + +function swap(node1, node2) { +    const afterNode2 = node2.nextElementSibling; +    const parent = node2.parentNode; +    node1.replaceWith(node2); +    parent.insertBefore(node1, afterNode2); +} + +function groupedMoveDown(group) { +	// let grouping = REDIRECTS.map((row, i) => { return { row, index: i}}) +	// 								.filter(result => result.row.grouped) +	// 								.sort((a, b) => b.index - a.index); +		for(let rule of group) { +			// swap positions in dom +			let elm = document.querySelector("[data-index='" + (rule.index).toString() + "']"); +			let prev = document.querySelector("[data-index='" + (rule.index + group.length).toString() + "']"); +			swap(elm,prev); +		} + +		for(let rule of group) { +			// swap positions in array +			rule.row.grouping = false; +			let prevRedir = REDIRECTS[rule.index + group.length]; +			REDIRECTS[rule.index + group.length] = REDIRECTS[rule.index]; +			REDIRECTS[rule.index] = prevRedir; +			// REDIRECTS[rule.index].grouped = false; +		} +} + +function isGroupAdjacent(grouping) { +	let distances = []; +	for(let i = grouping.length - 1; i >= 0; i--) { + +		if(i != 0) { +			distances.push(grouping[i].index - grouping[i - 1].index); + +		} + +	} +	return distances.every(distance => distance === 1); +} + +function groupedMoveUp(group) { + + +	console.log(group); + +	// working config - sort of +	// for(let rule of group) { +	// 	// swap positions in dom +	// 	let elm = document.querySelector("[data-index='" + (rule.index).toString() + "']"); +	// 	let prev = document.querySelector("[data-index='" + (rule.index - group.length).toString() + "']"); +	// 	elm.childNodes[7].childNodes[3].classList.remove("checkMarked"); +    //     console.log(`NSC: groupedMoveUp -> elm`, elm); +	// 	prev.childNodes[7].childNodes[3].classList.remove("checkMarked"); +    //     console.log(`NSC: groupedMoveUp -> prev`, prev); +	// 	swap(elm,prev); +	// } + +	// for(let rule of group) { +	// 	// swap positions in array +	// 	rule.row.grouping = false; +	// 	let prevRedir = REDIRECTS[rule.index - group.length]; +	// 	REDIRECTS[rule.index - group.length] = REDIRECTS[rule.index]; +	// 	REDIRECTS[rule.index] = prevRedir; +	// 	// REDIRECTS[rule.index].grouped = false; +	// } + +	// only set the below to 1 if groupings are not next to each other. +	var jumpLength = 1; + +	if(isGroupAdjacent(group)) { +		console.log('adjacent') +		jumpLength = group.length; +	} + + + + +	for(let rule of group) { +        console.log(`NSC: groupedMoveUp -> rule`, rule); +		// swap positions in dom +		let elm = document.querySelector("[data-index='" + (rule.index).toString() + "']"); +        console.log(`NSC: groupedMoveUp -> elm`, elm); +		let prev = document.querySelector("[data-index='" + (rule.index - jumpLength).toString() + "']"); +        console.log(`NSC: groupedMoveUp -> prev`, prev); +		elm.childNodes[7].childNodes[3].classList.remove("checkMarked"); +		prev.childNodes[7].childNodes[3].classList.remove("checkMarked"); +		if(jumpLength > 1) { +			swap(elm,prev); +		} + +	} +	for(let rule of group) { +		// swap positions in array +		rule.row.grouping = false; +		let prevRedir = REDIRECTS[rule.index - jumpLength]; +		REDIRECTS[rule.index - jumpLength] = REDIRECTS[rule.index]; +		REDIRECTS[rule.index] = prevRedir; +	} +}  function moveUp(index) { -	let prev = REDIRECTS[index-1]; -	REDIRECTS[index-1] = REDIRECTS[index]; -	REDIRECTS[index] = prev; +	let grouping = REDIRECTS.map((row, i) => { return { row, index: i}}) +									.filter(result => result.row.grouped) +									.sort((a, b) => a.index - b.index); + +	if(grouping.length > 1) { +		// many +		console.log('many') + +		groupedMoveUp(grouping); + +	} else { +		// one +		let prev = REDIRECTS[index-1]; +		REDIRECTS[index-1] = REDIRECTS[index]; +		REDIRECTS[index] = prev; +	} +  	updateBindings();  	saveChanges();  }  function moveDown(index) { -	let next = REDIRECTS[index+1]; -	REDIRECTS[index+1] = REDIRECTS[index]; -	REDIRECTS[index] = next; +	let grouping = REDIRECTS.map((row, i) => { return { row, index: i}}) +									.filter(result => result.row.grouped) +									.sort((a, b) => a.index - b.index); + +	if(grouping.length > 1) { +		// many +		groupedMoveDown(grouping); +	} else { +		// one +		let next = REDIRECTS[index+1]; +		REDIRECTS[index+1] = REDIRECTS[index]; +		REDIRECTS[index] = next; +	} + +	updateBindings(); +	saveChanges(); +} + +function moveUpTop(index) { +	let top = REDIRECTS[0]; +	move(REDIRECTS, index, top); +	updateBindings(); +	saveChanges(); +} + +function moveDownBottom(index) { +	let bottom = REDIRECTS.length - 1; +	move(REDIRECTS, index, bottom);  	updateBindings();  	saveChanges();  } @@ -138,7 +295,7 @@ function moveDown(index) {  function pageLoad() {  	template = el('#redirect-row-template');  	template.parentNode.removeChild(template); -	 +  	//Need to proxy this through the background page, because Firefox gives us dead objects  	//nonsense when accessing chrome.storage directly.  	chrome.runtime.sendMessage({type: "get-redirects"}, function(response) { @@ -169,7 +326,7 @@ function pageLoad() {  			));  		}  		renderRedirects(); -	});  +	});  	chrome.storage.local.get({isSyncEnabled:false}, function(obj){  		options.isSyncEnabled = obj.isSyncEnabled; @@ -186,6 +343,11 @@ function pageLoad() {  	el('#storage-sync-option input').addEventListener('click', toggleSyncSetting);  	el('.redirect-rows').addEventListener('click', function(ev) { +		// apply checkMarked class for Grouping +		if(ev.target.type == 'checkbox') { +			ev.target.nextElementSibling.classList.add("checkMarked"); +		} +  		let action = ev.target.getAttribute('data-action');  		//We clone and re-use nodes all the time, so instead of attaching and removing event handlers endlessly we just put @@ -214,4 +376,8 @@ let mql = window.matchMedia('(prefers-color-scheme:dark)');  mql.onchange = updateFavicon;  updateFavicon(mql); +function toggleGrouping(index) { +	REDIRECTS[index].grouped = !REDIRECTS[index].grouped; +} +  pageLoad();
\ No newline at end of file @@ -36,12 +36,12 @@ function dataBind(el, dataObject) {  		}  	}  	for (let tag of el.querySelectorAll('[data-show]')) { -		let shouldShow = boolValue(tag.getAttribute('data-show'));	 +		let shouldShow = boolValue(tag.getAttribute('data-show'));  		tag.style.display = shouldShow ? '' : 'none';  	}  	for (let tag of el.querySelectorAll('[data-disabled]')) {  		let isDisabled = boolValue(tag.getAttribute('data-disabled')); -		 +  		if (isDisabled) {  			tag.classList.add('disabled');  			tag.setAttribute('disabled', 'disabled'); @@ -82,6 +82,10 @@ function showForm(selector, dataObject) {  	show(selector);  } +function move(arr, from, to) { +    arr.splice(to, 0, arr.splice(from, 1)[0]); +} +  function hideForm(selector) {  	hide('#cover');  	hide(selector); diff --git a/redirector.html b/redirector.html index af11b6b..d2fbf9c 100644 --- a/redirector.html +++ b/redirector.html @@ -10,7 +10,7 @@  	<body>  		<div id="cover">  		</div> -		 +  		<!-- Confirmation form for deleting redirects -->  		<div id="delete-redirect-form">  			<h3>Are you sure you want to delete this redirect?</h3> @@ -39,7 +39,7 @@  				<button id="cancel-delete" class="btn grey large">No, don't delete it</button>  			</div>  		</div> -		 +  		<!-- Form for creating and editing redirects -->  		<div id="edit-redirect-form"> @@ -124,20 +124,21 @@  		<div id="blur-wrapper"> -			 +  			<h1>REDIRECTOR</h1>  			<h5>Go where <em>YOU</em> want!</h5>  			<div id="menu">  				<a id="create-new-redirect" class="btn blue large">Create new redirect</a> -				 +  				<!-- Importing/Exporting of redirects -->  				<span>  					<input type="file" id="import-file" accept=".rjson,.json,.txt"  />  					<label for="import-file" class="btn blue large">Import</label>  					<a class="btn blue large" id="export-link" download="Redirector.json">Export</a> +					<button class="btn blue large" id="organize-mode">Organize</button>  				</span> -	 +  				<a class="btn blue large" href="help.html" target="_blank">Help</a>  			</div> @@ -177,10 +178,21 @@  								<button class="btn medium blue" data-action="toggleDisabled"><span data-show="disabled">Enable</span><span data-show="!disabled">Disable</span></button>  								<button class="btn medium green" data-action="editRedirect">Edit</button>  								<button class="btn medium red" data-action="confirmDeleteRedirect">Delete</button> +								<span class="hidden"> +									<button class="btn medium grey move-uptop-btn arrows" data-action="moveUpTop" data-disabled="$first">⟰</button> +								</span> +  								<button class="btn medium grey move-up-btn" data-action="moveUp" data-disabled="$first">▲</button>  								<button class="btn medium grey move-down-btn" data-action="moveDown" data-disabled="$last">▼</button> +								<span class="hidden"> +									<button class="btn medium grey move-downbottom-btn arrows" data-action="moveDownBottom" data-disabled="$last">⟱</button> +								</span>  								<button class="btn medium grey" data-action="duplicateRedirect">Duplicate</button>  							</div> +							<label class="toggle-container"> +								<input type="checkbox"> +								<span class="checkmark groupings hidden" data-action="toggleGrouping"></span> +							</label>  						</div>  					</div>  				<label id="storage-sync-option"><input type="checkbox" /> Enable Storage Sync</label> @@ -189,7 +201,7 @@  			<footer>  				<small>Redirector is created by <a target="_blank" href="http://einaregilsson.com">Einar Egilsson</a></small>  			</footer> -		 +  		</div>  		<script src="js/stub.js"></script> @@ -198,5 +210,6 @@  		<script src="js/redirectorpage.js"></script>  		<script src="js/editredirect.js"></script>  		<script src="js/importexport.js"></script> +		<script src="js/organizemode.js"></script>  	</body>  </html> | 
