Add material design and settings page.

Squashed commit of the following:

commit 8eaac39b94663e32659e41dde211a197cfdf567f
Author: Nils Norman Haukås <nils@thunki.com>
Date:   Sun Aug 7 13:46:56 2016 +0200

    Prepare new feature for plugification

    Menu.tpl.html now references chrome plugin file resources for material design.
    Adjusted build step to copy material design styles and such. Whitelisted web accessible resources in
    manifest.json. Bump version number.

commit a2662042904ee69613b75fc1bebb188c9783f414
Author: Nils Norman Haukås <nils@thunki.com>
Date:   Sun Aug 7 12:54:27 2016 +0200

    Use parameter properties

commit 888e3511212eca705585651f4d9286502b5c05b6
Author: Nils Norman Haukås <nils@thunki.com>
Date:   Sun Aug 7 12:45:51 2016 +0200

    Post tags to configurable endpoint

    User can now specify the url where tags should be posted.

commit f4adda15223bba3044184d4a0780387051828958
Author: Nils Norman Haukås <nils@thunki.com>
Date:   Sun Aug 7 07:06:30 2016 +0200

    Rename to settings service

commit 5646a8dabab740267181d0d87ec369bbdabe42e5
Author: Nils Norman Haukås <nils@thunki.com>
Date:   Sun Aug 7 07:02:36 2016 +0200

    Reworked settings

    Replaced index.Appconfig.ts with appConfig.service. This paves way for an adjustable settingspage.

commit 63c7d5bfa2029702ba5e4eafa462436f5e547afa
Author: Nils Norman Haukås <nils@thunki.com>
Date:   Sun Aug 7 07:01:57 2016 +0200

    Update angular.js version to 1.5.8

commit bcee26ccda036abdb90a98121dab3bd1e0e39fcc
Author: Nils Norman Haukås <nils@thunki.com>
Date:   Thu Jul 14 22:00:26 2016 +0200

    removed browsersync notification

commit 71b0c3a1c735c1b2293c21a756f0bf05f85ccacc
Author: Nils Norman Haukås <nils@thunki.com>
Date:   Thu Jul 14 21:58:12 2016 +0200

    Added spinner, started settings page

commit fd0b16a146c7f633f354ed4f8c69754d9b66cfdd
Author: Nils Norman Haukås <nils@thunki.com>
Date:   Mon Jun 27 21:50:50 2016 +0200

    Include material-design-lite, started building settings page.
This commit is contained in:
Nils Norman Haukås 2016-08-07 13:49:33 +02:00
parent 606f1a6bd2
commit 1dc2c1a247
16 changed files with 301 additions and 137 deletions

View file

@ -3,8 +3,12 @@
# Note: The javascript is generated by the
# package.json before calling this script.
mkdir dist #ensure there's a folder
#copy styles
cp -v src/app/style.css dist/
cp -v node_modules/material-design-lite/dist/material.css dist/
cp -v node_modules/material-design-lite/dist/material.js dist/
#copy html menu
cp -v src/app/menu/menu.tpl.html dist/

View file

@ -14,7 +14,7 @@ System.config({
},
map: {
"angular": "github:angular/bower-angular@1.5.5",
"angular": "github:angular/bower-angular@1.5.8",
"jquery": "npm:jquery@2.2.4",
"lodash": "npm:lodash@4.12.0",
"ngstorage": "npm:ngstorage@0.3.10",

View file

@ -6,7 +6,7 @@
"scripts": {
"clean": "rm -r dist",
"build": "jspm bundle-sfx src/app/main.ts dist/app.js && ./build.sh",
"start": "browser-sync start -b google-chrome --startPath test/ --server --files 'src/**/*'",
"start": "browser-sync start --no-notify -b google-chrome --startPath test/ --server --files 'src/**/*'",
"postinstall": "typings install && jspm install"
},
"repository": {
@ -28,11 +28,12 @@
"dependencies": {
"browser-sync": "^2.9.6",
"jspm": "^0.16.39",
"material-design-lite": "^1.1.3",
"typings": "^1.3.0"
},
"jspm": {
"dependencies": {
"angular": "github:angular/bower-angular@^1.5.5",
"angular": "github:angular/bower-angular@^1.5.8",
"jquery": "npm:jquery@^2.2.4",
"lodash": "npm:lodash@^4.12.0",
"ngstorage": "npm:ngstorage@^0.3.10",

View file

@ -1,9 +0,0 @@
export function AppConfigInitializer ($logProvider: ng.ILogProvider) {
$logProvider.debugEnabled(true);
}
export class AppConfigService {
serverUrl = 'https://imdb.uib.no/lexitags/lexitags/';
}

View file

@ -5,9 +5,10 @@ import {
BackendService,
WebPageService,
TagStorageService,
FileService} from './services/index';
import {AppConfigInitializer, AppConfigService} from "./index.appConfig";
FileService,
SettingsService} from './services/index';
import {MenuCtrl} from './menu/menu.controller';
import {SettingsCtrl} from './menu/settings.controller';
import {preparePage} from './pageRebuilder';
import angular from 'angular';
import 'rangy';
@ -25,20 +26,13 @@ else {
function loadAngular() {
angular.module('tagit', ['ngStorage'])
.config(AppConfigInitializer)
.service('AppConfigService', AppConfigService)
.service('SettingsService', SettingsService)
.service('BackendService', BackendService)
.service('WebPageService', WebPageService)
.service('TagStorageService', TagStorageService)
.service('FileService', FileService)
.controller('SettingsCtrl', SettingsCtrl)
.controller('MenuCtrl', MenuCtrl)
.controller('TestCtrl', function () {
var vm = this;
vm.test = 'hello world';
vm.alert = function () {
alert('test alert');
}
});
angular.bootstrap(
(<HTMLIFrameElement>document.getElementById("tagit-iframe"))

View file

@ -13,26 +13,15 @@ export class MenuCtrl {
selectedWord = "";
senses: Object[];
backendService: BackendService;
webPageService: WebPageService;
tagStorageService: TagStorageService;
fileService: FileService;
$log: ng.ILogService;
$scope: ng.IScope;
/* @ngInject */
constructor($scope: IVMScope, $log: angular.ILogService,
BackendService: BackendService,
WebPageService: WebPageService,
TagStorageService: TagStorageService,
FileService: FileService) {
constructor(
private $scope: IVMScope,
private $log: angular.ILogService,
private BackendService: BackendService,
private WebPageService: WebPageService,
private TagStorageService: TagStorageService,
private FileService: FileService) {
$scope.vm = this;
this.$log = $log;
this.$scope = $scope;
this.backendService = BackendService;
this.webPageService = WebPageService;
this.tagStorageService = TagStorageService;
this.fileService = FileService;
this.$scope.$on('wordWasSelected', (event, selectedWord) => {
this.$log.debug(`Menucontroller received wordWasSelected event for: ${selectedWord}`);
@ -45,12 +34,12 @@ export class MenuCtrl {
});
// Reload existing tags
var tagsToLoad = this.tagStorageService.loadTagsForCurrentPage();
var tagsToLoad = this.TagStorageService.loadTagsForCurrentPage();
this.$log.debug('these tags were found in storage');
this.$log.debug(tagsToLoad);
this.webPageService.readdTagsToPage(tagsToLoad);
this.WebPageService.readdTagsToPage(tagsToLoad);
}
/**
@ -60,17 +49,17 @@ export class MenuCtrl {
onSenseSelect(sense: ISense) {
//remove all tags so that new tag range is serialized
//based on a document without any highlights
this.webPageService.removeAllTagsFromPage(() => {
this.WebPageService.removeAllTagsFromPage(() => {
//initialize and save the new tag
var senseTag = this.webPageService.initializeNewTag(sense);
this.tagStorageService.saveTag(senseTag);
var senseTag = this.WebPageService.initializeNewTag(sense);
this.TagStorageService.saveTag(senseTag);
// deactivate backendService for now.
// this.backendService.sendTaggedDataToServer(senseTag);
// deactivate BackendService for now.
this.BackendService.sendTaggedDataToServer(senseTag);
//re-add tags, with new tag. Clear menu options.
this.webPageService.readdTagsToPage(
this.tagStorageService.loadTagsForCurrentPage()
this.WebPageService.readdTagsToPage(
this.TagStorageService.loadTagsForCurrentPage()
);
this.clearMenuVariables();
});
@ -84,7 +73,7 @@ export class MenuCtrl {
this.$log.debug('Did not find chrome facilities. Can\'t download.')
return; //do nothing
}
this.fileService.saveFile(this.tagStorageService.loadTagsForCurrentPage());
this.FileService.saveFile(this.TagStorageService.loadTagsForCurrentPage());
}
downloadAllTagsForDomain() {
@ -92,7 +81,7 @@ export class MenuCtrl {
this.$log.debug('Did not find chrome facilities. Can\'t download.')
return; //do nothing
}
this.fileService.saveFile(this.tagStorageService.loadAllTagsInLocalStorage());
this.FileService.saveFile(this.TagStorageService.loadAllTagsInLocalStorage());
}
/**
@ -100,23 +89,24 @@ export class MenuCtrl {
*/
removeTagsFromLocalStorage() {
if (confirm('Really delete tags from the current page?')) {
this.webPageService.removeAllTagsFromPage(() => {
this.tagStorageService.deleteTagsFromCurrentPage()
this.WebPageService.removeAllTagsFromPage(() => {
this.TagStorageService.deleteTagsFromCurrentPage()
})
}
}
onWordSelectedEvent = (newWord: string) => {
if (countWords(newWord) > 2) {
this.selectedWord = "Wops! Plugin can't handle more than two words.";
this.senses = [];
this.selectedWord = "Wops! Plugin can't handle more than two words."
this.senses = []
}
else if (newWord.length === 0) {
this.clearMenuVariables();
}
else {
this.selectedWord = newWord;
this.backendService.callServer(newWord)
this.senses = []
this.BackendService.callServer(newWord)
.then((synsets: any) => {
this.$log.debug(synsets);
this.senses = synsets.data.senses;
@ -138,4 +128,9 @@ export class MenuCtrl {
this.senses = [];
}
isLoadingSenses() {
// if senses var has initialized, we check length to see if they've been loaded.
return this.senses && this.senses.length == 0 && this.selectedWord
}
}

View file

@ -3,9 +3,19 @@
<head>
<title></title>
<style>
<!--Will only work in dev-->
<link href="/node_modules/material-design-lite/dist/material.css" rel="stylesheet">
<script src="/node_modules/material-design-lite/dist/material.js"></script>
<!--
Will only work when deployed as a chrome plugin
more info: https://developer.chrome.com/extensions/overview#relative-urls
-->
<link href="chrome-extension://lpjhabnmodhcjhfdlfahkncecambemoi/material.css" rel="stylesheet">
<script src="chrome-extension://lpjhabnmodhcjhfdlfahkncecambemoi/material.js"></script>
<style>
/*
This styling is embedded due to the test setup.
When used as a Chrome plugin the main style can be
@ -16,7 +26,7 @@
So all the styles necessary to style the internals
of this menu is included here instead of the main style.css.
*/
.tagit-menu {
background-color: #FFF;
}
@ -35,37 +45,92 @@
cursor: pointer;
}
</style>
</head>
<body id="tagit-menu" class="tagit-menu">
<div ng-controller="MenuCtrl">
<h2>Tag you're it</h2>
<p>Menu:
<a href ng-click="vm.downloadTagsForPage()">Download tags (current page)</a> |
<a href ng-click="vm.downloadAllTagsForDomain()">Download all tags (domain)</a> |
<a href ng-click="vm.removeTagsFromLocalStorage()">Delete tags from current page</a>
</p>
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header mdl-layout--fixed-tabs">
<p>
Mark one or two words on the page to the right. And we'll supply you with possible definitions. Select a definition and the
word will be tagged.
</p>
<header class="mdl-layout__header">
<div class="mdl-layout__tab-bar mdl-js-ripple-effect">
<a href="#main-panel" class="mdl-layout__tab is-active">Tag you're it</a>
<a href="#settings-panel" class="mdl-layout__tab">Settings</a>
</div>
</header>
<p>
Currently selected word: <strong>{{vm.selectedWord}}</strong>
</p>
<div class="mdl-layout__content">
<input type="text" ng-model="wordToFilterBy" class="form-control" placeholder="Filter tags" />
<div ng-controller="MenuCtrl" class="mdl-layout__tab-panel mdl-grid is-active" id="main-panel">
<ul id="senses">
<li ng-click="vm.onSenseSelect(sense)" id="sense.senseid" ng-repeat="sense in vm.senses | filter:wordToFilterBy" class="list-unstyled">
<strong>{{sense.word}}</strong> {{sense.explanation}}
</li>
</ul>
<br>
<p>Actions:
<a href ng-click="vm.downloadTagsForPage()">Download tags (current page)</a> |
<a href ng-click="vm.downloadAllTagsForDomain()">Download all tags (domain)</a> |
<a href ng-click="vm.removeTagsFromLocalStorage()">Delete tags from current page</a>
</p>
<p>
Mark one or two words on the page to the right. And we'll supply you with possible definitions. Select a definition and the
word will be tagged.
</p>
<p ng-if="vm.selectedWord">
Currently selected word: <strong>{{vm.selectedWord}}</strong>
</p>
<p ng-if="!vm.selectedWord">
Currently selected word: <strong>No word selected.</strong>
</p>
<center>
<div ng-if="vm.isLoadingSenses()" class="is-active mdl-spinner mdl-spinner--single-color mdl-js-spinner"></div>
</center>
<ul id="senses">
<li ng-if="vm.senses.length > 0">
<strong>Available sense tags:</strong>
</li>
<li ng-click="vm.onSenseSelect(sense)" id="sense.senseid" ng-repeat="sense in vm.senses" class="list-unstyled">
<strong>{{sense.word}}</strong> {{sense.explanation}}
</li>
</ul>
</div>
<div ng-controller="SettingsCtrl" class="mdl-layout__tab-panel mdl-grid" id="settings-panel">
<br>
<p>Changes will be automatically saved.</p>
<form action="">
<p>
<strong><label for="backend-url">Tag destination url:</label></strong>
<input id="backend-url" type="text" ng-model="vm.serverToSendTo">
<br>
As you go about tagging pages the tags will be posted to this server endpoint.
</p>
<p>
<strong><label for="tag-email">Tag email:</label></strong>
<input id="tag-email" type="text" ng-model="vm.emailToTagWith">
<br>
The email that will be included in the tag when it's sent to the server.
</p>
</form>
<p><a href ng-click="vm.resetDefaults()">Reset to default settings</a></p>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,51 @@
'use strict';
import {
BackendService,
WebPageService,
TagStorageService,
FileService,
SettingsService} from '../services/index'
import {IVMScope, ISense} from '../index.interfaces'
/**
* Responsible for making settings user editable
* and communicating those settings changes to
* chrome extension background page.
*/
export class SettingsCtrl {
serverToSendTo
senseQueryUrl
constructor(
private $scope: IVMScope,
$log: angular.ILogService,
BackendService: BackendService,
WebPageService: WebPageService,
TagStorageService: TagStorageService,
private SettingsService: SettingsService) {
$scope.vm = this;
this.serverToSendTo = SettingsService.senseDestinationUrl
this.senseQueryUrl = SettingsService.senseQueryUrl
$scope.$watch('vm.serverToSendTo', (newValue: string, oldValue) => {
SettingsService.senseDestinationUrl = newValue;
})
}
resetDefaults() {
if (!confirm('Reset settings to default values?')) {
return //exit if user says 'no'
}
this.SettingsService.resetSettings(() => {
this.serverToSendTo = this.SettingsService.senseDestinationUrl
});
}
}

View file

@ -1,6 +1,6 @@
'use strict';
import {AppConfigService} from '../index.appConfig';
import {SettingsService} from '../services/index';
import {ISenseTag} from '../index.interfaces';
/**
@ -9,18 +9,13 @@ import {ISenseTag} from '../index.interfaces';
*/
export class BackendService {
$http: ng.IHttpService;
$log: ng.ILogService;
$q: ng.IQService;
private serverUrl: string = null;
private previousCall: string;
private previousCall: string
/* @ngInject */
constructor($http: ng.IHttpService, $q: ng.IQService, $log: ng.ILogService, AppConfigService: AppConfigService) {
this.$http = $http;
this.$log = $log;
this.$q = $q;
this.serverUrl = AppConfigService.serverUrl;
constructor(
private $http: ng.IHttpService,
private $q: ng.IQService,
private $log: ng.ILogService,
private SettingsService: SettingsService) {
}
callServer(word: string) {
@ -34,25 +29,12 @@ export class BackendService {
//alright let's make this query!
this.previousCall = word;
return this.$http.get(this.serverUrl + word);
return this.$http.get(`${this.SettingsService.senseQueryUrl}/${word}`);
}
sendTaggedDataToServer(senseTag: ISenseTag) {
this.$log.debug('sendTaggedDataToServer() was called');
this.$log.debug('would have sent this to the server:');
this.$log.debug(senseTag);
this.$log.debug('please uncomment code for actually sending to server');
// this.$http.post("example.org", senseTag)
// .then((response) => {
// this.$log.debug('successfully posted to server. Response:');
// this.$log.debug(response);
// })
// .catch((error) => {
// this.$log.error('something went wrong when posting to server');
// this.$log.error(error);
// });
return this.$http.post(this.SettingsService.senseDestinationUrl, senseTag)
}
}

View file

@ -5,12 +5,7 @@ import {ISenseTag} from '../index.interfaces';
//Responsible for saving.
export class FileService {
$log: ng.ILogService;
/* @ngInject */
constructor($log: ng.ILogService) {
this.$log = $log;
}
constructor(private $log: ng.ILogService) {}
saveFile(content: ISenseTag[]) {
var json = JSON.stringify(content, null, 2);

View file

@ -2,3 +2,4 @@ export * from './backend.service';
export * from './file.service';
export * from './tagStorage.service';
export * from './webpage.service';
export * from './settings.service';

View file

@ -0,0 +1,93 @@
/**
* Responsible for handling persisting and retrieving
* *select* user settings. Some settings might not be user
* editable.
*/
export class SettingsService {
// where to query for senses
private _senseQueryUrl = 'https://imdb.uib.no/lexitags/lexitags'
private _defaultSenseQueryUrl = 'https://imdb.uib.no/lexitags/lexitags'
// where to send the senses
private _senseDestinationUrl = 'https://www.example.org/somewhere'
private _defaultSenseDestinationUrl = 'https://www.example.org/somewhere'
private _emailToTagWith = ''
constructor(private $log: ng.ILogService) {
// fall back to default value if no user defined setting
this.loadSetting('tagitSenseQueryUrl', (loadedSetting) => {
if (loadedSetting) this._senseQueryUrl = loadedSetting
else this._senseQueryUrl = this._defaultSenseQueryUrl
})
this.loadSetting('tagitSenseDestinationUrl', (loadedSetting) => {
if (loadedSetting) this._senseDestinationUrl = loadedSetting
else this._senseDestinationUrl = this._defaultSenseDestinationUrl
})
}
resetSettings(callback) {
// reset to default values
this._defaultSenseQueryUrl = this._defaultSenseQueryUrl
this._senseDestinationUrl = this._defaultSenseDestinationUrl
// delete from chrome storage
if (typeof chrome.storage !== 'undefined') {
const done = _.after(2, callback);
chrome.storage.sync.remove('tagitSenseDestinationUrl', done)
chrome.storage.sync.remove('tagitSenseQueryUrl', done)
}
// delete from localstorage (dev mode)
else {
localStorage.removeItem('tagitSenseQueryUrl')
localStorage.removeItem('tagitSenseDestinationUrl')
callback()
}
}
private loadSetting(whatToFind: string, callback) {
// support for dev mode (when not loaded as a proper chrome plugin).
if (typeof chrome.storage === 'undefined') {
callback(localStorage.getItem(whatToFind))
return // exit function
}
const syncStorage = chrome.storage.sync
syncStorage.get(whatToFind, callback)
}
private saveSetting(nameOfThingToSave: string, valueToSave) {
// support for dev mode (when not loaded as a proper chrome plugin).
if (typeof chrome.storage === 'undefined') {
localStorage.setItem(nameOfThingToSave, valueToSave)
return // exit function
}
const syncStorage = chrome.storage.sync
syncStorage.set({ nameOfThingToSave: valueToSave })
}
get senseQueryUrl() {
return this._senseQueryUrl
}
set senseQueryUrl(newUrl: string) {
this._senseQueryUrl = newUrl
}
get senseDestinationUrl() {
return this._senseDestinationUrl
}
set senseDestinationUrl(newUrl: string) {
this._senseDestinationUrl = newUrl
this.saveSetting('tagitSenseDestinationUrl', newUrl)
}
}

View file

@ -6,15 +6,12 @@ import {ISenseTag} from '../index.interfaces';
// any tags related to the current page
export class TagStorageService {
$http: ng.IHttpService;
$log: ng.ILogService;
$localStorage: { tagStorage: ISenseTag[] };
/* @ngInject */
constructor($http: ng.IHttpService, $log: ng.ILogService,
constructor(
private $http: ng.IHttpService,
private $log: ng.ILogService,
$localStorage: any) {
this.$http = $http;
this.$log = $log;
this.$localStorage = $localStorage;
if (window.location.href.indexOf("tagitreset") !== -1) {

View file

@ -14,20 +14,17 @@ import rangy from 'rangy';
*/
export class WebPageService {
$log: ng.ILogService;
// when clicking the menu to select a synset
// we need to remember what the currently selected word was
tagStorageService: TagStorageService;
rootScopeService: ng.IRootScopeService;
savedSelection: Object;
savedText: string;
listOfFramesWithContent: (HTMLFrameElement | HTMLIFrameElement)[] = [];
/* @ngInject */
constructor($log: ng.ILogService, TagStorageService: TagStorageService, $rootScope: ng.IRootScopeService) {
this.$log = $log;
this.tagStorageService = TagStorageService;
this.rootScopeService = $rootScope;
constructor(
private $log: ng.ILogService,
private TagStorageService: TagStorageService,
private $rootScope: ng.IRootScopeService) {
this.wireUpListener();
rangy.init();
}
@ -45,17 +42,17 @@ export class WebPageService {
}
else if (wasRemoveTagButtonClicked(evt)) {
this.$log.debug('remove tag button was clicked');
removeTagFromWebAndStorage(evt, this.tagStorageService);
removeTagFromWebAndStorage(evt, this.TagStorageService);
}
else if (documentThatWasClicked.getSelection().toString() !== '') {
resetSavedSelection(this.savedSelection);
this.savedSelection = rangy.saveSelection(documentThatWasClicked);
this.savedText = joinLongWords(documentThatWasClicked.getSelection().toString());
this.rootScopeService.$broadcast('wordWasSelected', this.savedText);
this.$rootScope.$broadcast('wordWasSelected', this.savedText);
} else {
resetSavedSelection(this.savedSelection);
this.savedText = '';
this.rootScopeService.$broadcast('wordWasDeSelected', null);
this.$rootScope.$broadcast('wordWasDeSelected', null);
}
/**

View file

@ -1,7 +1,7 @@
{
"name": "Tag you're it",
"description": "A browser tool for tagging words with exact definitions.",
"version": "0.0.9",
"version": "1.0.0",
"manifest_version": 2,
"icons": {
"16": "icon16.png",
@ -20,8 +20,7 @@
"web_accessible_resources": [
"*.html",
"*.css",
"vendor/**/*.js",
"bundle.js"
"*.js"
],
"browser_action": {
"default_icon": "icon48.png"

View file

@ -8,12 +8,11 @@
<script src="../jspm_packages/system.js"></script>
<script src="../config.js"></script>
<script src="../tmp/bundle.js"></script>
<script>
window.tagitTestMode = true;
System.import('main');
System.import('/src/app/main.ts');
</script>
</head>