├── .babelrc ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── CHANGELOG ├── Dockerfile ├── LICENSE ├── README.md ├── aurelia_project ├── aurelia.json ├── environments │ ├── dev.js │ ├── prod.js │ └── stage.js ├── generators │ ├── attribute.js │ ├── attribute.json │ ├── binding-behavior.js │ ├── binding-behavior.json │ ├── element.js │ ├── element.json │ ├── generator.js │ ├── generator.json │ ├── task.js │ ├── task.json │ ├── value-converter.js │ └── value-converter.json └── tasks │ ├── build.js │ ├── build.json │ ├── process-css.js │ ├── process-markup.js │ ├── run.js │ ├── run.json │ ├── test.js │ ├── test.json │ └── transpile.js ├── composer.json ├── img ├── ico │ └── inkstone.ico ├── icons │ ├── circle-icons │ │ ├── LICENSE │ │ ├── audio.svg │ │ ├── bookmark.svg │ │ ├── bubble.svg │ │ ├── calendar.svg │ │ ├── check.svg │ │ ├── document.svg │ │ ├── film.svg │ │ ├── heart-2.svg │ │ ├── heart.svg │ │ ├── lens.svg │ │ ├── location.svg │ │ ├── memcard.svg │ │ ├── mic.svg │ │ ├── pencil.svg │ │ ├── person.svg │ │ ├── photo.svg │ │ ├── power.svg │ │ ├── recycle.svg │ │ ├── search.svg │ │ ├── settings.svg │ │ └── uparrow.svg │ ├── deriv │ │ └── plus.svg │ └── fetch.sh ├── png │ ├── inkstone.png │ ├── inkstone144.png │ └── inkstone192.png └── svg │ ├── inkling.svg │ └── inkstone.svg ├── index.html ├── karma.conf.js ├── manifest.json ├── package.json ├── php ├── .htaccess ├── discoverEndpoints.php ├── post.php ├── preformatted_post.php ├── query_source.php ├── route.php ├── test │ └── query_source.php ├── token.php └── tools.php ├── scripts ├── require.js └── text.js ├── src ├── app.html ├── app.js ├── config.js ├── delete.html ├── delete.js ├── edit.html ├── edit.js ├── environment.js ├── home.html ├── home.js ├── login.html ├── login.js ├── main.js ├── micropub.js ├── post.html ├── post.js ├── resources │ └── index.js ├── settings.html ├── settings.js ├── styles.old ├── styles.scss ├── undelete.html ├── undelete.js └── utility.js ├── styles.css.map ├── test ├── aurelia-karma.js └── unit │ ├── app.spec.js │ └── setup.js └── worker.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "sourceMap": true, 3 | "moduleIds": false, 4 | "comments": false, 5 | "compact": false, 6 | "code": true, 7 | "presets": [ "es2015-loose", "stage-1"], 8 | "plugins": [ 9 | "syntax-flow", 10 | "transform-decorators-legacy", 11 | "transform-flow-strip-types" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # 2 space indentation 12 | [**.*] 13 | indent_style = space 14 | indent_size = 2 -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/aurelia-tools/.eslintrc.json" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.idea 3 | *.DS_STORE 4 | *.swp 5 | *.swo 6 | 7 | scripts/app-bundle.js 8 | scripts/app-bundle.js.map 9 | scripts/vendor-bundle.js 10 | vendor 11 | composer.lock 12 | .sass-cache/ 13 | styles.css 14 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 1.1.10 2 | starting an official changelog finally 3 | fix issue #18, auth callback should not require 'me' parameter 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.0-apache 2 | 3 | COPY index.html manifest.json worker.js package.json composer.json /var/www/html/ 4 | 5 | COPY aurelia_project /var/www/html/aurelia_project 6 | COPY icons /var/www/html/icons 7 | COPY img /var/www/html/img 8 | COPY php /var/www/html/php 9 | COPY scripts /var/www/html/scripts 10 | COPY src /var/www/html/src 11 | 12 | WORKDIR /var/www/html 13 | 14 | RUN npm install -g aurelia-cli sass 15 | RUN npm install --loglevel=error 16 | RUN sass src/styles.scss > styles.css 17 | RUN au build 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is released CC0 2 | https://creativecommons.org/publicdomain/zero/1.0/ 3 | 4 | Several icons used in this software are not under this license and will have their own LICENSE file included in their respective folders 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # InkStone 3 | 4 | InkStone is a aurelia based micropub client that uses a service worker to allow for full offline editing. 5 | 6 | ## Install Libs 7 | 8 | cd into the main directory 9 | 10 | ```bash 11 | npm install aurelia-cli -g 12 | npm install 13 | ``` 14 | This will install the client libraries in their correct locations. 15 | 16 | ## Configure 17 | 18 | edit src/config.js and set te client_id and redirect_uri value. 19 | 20 | edit worker.js and set the BASE_DIR to whatever the web addressable path to this directlry is 21 | 22 | ## build 23 | 24 | ```bash 25 | au build 26 | composer install 27 | sass src/styles.scss > styles.css 28 | ``` 29 | The au command will build the scripts directory which is the core javascript needed to run the app. 30 | The composer command will build the vendor directory which is used by the php folder on the server. 31 | The sass command builds an extra copy of the stylesheets, this is needed for those visiting the page without js. 32 | 33 | ## deploy 34 | The only files needed are 35 | 36 | * index.html 37 | * manifest.json 38 | * worker.js 39 | * scripts/ 40 | * php/ 41 | * icons/ 42 | * vendor/ 43 | 44 | ## Prefilled Posts 45 | 46 | InkStone will automatically populate any field given in the URL. As such you can use tools such as URL-Forwarder on mobile to allow for using the Share menu 47 | Some examples would be 48 | 49 | * https://inklings.io/inkstone/#/post?like-of=@url 50 | * https://inklings.io/inkstone/#/post?in-reply-to=@url 51 | 52 | ## Edit, Delete, and Undelete 53 | 54 | * Currently these features are pretty new and so I don't want to put them in the main interface just yet, but you can access them by going to 55 | /#/edit/?url= 56 | /#/delete/?url= 57 | /#/undelete/?url= 58 | 59 | ## MobilePub 2 and Earlier 60 | 61 | Mobilepub was the name for previous versions of this software. These versions still exist as branches on the github repo, but are no longer maintained. 62 | 63 | -------------------------------------------------------------------------------- /aurelia_project/aurelia.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mobilepub", 3 | "type": "project:application", 4 | "platform": { 5 | "id": "web", 6 | "displayName": "Web", 7 | "output": "scripts" 8 | }, 9 | "transpiler": { 10 | "id": "babel", 11 | "displayName": "Babel", 12 | "fileExtension": ".js", 13 | "options": { 14 | "plugins": [ 15 | "transform-es2015-modules-amd" 16 | ] 17 | }, 18 | "source": "src/**/*.js" 19 | }, 20 | "markupProcessor": { 21 | "id": "none", 22 | "displayName": "None", 23 | "fileExtension": ".html", 24 | "source": "src/**/*.html" 25 | }, 26 | "cssProcessor": { 27 | "id": "sass", 28 | "displayName": "Sass", 29 | "fileExtension": ".scss", 30 | "source": "src/**/*.scss" 31 | }, 32 | "editor": { 33 | "id": "atom", 34 | "displayName": "Atom" 35 | }, 36 | "unitTestRunner": { 37 | "id": "karma", 38 | "displayName": "Karma", 39 | "source": "test/unit/**/*.js" 40 | }, 41 | "paths": { 42 | "root": "src", 43 | "resources": "src/resources", 44 | "elements": "src/resources/elements", 45 | "attributes": "src/resources/attributes", 46 | "valueConverters": "src/resources/value-converters", 47 | "bindingBehaviors": "src/resources/binding-behaviors" 48 | }, 49 | "testFramework": { 50 | "id": "jasmine", 51 | "displayName": "Jasmine" 52 | }, 53 | "build": { 54 | "targets": [ 55 | { 56 | "id": "web", 57 | "displayName": "Web", 58 | "output": "scripts" 59 | } 60 | ], 61 | "loader": { 62 | "type": "require", 63 | "configTarget": "vendor-bundle.js", 64 | "includeBundleMetadataInConfig": "auto", 65 | "plugins": [ 66 | { 67 | "name": "text", 68 | "extensions": [ 69 | ".html", 70 | ".css" 71 | ], 72 | "stub": true 73 | } 74 | ] 75 | }, 76 | "options": { 77 | "minify": "stage & prod", 78 | "sourcemaps": "dev & stage" 79 | }, 80 | "bundles": [ 81 | { 82 | "name": "app-bundle.js", 83 | "source": [ 84 | "[**/*.js]", 85 | "**/*.{css,html}" 86 | ] 87 | }, 88 | { 89 | "name": "vendor-bundle.js", 90 | "prepend": [ 91 | "node_modules/bluebird/js/browser/bluebird.core.js", 92 | "node_modules/whatwg-fetch/fetch.js", 93 | "scripts/require.js" 94 | ], 95 | "dependencies": [ 96 | "aurelia-binding", 97 | "aurelia-bootstrapper", 98 | "aurelia-dependency-injection", 99 | "aurelia-event-aggregator", 100 | "aurelia-framework", 101 | "aurelia-history", 102 | "aurelia-history-browser", 103 | "aurelia-fetch-client", 104 | "aurelia-loader", 105 | "aurelia-loader-default", 106 | "aurelia-logging", 107 | "aurelia-logging-console", 108 | "aurelia-metadata", 109 | "aurelia-pal", 110 | "aurelia-pal-browser", 111 | "aurelia-path", 112 | "aurelia-polyfills", 113 | "aurelia-route-recognizer", 114 | "aurelia-router", 115 | "aurelia-task-queue", 116 | "aurelia-templating", 117 | "aurelia-templating-binding", 118 | { 119 | "name": "text", 120 | "path": "../scripts/text" 121 | }, 122 | { 123 | "name": "aurelia-templating-resources", 124 | "path": "../node_modules/aurelia-templating-resources/dist/amd", 125 | "main": "aurelia-templating-resources" 126 | }, 127 | { 128 | "name": "aurelia-templating-router", 129 | "path": "../node_modules/aurelia-templating-router/dist/amd", 130 | "main": "aurelia-templating-router" 131 | }, 132 | { 133 | "name": "aurelia-testing", 134 | "path": "../node_modules/aurelia-testing/dist/amd", 135 | "main": "aurelia-testing", 136 | "env": "dev" 137 | } 138 | ] 139 | } 140 | ] 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /aurelia_project/environments/dev.js: -------------------------------------------------------------------------------- 1 | export default { 2 | debug: true, 3 | testing: true 4 | }; 5 | -------------------------------------------------------------------------------- /aurelia_project/environments/prod.js: -------------------------------------------------------------------------------- 1 | export default { 2 | debug: false, 3 | testing: false 4 | }; 5 | -------------------------------------------------------------------------------- /aurelia_project/environments/stage.js: -------------------------------------------------------------------------------- 1 | export default { 2 | debug: true, 3 | testing: false 4 | }; 5 | -------------------------------------------------------------------------------- /aurelia_project/generators/attribute.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class AttributeGenerator { 6 | constructor(project, options, ui) { 7 | this.project = project; 8 | this.options = options; 9 | this.ui = ui; 10 | } 11 | 12 | execute() { 13 | return this.ui 14 | .ensureAnswer(this.options.args[0], 'What would you like to call the custom attribute?') 15 | .then(name => { 16 | let fileName = this.project.makeFileName(name); 17 | let className = this.project.makeClassName(name); 18 | 19 | this.project.attributes.add( 20 | ProjectItem.text(`${fileName}.js`, this.generateSource(className)) 21 | ); 22 | 23 | return this.project.commitChanges() 24 | .then(() => this.ui.log(`Created ${fileName}.`)); 25 | }); 26 | } 27 | 28 | generateSource(className) { 29 | return `import {inject} from 'aurelia-framework'; 30 | 31 | @inject(Element) 32 | export class ${className}CustomAttribute { 33 | constructor(element) { 34 | this.element = element; 35 | } 36 | 37 | valueChanged(newValue, oldValue) { 38 | 39 | } 40 | } 41 | 42 | ` 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /aurelia_project/generators/attribute.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "attribute", 3 | "description": "Creates a custom attribute class and places it in the project resources." 4 | } 5 | -------------------------------------------------------------------------------- /aurelia_project/generators/binding-behavior.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class BindingBehaviorGenerator { 6 | constructor(project, options, ui) { 7 | this.project = project; 8 | this.options = options; 9 | this.ui = ui; 10 | } 11 | 12 | execute() { 13 | return this.ui 14 | .ensureAnswer(this.options.args[0], 'What would you like to call the binding behavior?') 15 | .then(name => { 16 | let fileName = this.project.makeFileName(name); 17 | let className = this.project.makeClassName(name); 18 | 19 | this.project.bindingBehaviors.add( 20 | ProjectItem.text(`${fileName}.js`, this.generateSource(className)) 21 | ); 22 | 23 | return this.project.commitChanges() 24 | .then(() => this.ui.log(`Created ${fileName}.`)); 25 | }); 26 | } 27 | 28 | generateSource(className) { 29 | return `export class ${className}BindingBehavior { 30 | bind(binding, source) { 31 | 32 | } 33 | 34 | unbind(binding, source) { 35 | 36 | } 37 | } 38 | 39 | ` 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /aurelia_project/generators/binding-behavior.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "binding-behavior", 3 | "description": "Creates a binding behavior class and places it in the project resources." 4 | } 5 | -------------------------------------------------------------------------------- /aurelia_project/generators/element.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class ElementGenerator { 6 | constructor(project, options, ui) { 7 | this.project = project; 8 | this.options = options; 9 | this.ui = ui; 10 | } 11 | 12 | execute() { 13 | return this.ui 14 | .ensureAnswer(this.options.args[0], 'What would you like to call the custom element?') 15 | .then(name => { 16 | let fileName = this.project.makeFileName(name); 17 | let className = this.project.makeClassName(name); 18 | 19 | this.project.elements.add( 20 | ProjectItem.text(`${fileName}.js`, this.generateJSSource(className)), 21 | ProjectItem.text(`${fileName}.html`, this.generateHTMLSource(className)) 22 | ); 23 | 24 | return this.project.commitChanges() 25 | .then(() => this.ui.log(`Created ${fileName}.`)); 26 | }); 27 | } 28 | 29 | generateJSSource(className) { 30 | return `import {bindable} from 'aurelia-framework'; 31 | 32 | export class ${className} { 33 | @bindable value; 34 | 35 | valueChanged(newValue, oldValue) { 36 | 37 | } 38 | } 39 | 40 | ` 41 | } 42 | 43 | generateHTMLSource(className) { 44 | return `` 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /aurelia_project/generators/element.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "element", 3 | "description": "Creates a custom element class and template, placing them in the project resources." 4 | } 5 | -------------------------------------------------------------------------------- /aurelia_project/generators/generator.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class GeneratorGenerator { 6 | constructor(project, options, ui) { 7 | this.project = project; 8 | this.options = options; 9 | this.ui = ui; 10 | } 11 | 12 | execute() { 13 | return this.ui 14 | .ensureAnswer(this.options.args[0], 'What would you like to call the generator?') 15 | .then(name => { 16 | let fileName = this.project.makeFileName(name); 17 | let className = this.project.makeClassName(name); 18 | 19 | this.project.generators.add( 20 | ProjectItem.text(`${fileName}.js`, this.generateSource(className)) 21 | ); 22 | 23 | return this.project.commitChanges() 24 | .then(() => this.ui.log(`Created ${fileName}.`)); 25 | }); 26 | } 27 | 28 | generateSource(className) { 29 | return `import {inject} from 'aurelia-dependency-injection'; 30 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 31 | 32 | @inject(Project, CLIOptions, UI) 33 | export default class ${className}Generator { 34 | constructor(project, options, ui) { 35 | this.project = project; 36 | this.options = options; 37 | this.ui = ui; 38 | } 39 | 40 | execute() { 41 | return this.ui 42 | .ensureAnswer(this.options.args[0], 'What would you like to call the new item?') 43 | .then(name => { 44 | let fileName = this.project.makeFileName(name); 45 | let className = this.project.makeClassName(name); 46 | 47 | this.project.elements.add( 48 | ProjectItem.text(\`\${fileName}.js\`, this.generateSource(className)) 49 | ); 50 | 51 | return this.project.commitChanges() 52 | .then(() => this.ui.log(\`Created \${fileName}.\`)); 53 | }); 54 | } 55 | 56 | generateSource(className) { 57 | return \`import {bindable} from 'aurelia-framework'; 58 | 59 | export class \${className} { 60 | @bindable value; 61 | 62 | valueChanged(newValue, oldValue) { 63 | 64 | } 65 | } 66 | 67 | \` 68 | } 69 | } 70 | 71 | ` 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /aurelia_project/generators/generator.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator", 3 | "description": "Creates a generator class and places it in the project generators folder." 4 | } 5 | -------------------------------------------------------------------------------- /aurelia_project/generators/task.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class TaskGenerator { 6 | constructor(project, options, ui) { 7 | this.project = project; 8 | this.options = options; 9 | this.ui = ui; 10 | } 11 | 12 | execute() { 13 | return this.ui 14 | .ensureAnswer(this.options.args[0], 'What would you like to call the task?') 15 | .then(name => { 16 | let fileName = this.project.makeFileName(name); 17 | let functionName = this.project.makeFunctionName(name); 18 | 19 | this.project.tasks.add( 20 | ProjectItem.text(`${fileName}.js`, this.generateSource(functionName)) 21 | ); 22 | 23 | return this.project.commitChanges() 24 | .then(() => this.ui.log(`Created ${fileName}.`)); 25 | }); 26 | } 27 | 28 | generateSource(functionName) { 29 | return `import gulp from 'gulp'; 30 | import changed from 'gulp-changed'; 31 | import project from '../aurelia.json'; 32 | 33 | export default function ${functionName}() { 34 | return gulp.src(project.paths.???) 35 | .pipe(changed(project.paths.output, {extension: '.???'})) 36 | .pipe(gulp.dest(project.paths.output)); 37 | } 38 | 39 | ` 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /aurelia_project/generators/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "task", 3 | "description": "Creates a task and places it in the project tasks folder." 4 | } 5 | -------------------------------------------------------------------------------- /aurelia_project/generators/value-converter.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class ValueConverterGenerator { 6 | constructor(project, options, ui) { 7 | this.project = project; 8 | this.options = options; 9 | this.ui = ui; 10 | } 11 | 12 | execute() { 13 | return this.ui 14 | .ensureAnswer(this.options.args[0], 'What would you like to call the value converter?') 15 | .then(name => { 16 | let fileName = this.project.makeFileName(name); 17 | let className = this.project.makeClassName(name); 18 | 19 | this.project.valueConverters.add( 20 | ProjectItem.text(`${fileName}.js`, this.generateSource(className)) 21 | ); 22 | 23 | return this.project.commitChanges() 24 | .then(() => this.ui.log(`Created ${fileName}.`)); 25 | }); 26 | } 27 | 28 | generateSource(className) { 29 | return `export class ${className}ValueConverter { 30 | toView(value) { 31 | 32 | } 33 | 34 | fromView(value) { 35 | 36 | } 37 | } 38 | 39 | ` 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /aurelia_project/generators/value-converter.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "value-converter", 3 | "description": "Creates a value converter class and places it in the project resources." 4 | } 5 | -------------------------------------------------------------------------------- /aurelia_project/tasks/build.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import transpile from './transpile'; 3 | import processMarkup from './process-markup'; 4 | import processCSS from './process-css'; 5 | import {build} from 'aurelia-cli'; 6 | import project from '../aurelia.json'; 7 | 8 | export default gulp.series( 9 | readProjectConfiguration, 10 | gulp.parallel( 11 | transpile, 12 | processMarkup, 13 | processCSS 14 | ), 15 | writeBundles 16 | ); 17 | 18 | function readProjectConfiguration() { 19 | return build.src(project); 20 | } 21 | 22 | function writeBundles() { 23 | return build.dest(); 24 | } 25 | -------------------------------------------------------------------------------- /aurelia_project/tasks/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build", 3 | "description": "Builds and processes all application assets.", 4 | "flags": [ 5 | { 6 | "name": "env", 7 | "description": "Sets the build environment.", 8 | "type": "string" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /aurelia_project/tasks/process-css.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import sourcemaps from 'gulp-sourcemaps'; 3 | import sass from 'gulp-sass'; 4 | import project from '../aurelia.json'; 5 | import {build} from 'aurelia-cli'; 6 | 7 | export default function processCSS() { 8 | return gulp.src(project.cssProcessor.source) 9 | .pipe(sourcemaps.init()) 10 | .pipe(sass().on('error', sass.logError)) 11 | .pipe(build.bundle()); 12 | }; 13 | -------------------------------------------------------------------------------- /aurelia_project/tasks/process-markup.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import changedInPlace from 'gulp-changed-in-place'; 3 | import project from '../aurelia.json'; 4 | import {build} from 'aurelia-cli'; 5 | 6 | export default function processMarkup() { 7 | return gulp.src(project.markupProcessor.source) 8 | .pipe(changedInPlace({firstPass:true})) 9 | .pipe(build.bundle()); 10 | } 11 | -------------------------------------------------------------------------------- /aurelia_project/tasks/run.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import browserSync from 'browser-sync' 3 | import historyApiFallback from 'connect-history-api-fallback/lib';; 4 | import project from '../aurelia.json'; 5 | import build from './build'; 6 | import {CLIOptions} from 'aurelia-cli'; 7 | 8 | function onChange(path) { 9 | console.log(`File Changed: ${path}`); 10 | } 11 | 12 | function reload(done) { 13 | browserSync.reload(); 14 | done(); 15 | } 16 | 17 | let serve = gulp.series( 18 | build, 19 | done => { 20 | browserSync({ 21 | online: false, 22 | open: false, 23 | port: 9000, 24 | logLevel: 'silent', 25 | server: { 26 | baseDir: ['.'], 27 | middleware: [historyApiFallback(), function(req, res, next) { 28 | res.setHeader('Access-Control-Allow-Origin', '*'); 29 | next(); 30 | }] 31 | } 32 | }, function (err, bs) { 33 | let urls = bs.options.get('urls').toJS(); 34 | console.log(`Application Available At: ${urls.local}`); 35 | console.log(`BrowserSync Available At: ${urls.ui}`); 36 | done(); 37 | }); 38 | } 39 | ); 40 | 41 | let refresh = gulp.series( 42 | build, 43 | reload 44 | ); 45 | 46 | let watch = function() { 47 | gulp.watch(project.transpiler.source, refresh).on('change', onChange); 48 | gulp.watch(project.markupProcessor.source, refresh).on('change', onChange); 49 | gulp.watch(project.cssProcessor.source, refresh).on('change', onChange) 50 | } 51 | 52 | let run; 53 | 54 | if (CLIOptions.hasFlag('watch')) { 55 | run = gulp.series( 56 | serve, 57 | watch 58 | ); 59 | } else { 60 | run = serve; 61 | } 62 | 63 | export default run; 64 | -------------------------------------------------------------------------------- /aurelia_project/tasks/run.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "run", 3 | "description": "Builds the application and serves up the assets via a local web server, watching files for changes as you work.", 4 | "flags": [ 5 | { 6 | "name": "env", 7 | "description": "Sets the build environment.", 8 | "type": "string" 9 | }, 10 | { 11 | "name": "watch", 12 | "description": "Watches source files for changes and refreshes the app automatically.", 13 | "type": "boolean" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /aurelia_project/tasks/test.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import {Server as Karma} from 'karma'; 3 | import {CLIOptions} from 'aurelia-cli'; 4 | 5 | export function unit(done) { 6 | new Karma({ 7 | configFile: __dirname + '/../../karma.conf.js', 8 | singleRun: !CLIOptions.hasFlag('watch') 9 | }, done).start(); 10 | } 11 | 12 | export default unit; 13 | -------------------------------------------------------------------------------- /aurelia_project/tasks/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "description": "Runs all unit tests and reports the results.", 4 | "flags": [ 5 | { 6 | "name": "env", 7 | "description": "Sets the build environment.", 8 | "type": "string" 9 | }, 10 | { 11 | "name": "watch", 12 | "description": "Watches test files for changes and re-runs the tests automatically.", 13 | "type": "boolean" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /aurelia_project/tasks/transpile.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import changedInPlace from 'gulp-changed-in-place'; 3 | import plumber from 'gulp-plumber'; 4 | import babel from 'gulp-babel'; 5 | import sourcemaps from 'gulp-sourcemaps'; 6 | import notify from 'gulp-notify'; 7 | import rename from 'gulp-rename'; 8 | import project from '../aurelia.json'; 9 | import {CLIOptions, build} from 'aurelia-cli'; 10 | 11 | function configureEnvironment() { 12 | let env = CLIOptions.getEnvironment(); 13 | 14 | return gulp.src(`aurelia_project/environments/${env}.js`) 15 | .pipe(changedInPlace({firstPass:true})) 16 | .pipe(rename('environment.js')) 17 | .pipe(gulp.dest(project.paths.root)); 18 | } 19 | 20 | function buildJavaScript() { 21 | return gulp.src(project.transpiler.source) 22 | .pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')})) 23 | .pipe(changedInPlace({firstPass:true})) 24 | .pipe(sourcemaps.init()) 25 | .pipe(babel(project.transpiler.options)) 26 | .pipe(build.bundle()); 27 | } 28 | 29 | export default gulp.series( 30 | configureEnvironment, 31 | buildJavaScript 32 | ); 33 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "mf2/mf2": "*", 4 | "indieweb/link-rel-parser": "^0.1.2", 5 | "indieauth/client": "^0.1.16" 6 | }, 7 | "autoload": { 8 | "files": ["php/tools.php"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /img/ico/inkstone.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Inklings-io/inkstone/82a88dac052f24939d426ae13de9bd308efb9223/img/ico/inkstone.ico -------------------------------------------------------------------------------- /img/icons/circle-icons/LICENSE: -------------------------------------------------------------------------------- 1 | Files in this directory are licensed under the GPL 2 | http://www.gnu.org/copyleft/gpl.html 3 | 4 | They are owned by Nick Roach http://www.elegantthemes.com/ 5 | 6 | These any many more GPL SVG icons can be found at https://www.iconfinder.com/iconsets/circle-icons-1 7 | -------------------------------------------------------------------------------- /img/icons/circle-icons/audio.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/bookmark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/bubble.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/calendar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/document.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/film.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/heart-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /img/icons/circle-icons/heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/lens.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/location.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/memcard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/mic.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/icons/circle-icons/pencil.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/person.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/photo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/power.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/recycle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/circle-icons/uparrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/icons/deriv/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /img/icons/fetch.sh: -------------------------------------------------------------------------------- 1 | wget https://www.iconfinder.com/icons/1055034/download/svg/128 -O circle-icons/location.svg 2 | wget https://www.iconfinder.com/icons/1054981/download/svg/128 -O circle-icons/settings.svg 3 | wget https://www.iconfinder.com/icons/1054992/download/svg/128 -O circle-icons/bookmark.svg 4 | wget https://www.iconfinder.com/icons/1055024/download/svg/128 -O circle-icons/audio.svg 5 | wget https://www.iconfinder.com/icons/1055045/download/svg/128 -O circle-icons/heart.svg 6 | wget https://www.iconfinder.com/icons/1055002/download/svg/128 -O circle-icons/power.svg 7 | wget https://www.iconfinder.com/icons/1055037/download/svg/128 -O circle-icons/lens.svg 8 | wget https://www.iconfinder.com/icons/1054941/download/svg/128 -O circle-icons/film.svg 9 | wget https://www.iconfinder.com/icons/1055045/download/svg/128 -O circle-icons/heart.svg 10 | wget https://www.iconfinder.com/icons/1055042/download/svg/128 -O circle-icons/photo.svg 11 | wget https://www.iconfinder.com/icons/1055095/download/svg/128 -O circle-icons/bubble.svg 12 | wget https://www.iconfinder.com/icons/1055000/download/svg/128 -O circle-icons/person.svg 13 | wget https://www.iconfinder.com/icons/1055013/download/svg/128 -O circle-icons/pencil.svg 14 | wget https://www.iconfinder.com/icons/1055119/download/svg/128 -O circle-icons/uparrow.svg 15 | wget https://www.iconfinder.com/icons/1055101/download/svg/128 -O circle-icons/calendar.svg 16 | wget https://www.iconfinder.com/icons/1055026/download/svg/128 -O circle-icons/memcard.svg 17 | wget https://www.iconfinder.com/icons/1054994/download/svg/128 -O circle-icons/recycle.svg 18 | -------------------------------------------------------------------------------- /img/png/inkstone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Inklings-io/inkstone/82a88dac052f24939d426ae13de9bd308efb9223/img/png/inkstone.png -------------------------------------------------------------------------------- /img/png/inkstone144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Inklings-io/inkstone/82a88dac052f24939d426ae13de9bd308efb9223/img/png/inkstone144.png -------------------------------------------------------------------------------- /img/png/inkstone192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Inklings-io/inkstone/82a88dac052f24939d426ae13de9bd308efb9223/img/png/inkstone192.png -------------------------------------------------------------------------------- /img/svg/inkling.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 61 | 66 | 67 | 73 | 76 | 81 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /img/svg/inkstone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 64 | 70 | 76 | 77 | 83 | 87 | 94 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | InkStone 5 | 6 | 7 | 8 | 9 | 10 | 21 | 22 | 23 | 24 | 25 | 26 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const path = require('path'); 3 | const project = require('./aurelia_project/aurelia.json'); 4 | 5 | let testSrc = [ 6 | { pattern: project.unitTestRunner.source, included: false }, 7 | 'test/aurelia-karma.js' 8 | ]; 9 | 10 | let output = project.platform.output; 11 | let appSrc = project.build.bundles.map(x => path.join(output, x.name)); 12 | let entryIndex = appSrc.indexOf(path.join(output, project.build.loader.configTarget)); 13 | let entryBundle = appSrc.splice(entryIndex, 1)[0]; 14 | let files = [entryBundle].concat(testSrc).concat(appSrc); 15 | 16 | module.exports = function(config) { 17 | config.set({ 18 | basePath: '', 19 | frameworks: [project.testFramework.id], 20 | files: files, 21 | exclude: [], 22 | preprocessors: { 23 | [project.unitTestRunner.source]: [project.transpiler.id] 24 | }, 25 | 'babelPreprocessor': { options: project.transpiler.options }, 26 | reporters: ['progress'], 27 | port: 9876, 28 | colors: true, 29 | logLevel: config.LOG_INFO, 30 | autoWatch: true, 31 | browsers: ['Chrome'], 32 | singleRun: false 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "version": "1.1.10", 4 | "name": "InkStone", 5 | "short_name": "InkStone", 6 | "default_locale": "en", 7 | "description": "micropub posting application",, 8 | "icons": [ 9 | { 10 | "src": "img/png/inkstone192.png", 11 | "sizes": "192x192", 12 | "type": "image/png" 13 | }, 14 | { 15 | "src": "img/png/inkstone144.png", 16 | "sizes": "144x144", 17 | "type": "image/png" 18 | } 19 | ], 20 | "start_url": "index.html", 21 | "display": "standalone" 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inkstone", 3 | "description": "An Aurelia client application.", 4 | "version": "1.1.10", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/dissolve/inkstone" 8 | }, 9 | "license": "MIT", 10 | "dependencies": { 11 | "aurelia-animator-css": "^1.0.0", 12 | "aurelia-bootstrapper": "^1.0.0", 13 | "aurelia-fetch-client": "^1.0.0", 14 | "bluebird": "^3.4.1", 15 | "whatwg-fetch": "^1.0.0" 16 | }, 17 | "peerDependencies": {}, 18 | "devDependencies": { 19 | "aurelia-cli": "^0.18.0", 20 | "aurelia-fetch-client": "^1.0.0", 21 | "aurelia-testing": "^1.0.0-beta.2.0.0", 22 | "aurelia-tools": "^0.2.2", 23 | "browser-sync": "^2.13.0", 24 | "connect-history-api-fallback": "^1.2.0", 25 | "gulp": "github:gulpjs/gulp#4.0", 26 | "gulp-changed-in-place": "^2.0.3", 27 | "gulp-plumber": "^1.1.0", 28 | "gulp-rename": "^1.2.2", 29 | "gulp-sourcemaps": "^2.0.0-alpha", 30 | "gulp-notify": "^2.2.0", 31 | "minimatch": "^3.0.2", 32 | "through2": "^2.0.1", 33 | "uglify-js": "^2.6.3", 34 | "vinyl-fs": "^2.4.3", 35 | "babel-eslint": "^6.0.4", 36 | "babel-plugin-syntax-flow": "^6.8.0", 37 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 38 | "babel-plugin-transform-es2015-modules-amd": "^6.8.0", 39 | "babel-plugin-transform-es2015-modules-commonjs": "^6.10.3", 40 | "babel-plugin-transform-flow-strip-types": "^6.8.0", 41 | "babel-preset-es2015": "^6.9.0", 42 | "babel-preset-es2015-loose": "^7.0.0", 43 | "babel-preset-stage-1": "^6.5.0", 44 | "babel-polyfill": "^6.9.1", 45 | "babel-register": "^6.9.0", 46 | "gulp-babel": "^6.1.2", 47 | "gulp-eslint": "^2.0.0", 48 | "gulp-sass": "^2.3.2", 49 | "jasmine-core": "^2.4.1", 50 | "karma": "^0.13.22", 51 | "karma-chrome-launcher": "^1.0.1", 52 | "karma-jasmine": "^1.0.2", 53 | "karma-babel-preprocessor": "^6.0.1" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /php/.htaccess: -------------------------------------------------------------------------------- 1 | # some php setups capture Authorization against our choice :( 2 | #RewriteRule ^$ index.php [L,QSA,E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 3 | #RewriteRule ^([^?]*) $1 [L,QSA,E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 4 | SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 5 | -------------------------------------------------------------------------------- /php/discoverEndpoints.php: -------------------------------------------------------------------------------- 1 | true); 8 | 9 | if(!isset($_POST['me'])){ 10 | $json['success'] = false; 11 | $json['error'] = 'please enter your url'; 12 | echo json_encode($json); 13 | exit(); 14 | } 15 | $me = normalizeUrl($_POST['me']); 16 | 17 | $auth_endpoint = IndieAuth\Client::discoverAuthorizationEndpoint($me); 18 | if(!$auth_endpoint){ 19 | $json['success'] = false; 20 | $json['error'] = 'You do not seem to have a IndieAuth Endpoint.'; 21 | } 22 | 23 | $json['auth_endpoint'] = $auth_endpoint ; 24 | 25 | $micropub_endpoint = IndieAuth\Client::discoverMicropubEndpoint($me); 26 | if(!$micropub_endpoint){ 27 | $json['success'] = false; 28 | $json['error'] = 'You do not seem to have a Micropub Endpoint.'; 29 | } else { 30 | $json['mp_endpoint'] = $micropub_endpoint; 31 | } 32 | 33 | echo json_encode($json); 34 | -------------------------------------------------------------------------------- /php/post.php: -------------------------------------------------------------------------------- 1 | "h-entry", 6 | "properties" => array( 7 | "published" => array("2016-02-21T12:50:53-08:00"), 8 | "content" => array("Hello World"), 9 | "in-reply-to" => array("Some Website"), 10 | "like-of" => array("Some Website Liked"), 11 | "category" => array( 12 | "foo", 13 | "bar" 14 | ) 15 | ), 16 | ); 17 | 18 | 19 | if(isset($_GET['properties'])){ 20 | $result_properties=array(); 21 | foreach($_GET['properties'] as $prop){ 22 | if(isset($post_obj['properties'][$prop])){ 23 | $result_properties[$prop] = $post_obj['properties'][$prop]; 24 | } 25 | } 26 | $post_obj = array('properties' => $result_properties); 27 | } 28 | 29 | echo json_encode($post_obj); 30 | -------------------------------------------------------------------------------- /php/token.php: -------------------------------------------------------------------------------- 1 | 'authorization_code', 15 | 'code' => $code, 16 | 'redirect_uri' => $redir, 17 | 'client_id' => $client_id, 18 | 'me' => $me 19 | ); 20 | if ($state) { 21 | $post_array['state'] = $state; 22 | } 23 | 24 | $post_data = http_build_query($post_array); 25 | 26 | $ch = curl_init($token_endpoint); 27 | 28 | if (!$ch) { 29 | //$this->log->write('error with curl_init'); 30 | } 31 | 32 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 33 | curl_setopt($ch, CURLOPT_POST, true); 34 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); 35 | 36 | $response = curl_exec($ch); 37 | $curl_info = curl_getinfo($ch); 38 | curl_close($ch); 39 | 40 | //debug_log(print_r($curl_info, true)); 41 | 42 | //$this->log->write('response from Auth endpoint: ' . $response); 43 | 44 | $results = array(); 45 | if($curl_info['content_type'] == 'application/json'){ 46 | $results = json_decode($response, true); 47 | } else { 48 | parse_str($response, $results); 49 | } 50 | 51 | //debug_log(print_r($results, true)); 52 | 53 | return $results; 54 | } 55 | 56 | 57 | $json = array( 'success' => true); 58 | 59 | if( !isset($_POST['me']) || !isset($_POST['code']) || !isset($_POST['client_id']) || !isset($_POST['state']) || !isset($_POST['redirect_uri'])) { 60 | $json['success'] = false; 61 | $json['error'] = 'Error with auth API in this app: some required data is missing'; 62 | } else { 63 | $me = normalizeUrl($_POST['me']); 64 | $code = $_POST['code']; 65 | $state = $_POST['state']; 66 | $client_id = $_POST['client_id']; 67 | $redir_url = $_POST['redirect_uri']; 68 | 69 | $token_results = getToken($me, $code, $redir_url, $client_id, $state); 70 | if (!$token_results || empty($token_results)) { 71 | $json['success'] = false; 72 | $json['error'] = 'Error getting Token'; 73 | } else { 74 | $json['token'] = $token_results['access_token']; 75 | $json['scope'] = $token_results['scope']; 76 | } 77 | } 78 | 79 | echo json_encode($json); 80 | -------------------------------------------------------------------------------- /php/tools.php: -------------------------------------------------------------------------------- 1 | $media_endpoint, 96 | CURLOPT_HEADER => true, 97 | CURLOPT_VERBOSE => true, 98 | CURLOPT_POST => true, 99 | CURLOPT_HTTPHEADER => $headers, 100 | CURLOPT_POSTFIELDS => $postfields, 101 | CURLOPT_RETURNTRANSFER => true 102 | ); // cURL options 103 | curl_setopt_array($ch, $options); 104 | 105 | $response = curl_exec($ch); 106 | 107 | if(!curl_errno($ch)) { 108 | $info = curl_getinfo($ch); 109 | if ($info['http_code'] == 201){ 110 | $errmsg = "File uploaded successfully"; 111 | 112 | $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); 113 | $header_text = substr($response, 0, $header_size); 114 | $body_text = substr($response, $header_size); 115 | 116 | foreach (explode("\r\n", $header_text) as $i => $line){ 117 | 118 | if( preg_match('/^location/i', $line)){ 119 | 120 | $split = explode(': ', $line); 121 | curl_close($ch); 122 | if($return_alt_obj && isset($media_data['alt']) && !empty($media_data['alt'])){ 123 | return array('alt' => $media_data['alt'], 'value' => $split[1]); 124 | } else { 125 | return $split[1]; 126 | } 127 | 128 | } 129 | } 130 | } else { 131 | // todo if ! 201 132 | curl_close($ch); 133 | return null; 134 | } 135 | } else { 136 | $errmsg = curl_error($ch); 137 | curl_close($ch); 138 | return null; 139 | } 140 | } 141 | } 142 | 143 | //TODO 144 | function multipartPost($target, $bearer_string, $post_data, $media_fields) 145 | { 146 | $ch = curl_init($target); 147 | 148 | if (!$ch) { 149 | header('HTTP/1.1 500 Server Error'); 150 | exit(); 151 | } 152 | 153 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 154 | curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: ' . $bearer_string)); 155 | curl_setopt($ch, CURLOPT_HEADER, true); 156 | if($post_data){ 157 | curl_setopt($ch, CURLOPT_POST, true); 158 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); 159 | } 160 | 161 | $response = curl_exec($ch); 162 | return $response; 163 | } 164 | 165 | function standardPost($target, $bearer_string, $post_data = null, $additional_headers = array()) 166 | { 167 | $ch = curl_init($target); 168 | 169 | if (!$ch) { 170 | header('HTTP/1.1 500 Server Error'); 171 | exit(); 172 | } 173 | 174 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 175 | curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge(array('Authorization: ' . $bearer_string), $additional_headers)); 176 | curl_setopt($ch, CURLOPT_VERBOSE, true); 177 | curl_setopt($ch, CURLOPT_HEADER, true); 178 | if($post_data){ 179 | curl_setopt($ch, CURLOPT_POST, true); 180 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); 181 | } 182 | 183 | $response = curl_exec($ch); 184 | 185 | $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); 186 | $header_text = substr($response, 0, $header_size); 187 | $body_text = substr($response, $header_size); 188 | 189 | return array('header' => $header_text, 'body' => $body_text); 190 | } 191 | 192 | function returnResponse($response_obj) 193 | { 194 | //TODO just return the result directly 195 | 196 | $header_text = $response_obj['header']; 197 | $body_text = $response_obj['body']; 198 | 199 | 200 | foreach (explode("\r\n", $header_text) as $i => $line){ 201 | 202 | if( 203 | preg_match('/^content-type:/i', $line) 204 | || preg_match('/^http\//i', $line) 205 | || preg_match('/^location/i', $line) 206 | || preg_match('/^content-length/i', $line) 207 | ){ 208 | header($line); 209 | } 210 | 211 | /* 212 | if ($i === 0) 213 | $headers['http_code'] = $line; 214 | else 215 | { 216 | list ($key, $value) = explode(': ', $line); 217 | 218 | $headers[$key] = $value; 219 | } 220 | */ 221 | } 222 | echo $body_text; 223 | } 224 | 225 | function debug_log($content){ 226 | file_put_contents('log.txt', 227 | date("Y-m-d H:i:s - ") .$content . "\n", 228 | FILE_APPEND); 229 | } 230 | 231 | function transform_json_post($old_format){ 232 | $result_obj = array('type' => array(), 'properties' => array()); 233 | if(isset($old_format['h'])){ 234 | $result_obj['type'][] = 'h-' . $old_format['h']; 235 | unset($old_format['h']); 236 | } 237 | foreach($old_format as $key => $val){ 238 | if(!empty($val)){ 239 | if(is_array($val)){ 240 | $result_obj['properties'][$key] = $val; 241 | } else { 242 | $result_obj['properties'][$key] = array($val); 243 | } 244 | } 245 | } 246 | 247 | return $result_obj; 248 | 249 | } 250 | 251 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import {MicropubAPI} from './micropub'; 2 | import {Router, RouterConfiguration, Redirect} from 'aurelia-router'; 3 | 4 | export class App { 5 | router: Router; 6 | 7 | configureRouter(config: RouterConfiguration, router: Router){ 8 | config.title = 'InkStone'; 9 | config.pushState = true; 10 | config.addAuthorizeStep(AuthorizeStep); 11 | config.map([ 12 | { route: '', moduleId: 'home', name:'home', title: 'Home', loggedIn: true }, 13 | { route: 'home', moduleId: 'home', title: 'Home', loggedIn: true }, 14 | { route: 'login', moduleId: 'login', name:'login', title: 'Login', loggedOut: true }, 15 | { route: 'settings', moduleId: 'settings', name:'settings', title: 'Settings', loggedIn: true }, 16 | { route: 'post/:num', moduleId: 'post', name:'post', title: 'Edit Post', loggedIn: true }, 17 | { route: 'post', moduleId: 'post', name:'newpost', title: 'Create Post', loggedIn: true }, 18 | { route: 'edit', moduleId: 'edit', name:'editpost', title: "Edit Post", loggedIn: true }, 19 | { route: 'delete', moduleId: 'delete', name:'deletepost', title: "Delete Post", loggedIn: true }, 20 | { route: 'undelete', moduleId: 'undelete', name:'undeletepost', title: "Undelete Post", loggedIn: true } 21 | 22 | ]); 23 | 24 | this.router = router; 25 | } 26 | } 27 | 28 | class AuthorizeStep { 29 | 30 | run(navigationInstruction, next) { 31 | var mp = new MicropubAPI; 32 | var isLoggedIn = mp.logged_in(); 33 | if (navigationInstruction.getAllInstructions().some(i => i.config.loggedIn)) { 34 | if (!isLoggedIn) { 35 | return next.cancel(new Redirect('login')); 36 | } 37 | } else if (navigationInstruction.getAllInstructions().some(i => i.config.loggedOut)) { 38 | if (isLoggedIn) { 39 | return next.cancel(new Redirect('home')); 40 | } 41 | } 42 | 43 | return next(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export class Config { 2 | 3 | 4 | constructor(){ 5 | 6 | this.software_version = '1.1.10'; 7 | 8 | this.post_encoding = 'form'; 9 | 10 | this.client_id = 'https://inklings.io/inkstone/'; 11 | this.redirect_uri = 'https://inklings.io/inkstone/'; 12 | 13 | this.field_template = { 14 | name: 'micropub-field', 15 | label: 'Field Display Name', 16 | type: 'string', 17 | icon: 'img/icons/circle-icons/settings.svg', 18 | adding: '', 19 | adding_option: '', 20 | options: [], 21 | custom: true, 22 | shown: false, 23 | preview_img: false, 24 | html: false, 25 | always_send: false 26 | } 27 | 28 | 29 | this.preset_post = { 30 | 'h' : 'entry', 31 | 'bookmark-of' : '', 32 | 'category' : [], 33 | 'content' : '', 34 | 'in-reply-to' : '', 35 | 'like-of' : '', 36 | 'location' : '', 37 | 'location-name' : '', 38 | 'mp-type' : 'note', 39 | 'name' : '', 40 | 'repost-of' : '', 41 | 'slug' : '', 42 | 'summary' : '', 43 | 'tag-of' : [], 44 | 'photo' : [], 45 | 'video' : [], 46 | 'audio' : [] 47 | }; 48 | this.preset_fields = [ 49 | { 50 | name: 'h', 51 | label: 'microformat type', 52 | type: 'select', 53 | icon: '', 54 | adding: '', 55 | adding_option: '', 56 | options: ['entry'], 57 | custom: false, 58 | shown: false, 59 | preview_img: false, 60 | html: false, 61 | always_send: true 62 | }, 63 | { 64 | name: 'content', 65 | label: 'Content', 66 | type: 'text', 67 | icon: '', 68 | adding: '', 69 | adding_option: '', 70 | options: [], 71 | custom: false, 72 | shown: true, 73 | preview_img: false, 74 | html: true, 75 | always_send: false 76 | }, 77 | { 78 | name: 'name', 79 | label: 'Title', 80 | type: 'string', 81 | icon: 'img/icons/circle-icons/document.svg', 82 | adding: '', 83 | adding_option: '', 84 | options: [], 85 | custom: false, 86 | shown: true, 87 | preview_img: false, 88 | html: false, 89 | always_send: false 90 | }, 91 | { 92 | name: 'category', 93 | label: 'Category', 94 | type: 'list', 95 | icon: 'img/icons/circle-icons/document.svg', 96 | adding: '', 97 | adding_option: '', 98 | options: [], 99 | custom: false, 100 | shown: false, 101 | preview_img: false, 102 | html: false, 103 | always_send: false 104 | }, 105 | { 106 | name: 'in-reply-to', 107 | label: 'In Reply To', 108 | type: 'string', 109 | icon: 'img/icons/circle-icons/bubble.svg', 110 | adding: '', 111 | adding_option: '', 112 | options: [], 113 | custom: false, 114 | shown: false, 115 | preview_img: false, 116 | html: false, 117 | always_send: false 118 | }, 119 | { 120 | name: 'location', 121 | label: 'Location', 122 | type: 'string', 123 | icon: 'img/icons/circle-icons/location.svg', 124 | adding: '', 125 | adding_option: '', 126 | options: [], 127 | custom: false, 128 | shown: false, 129 | preview_img: false, 130 | html: false, 131 | always_send: false 132 | }, 133 | { 134 | name: 'photo', 135 | label: 'Photo', 136 | type: 'files', 137 | icon: 'img/icons/circle-icons/photo.svg', 138 | adding: '', 139 | adding_option: '', 140 | options: [], 141 | custom: false, 142 | shown: false, 143 | preview_img: true, 144 | html: false, 145 | always_send: false 146 | }, 147 | { 148 | name: 'video', 149 | label: 'Video', 150 | type: 'files', 151 | icon: 'img/icons/circle-icons/film.svg', 152 | adding: '', 153 | adding_option: '', 154 | options: [], 155 | custom: false, 156 | shown: false, 157 | preview_img: false, 158 | html: false, 159 | always_send: false 160 | }, 161 | { 162 | name: 'audio', 163 | label: 'Audio', 164 | type: 'files', 165 | icon: 'img/icons/circle-icons/audio.svg', 166 | adding: '', 167 | adding_option: '', 168 | options: [], 169 | custom: false, 170 | shown: false, 171 | preview_img: false, 172 | html: false, 173 | always_send: false 174 | }, 175 | { 176 | name: 'like-of', 177 | label: 'Like of', 178 | type: 'string', 179 | icon: 'img/icons/circle-icons/heart.svg', 180 | adding: '', 181 | adding_option: '', 182 | options: [], 183 | custom: false, 184 | shown: false, 185 | preview_img: false, 186 | html: false, 187 | always_send: false 188 | }, 189 | { 190 | name: 'summary', 191 | label: 'Summary', 192 | type: 'string', 193 | icon: 'img/icons/circle-icons/document.svg', 194 | adding: '', 195 | adding_option: '', 196 | options: [], 197 | custom: false, 198 | shown: false, 199 | preview_img: false, 200 | html: false, 201 | always_send: false 202 | }, 203 | { 204 | name: 'slug', 205 | label: 'Slug', 206 | type: 'string', 207 | icon: 'img/icons/circle-icons/search.svg', 208 | adding: '', 209 | adding_option: '', 210 | options: [], 211 | custom: false, 212 | shown: false, 213 | preview_img: false, 214 | html: false, 215 | always_send: false 216 | }, 217 | { 218 | name: 'repost-of', 219 | label: 'Repost Of URL', 220 | type: 'string', 221 | icon: 'img/icons/circle-icons/recycle.svg', 222 | adding: '', 223 | adding_option: '', 224 | options: [], 225 | custom: false, 226 | shown: false, 227 | preview_img: false, 228 | html: false, 229 | always_send: false 230 | }, 231 | { 232 | name: 'bookmark-of', 233 | label: 'Bookmark URL', 234 | type: 'string', 235 | icon: 'img/icons/circle-icons/bookmark.svg', 236 | adding: '', 237 | adding_option: '', 238 | options: [], 239 | custom: false, 240 | shown: false, 241 | preview_img: false, 242 | html: false, 243 | always_send: false 244 | }, 245 | { 246 | name: 'tag-of', 247 | label: 'Tag of', 248 | type: 'list', 249 | icon: 'img/icons/circle-icons/person.svg', 250 | adding: '', 251 | adding_option: '', 252 | options: [], 253 | custom: false, 254 | shown: false, 255 | preview_img: false, 256 | html: false, 257 | always_send: false 258 | }, 259 | { 260 | name: 'location-name', 261 | label: 'Location Name', 262 | type: 'string', 263 | icon: 'img/icons/circle-icons/location.svg', 264 | adding: '', 265 | adding_option: '', 266 | options: [], 267 | custom: true, 268 | shown: false, 269 | preview_img: false, 270 | html: false, 271 | always_send: false 272 | }, 273 | { 274 | label: 'Type', 275 | name: 'mp-type', 276 | type: 'select', 277 | icon: 'img/icons/circle-icons/settings.svg', 278 | adding: '', 279 | adding_option: '', 280 | options: ['note','checkin'], 281 | custom: true, 282 | shown: false, 283 | preview_img: false, 284 | html: false, 285 | always_send: false 286 | } 287 | ]; 288 | 289 | this.preset_scope = 'create'; 290 | 291 | if(!window.localStorage.getItem('settings_default_post')){ 292 | window.localStorage.setItem('settings_default_post', JSON.stringify(this.preset_post)); 293 | } 294 | if(!window.localStorage.getItem('settings_default_post_config')){ 295 | window.localStorage.setItem('settings_default_post_config', JSON.stringify(this.preset_fields)); 296 | } 297 | 298 | if(!window.localStorage.getItem('settings_scope')){ 299 | window.localStorage.setItem('settings_scope', JSON.stringify(this.preset_scope)); 300 | } 301 | } 302 | 303 | 304 | 305 | get(key){ 306 | if(window.localStorage.getItem('settings_' + key)){ 307 | return JSON.parse(window.localStorage.getItem('settings_' + key)); 308 | } else { 309 | if(this.hasOwnProperty(key)){ 310 | return this[key]; 311 | } else { 312 | return null; 313 | } 314 | } 315 | } 316 | 317 | set(key, value){ 318 | window.localStorage.setItem('settings_' + key, JSON.stringify(value)); 319 | } 320 | 321 | 322 | reset(){ 323 | window.localStorage.setItem('settings_default_post', JSON.stringify(this.preset_post)); 324 | window.localStorage.setItem('settings_default_post_config', JSON.stringify(this.preset_fields)); 325 | window.localStorage.setItem('settings_scope', JSON.stringify(this.preset_scope)); 326 | } 327 | 328 | 329 | 330 | } 331 | -------------------------------------------------------------------------------- /src/delete.html: -------------------------------------------------------------------------------- 1 | 56 | -------------------------------------------------------------------------------- /src/delete.js: -------------------------------------------------------------------------------- 1 | import {Config} from './config'; 2 | import {MicropubAPI} from './micropub'; 3 | import {areEqual} from './utility'; 4 | import {Router} from 'aurelia-router'; 5 | 6 | export class EditDetails { 7 | static inject() { return [Router, MicropubAPI, Config]; } 8 | 9 | 10 | constructor(Router, MicropubAPI, Config){ 11 | this.config = Config; 12 | this.mp = MicropubAPI; 13 | this.router = Router; 14 | this.notifications = []; 15 | this.notifications_id= 1; 16 | 17 | this.user = this.mp.get_user(); 18 | 19 | this.url = ''; 20 | 21 | } 22 | 23 | clearPostData(){ 24 | this.url = ''; 25 | } 26 | 27 | blankPost(){ 28 | this.clearPostData(); 29 | } 30 | 31 | 32 | activate(params, routeConfig) { 33 | this.routeConfig = routeConfig; 34 | 35 | if(params.url){ 36 | this.url = params.url; 37 | } 38 | return true; 39 | } 40 | 41 | 42 | clearPostConfirm(){ 43 | if (!areEqual(this.originalPost, this.post)){ 44 | if( confirm('Are you sure you want to clear the url?')) { 45 | this.blankPost(); 46 | } 47 | } 48 | } 49 | 50 | canDeactivate() { 51 | if (this.url){ 52 | return confirm('You have not submitted. Are you sure you wish to leave?'); 53 | } 54 | 55 | return true; 56 | } 57 | 58 | 59 | doPost(){ 60 | if(this.url){ 61 | this.mp.send_delete(this.url).then(data => { 62 | console.log(data); 63 | this.addNotification("Post Deleted", data); 64 | this.blankPost(); 65 | }).catch(error => { 66 | console.log(error); 67 | }); 68 | } 69 | } 70 | 71 | 72 | 73 | 74 | canPost() { 75 | //return true; 76 | return navigator.onLine; 77 | } 78 | 79 | addNotification(message, url){ 80 | this.notifications.push({id:this.notifications_id, msg: message, url:url}); 81 | this.notifications_id += 1; 82 | } 83 | 84 | delNotification(id){ 85 | for(var i = 0; i < this.notifications.length; i++){ 86 | if(this.notifications[i].id == id){ 87 | this.notifications.splice(i,1); 88 | break; 89 | } 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/edit.html: -------------------------------------------------------------------------------- 1 | 127 | -------------------------------------------------------------------------------- /src/edit.js: -------------------------------------------------------------------------------- 1 | import {Config} from './config'; 2 | import {MicropubAPI} from './micropub'; 3 | import {areEqual} from './utility'; 4 | import {Router} from 'aurelia-router'; 5 | 6 | export class EditDetails { 7 | static inject() { return [Router, MicropubAPI, Config]; } 8 | 9 | 10 | constructor(Router, MicropubAPI, Config){ 11 | this.config = Config; 12 | this.mp = MicropubAPI; 13 | this.router = Router; 14 | this.notifications = []; 15 | this.notifications_id= 1; 16 | 17 | this.user = this.mp.get_user(); 18 | 19 | this.post_config = this.config.get('default_post_config'); 20 | 21 | //this.syndicate_tos = []; 22 | 23 | /* 24 | this.mp_configs = null 25 | this.mp.get_configs().then(data => { 26 | if(!data){ 27 | this.mp.get_configs(true,true).then(data => { 28 | this.mp_configs = data; 29 | }).catch(error => { 30 | console.log(error); 31 | }); 32 | } 33 | this.mp_configs = data; 34 | }).catch(error => { 35 | this.mp.get_configs(true,true).then(data => { 36 | this.mp_configs = data; 37 | }).catch(error => { 38 | console.log(error); 39 | }); 40 | }); 41 | */ 42 | } 43 | 44 | clearPostData(){ 45 | this.post = JSON.parse(JSON.stringify(this.originalPost)); 46 | } 47 | 48 | blankPost(){ 49 | this.post_config = JSON.parse(JSON.stringify(this.originalPostConfig)); 50 | this.clearPostData(); 51 | } 52 | 53 | 54 | activate(params, routeConfig) { 55 | this.routeConfig = routeConfig; 56 | 57 | var field_names = ''; 58 | if(params.properties){ 59 | field_names = params.properties 60 | } 61 | //this.toggleField('content'); 62 | // 63 | 64 | if(params.url){ 65 | 66 | this.post = {}; 67 | this.url = params.url; 68 | 69 | this.mp.fetch_source(params.url, field_names).then(data => { 70 | //console.log('sucessfully recalled ' + JSON.stringify(data)); 71 | var keys = Object.keys(data['properties']); 72 | 73 | //console.log(JSON.stringify(keys)); 74 | //console.log(JSON.stringify(this.post_config)); 75 | 76 | this.post = {}; 77 | for(var i = 0; i < this.post_config.length; i++){ 78 | this.post_config[i].shown = false; 79 | } 80 | 81 | for(var i = 0; i < keys.length; i++){ 82 | var field_name = keys[i]; 83 | 84 | var entry = data['properties'][field_name]; 85 | 86 | //TODO if this is an array of one item (string field) we need to clean this up 87 | for(var j = 0; j < this.post_config.length; j++){ 88 | if(this.post_config[j].name == field_name){ 89 | this.post_config[j].shown = true; 90 | if( this.post_config[j].type == 'string' || 91 | this.post_config[j].type == 'text' || 92 | this.post_config[j].type == 'select') { 93 | 94 | if( typeof entry === 'object' && entry.constructor === Array){ 95 | entry = entry[0]; 96 | } 97 | } 98 | } 99 | } 100 | console.log(JSON.stringify(entry)); 101 | this.post[field_name] = entry; 102 | console.log(JSON.stringify(this.post[field_name])); 103 | console.log(JSON.stringify(this.post)); 104 | 105 | 106 | 107 | } 108 | //this.post = data['properties']; 109 | this.originalPost = JSON.parse(JSON.stringify(this.post)); 110 | this.originalPostConfig = JSON.parse(JSON.stringify(this.post_config)); 111 | }).catch( error => { 112 | console.log('routing away from edit, T1 ' + error); 113 | //TODO: some sort of session error message 114 | this.router.navigate('/post'); 115 | }); 116 | 117 | } else { 118 | console.log('routing away from edit, T2'); 119 | //TODO: some sort of session error message? 120 | this.router.navigate('/post'); 121 | } 122 | 123 | return true; 124 | } 125 | 126 | 127 | clearPostConfirm(){ 128 | if (!areEqual(this.originalPost, this.post)){ 129 | if( confirm('Are you sure you want to reset this edit?')) { 130 | this.blankPost(); 131 | } 132 | } 133 | } 134 | 135 | canDeactivate() { 136 | if (!areEqual(this.originalPost, this.post)){ 137 | return confirm('You have changes. Are you sure you wish to leave?'); 138 | } 139 | 140 | return true; 141 | } 142 | 143 | 144 | doPost(){ 145 | var edit_obj = this.prepForPost(); 146 | if(edit_obj){ 147 | this.mp.send_edit(edit_obj).then(data => { 148 | console.log(data); 149 | //this.addNotification("Post Updated", data); 150 | this.blankPost(); 151 | }).catch(error => { 152 | delete this.post.post_config; 153 | }); 154 | } 155 | } 156 | 157 | prepForPost() { 158 | 159 | var result_obj = { 160 | 'action': 'update', 161 | 'url': this.url 162 | }; 163 | 164 | var deletes = []; 165 | var removes = {}; 166 | var adds = {}; 167 | var replaces = {}; 168 | 169 | var deletes_tainted = false; 170 | var adds_tainted = false; 171 | var replaces_tainted = false; 172 | 173 | var only_array_replaces = true; 174 | 175 | //console.log(JSON.stringify(this.post_config)); 176 | for(var i = 0; i < this.post_config.length; i++){ 177 | var field_name = this.post_config[i].name; 178 | if(this.post_config[i].shown){ 179 | if(this.originalPost.hasOwnProperty(field_name)){ 180 | if(JSON.stringify(this.originalPost[field_name]) != JSON.stringify(this.post[field_name])){ 181 | if(this.post[field_name] == null || this.post[field_name] == '' || this.post[field_name] == []){ 182 | deletes_tainted = true; 183 | deletes.push(field_name); 184 | } else { 185 | replaces_tainted = true; 186 | if (typeof this.post[field_name] === 'object' && this.post[field_name].constructor === Array){ 187 | replaces[field_name] = this.post[field_name]; 188 | } else { 189 | replaces[field_name] = [this.post[field_name]]; 190 | only_array_replaces = false; 191 | } 192 | } 193 | } 194 | //might be updating 195 | } else { 196 | if(this.post[field_name] != null && this.post[field_name] != '' && this.post[field_name] != []){ 197 | adds_tainted = true; 198 | 199 | if (typeof this.post[field_name] === 'object' && this.post[field_name].constructor === Array){ 200 | adds[field_name] = this.post[field_name]; 201 | } else { 202 | adds[field_name] = [this.post[field_name]]; 203 | } 204 | } 205 | } 206 | 207 | } else if (this.originalPost.hasOwnProperty(field_name) ) { 208 | //item is not shown and the original post had it 209 | deletes_tainted = true; 210 | deletes.push(field_name); 211 | } 212 | } 213 | 214 | 215 | if(!deletes_tainted && !adds_tainted && !replaces_tainted){ 216 | return null; 217 | } 218 | if(!deletes_tainted && !adds_tainted && replaces_tainted && only_array_replaces){ 219 | var keys = Object.keys(replaces); 220 | for(var i = 0; i < keys.length; i++){ 221 | var field_name = keys[i]; 222 | var entry_list = replaces[field_name]; 223 | 224 | for(var j = 0; j < entry_list.length; j++){ 225 | var current_entry = entry_list[j]; 226 | var found = false; 227 | for(var k = 0; k < this.originalPost[field_name].length && !found ; k++){ 228 | var original_entry = this.originalPost[field_name][k]; 229 | if( original_entry == current_entry){ 230 | found = true; 231 | } 232 | } 233 | if(!found){ 234 | if(!adds.hasOwnProperty(field_name)){ 235 | adds[field_name] = []; 236 | } 237 | adds_tainted = true; 238 | adds[field_name].push(current_entry); 239 | } 240 | } 241 | 242 | 243 | for(var j = 0; j < this.originalPost[field_name].length ; j++){ 244 | var original_entry = this.originalPost[field_name][j]; 245 | console.log(original_entry); 246 | var found = false; 247 | for(var k = 0; k < entry_list.length && !found; k++){ 248 | var current_entry = entry_list[k]; 249 | if( original_entry == current_entry){ 250 | found = true; 251 | } 252 | } 253 | if(!found){ 254 | console.log("didn't find it"); 255 | if(!removes.hasOwnProperty(field_name)){ 256 | console.log('debug ' + field_name); 257 | removes[field_name] = []; 258 | } 259 | deletes_tainted = true; 260 | removes[field_name].push(original_entry); 261 | console.log(JSON.stringify(removes)); 262 | } 263 | } 264 | 265 | } 266 | if(deletes_tainted){ 267 | result_obj['delete'] = removes; 268 | } 269 | if(adds_tainted){ 270 | result_obj['add'] = adds; 271 | } 272 | 273 | } else { 274 | if(deletes_tainted){ 275 | result_obj['delete'] = deletes; 276 | } 277 | if(adds_tainted){ 278 | result_obj['add'] = adds; 279 | } 280 | if(replaces_tainted){ 281 | result_obj['replace'] = replaces; 282 | } 283 | } 284 | 285 | console.log(JSON.stringify(result_obj)); 286 | return result_obj; 287 | } 288 | 289 | toggleField(field_name){ 290 | for(var i = 0; i < this.post_config.length; i++){ 291 | if(this.post_config[i].name == field_name){ 292 | this.post_config[i].shown = !this.post_config[i].shown; 293 | break; 294 | } 295 | } 296 | } 297 | 298 | addListItem(field_name){ 299 | 300 | for(var i = 0; i < this.post_config.length; i++){ 301 | if(this.post_config[i].name == field_name){ 302 | if(this.post_config[i].adding != '' ){ 303 | if(this.post[field_name] == null){ 304 | this.post[field_name] = []; 305 | } 306 | this.post[field_name].push(this.post_config[i].adding); 307 | this.post_config[i].adding = ''; 308 | } 309 | break; 310 | } 311 | } 312 | } 313 | 314 | removeListItem(field_name, option){ 315 | 316 | if (this.post.hasOwnProperty(field_name) && typeof this.post[field_name] === 'object' && this.post[field_name].constructor === Array){ 317 | 318 | for(var i = 0; i < this.post[field_name].length; i++){ 319 | if(this.post[field_name][i] == option){ 320 | this.post[field_name].splice(i, 1); 321 | break; 322 | } 323 | } 324 | } 325 | } 326 | 327 | 328 | canPost() { 329 | //return true; 330 | return navigator.onLine; 331 | } 332 | 333 | getGeo(){ 334 | function setPos(position){ 335 | console.log( 'geo:'+position.coords.latitude + "," + position.coords.longitude); 336 | this.post.location = 'geo:'+position.coords.latitude + "," + position.coords.longitude 337 | } 338 | navigator.geolocation.getCurrentPosition(setPos.bind(this), 339 | function(message){ 340 | console.log(message); 341 | //error('error: ' + error.code + '\n' + 'message: ' + error.message + '\n'); 342 | },{ 343 | maximumAge: 3000, timeout: 5000, enableHighAccuracy: false 344 | } 345 | ); 346 | } 347 | 348 | addFile(field_name){ 349 | 350 | if(this.files[field_name]){ 351 | for(var i = 0; i < this.files[field_name].length; i++){ 352 | let itm = this.files[field_name].item(i); 353 | let fr = new FileReader(); 354 | console.log(itm); 355 | console.log(fr); 356 | fr.onload = function(e) { 357 | console.log(fr.result); 358 | this.post[field_name].push( {'src' : fr.result, 'size': itm.size, 'lastModified': itm.lastModified, 'type': itm.type, 'name': itm.name, 'alt': ''} ); 359 | }.bind(this); 360 | fr.readAsDataURL(itm); 361 | } 362 | this.files[field_name] = []; 363 | } 364 | } 365 | 366 | removeFile(field_name, index) { 367 | if(this.post[field_name] && this.post[field_name][index]) { 368 | this.post[field_name].splice(index, 1); 369 | } 370 | 371 | } 372 | 373 | } 374 | -------------------------------------------------------------------------------- /src/environment.js: -------------------------------------------------------------------------------- 1 | export default { 2 | debug: true, 3 | testing: true 4 | }; 5 | -------------------------------------------------------------------------------- /src/home.html: -------------------------------------------------------------------------------- 1 | 94 | -------------------------------------------------------------------------------- /src/home.js: -------------------------------------------------------------------------------- 1 | import {MicropubAPI} from './micropub'; 2 | 3 | export class Home{ 4 | 5 | static inject() { return [ MicropubAPI]; } 6 | 7 | 8 | constructor(MicropubAPI){ 9 | this.mp = MicropubAPI; 10 | this.saved_post_list = []; 11 | 12 | this.user = this.mp.get_user(); 13 | 14 | var list = this.mp.get_saved_list(); 15 | if(list){ 16 | for(var i = 0; i < list.length; i++) { 17 | var post = list[i]; 18 | 19 | post.discovered = { 20 | content: '', 21 | type: '', 22 | additional: '' 23 | } 24 | 25 | if(post.content){ 26 | post.discovered.content = post.content; //todo trim this? 27 | } else if (post.name){ 28 | post.discovered.content = post.name; 29 | } 30 | 31 | if(post['rsvp']){ 32 | post.discovered.type = 'RSVP'; 33 | post.discovered.additional = post.discovered.rsvp; 34 | } else if(post['in-reply-to']){ 35 | post.discovered.type = 'Reply'; 36 | post.discovered.additional = post['in-reply-to']; 37 | } else if(post['repost-of']){ 38 | post.discovered.type = 'Share'; 39 | post.discovered.additional = post['repost-of']; 40 | } else if(post['like-of']){ 41 | post.discovered.type = 'Like'; 42 | post.discovered.additional = post['like-of']; 43 | //} else if(post['video'].length != 0){ 44 | //post.discovered.type = 'Video'; 45 | ////todo 46 | //} else if(post['photo'].length != 0){ 47 | //post.discovered.type = 'Photo'; 48 | ////todo 49 | //} else if(post['audio'].length != 0){ 50 | //post.discovered.type = 'Audio'; 51 | ////todo 52 | } else { 53 | post.discovered.type = 'Note'; 54 | } 55 | 56 | 57 | 58 | post.index = i; 59 | this.saved_post_list.push(post); 60 | } 61 | } 62 | } 63 | 64 | logout(){ 65 | var mp = new MicropubAPI; 66 | mp.logout(); 67 | return true; 68 | } 69 | 70 | canPost() { 71 | return navigator.onLine; 72 | } 73 | 74 | postAll() { 75 | this.mp.send_all_saved(); 76 | this.saved_post_list = []; 77 | 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/login.html: -------------------------------------------------------------------------------- 1 | 93 | -------------------------------------------------------------------------------- /src/login.js: -------------------------------------------------------------------------------- 1 | import {MicropubAPI} from './micropub'; 2 | import {Router} from 'aurelia-router'; 3 | 4 | export class Login { 5 | static inject() { return [Router, MicropubAPI]; } 6 | 7 | constructor(Router, MicropubAPI){ 8 | this.debug_mode = false; 9 | this.loginUrl = ""; 10 | this.mp = MicropubAPI; 11 | this.router = Router; 12 | this.message = ""; 13 | } 14 | 15 | activate(params, routeConfig) { 16 | if(document.location.search){ 17 | document.location = document.location.pathname + '#/login' + document.location.search; 18 | } 19 | if(params.code || params.state ){ 20 | if(!params.code || !params.state ){ 21 | this.message = "There seems to have been an issue in the login process. Data needed to verify your login is missing."; 22 | } else { 23 | this.mp.auth(params.code, params.state) 24 | .then( data => { 25 | this.router.navigate('#/home'); 26 | }) 27 | .catch( error => { 28 | this.message = 'Login Error: ' + error.message; 29 | }); 30 | } 31 | } 32 | this.routeConfig = routeConfig; 33 | } 34 | 35 | submit_login(){ 36 | if(this.loginUrl){ 37 | if(this.debug_mode){ 38 | this.mp.login_test(this.loginUrl); 39 | window.location.href = '/'; 40 | } else { 41 | this.mp.login(this.loginUrl).then( url => { 42 | //console.log('good' + url ); 43 | window.location = url; 44 | }) 45 | .catch( error => { 46 | this.message = 'Login Error: ' + error.message; 47 | }); 48 | } 49 | } 50 | return true; 51 | } 52 | 53 | isOnline() { 54 | return this.debug_mode || navigator.onLine; 55 | } 56 | 57 | clearMessage(){ 58 | this.message = ''; 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import environment from './environment'; 2 | 3 | //Configure Bluebird Promises. 4 | //Note: You may want to use environment-specific configuration. 5 | Promise.config({ 6 | warnings: { 7 | wForgottenReturn: false 8 | } 9 | }); 10 | 11 | export function configure(aurelia) { 12 | aurelia.use 13 | .standardConfiguration() 14 | .feature('resources'); 15 | 16 | if (environment.debug) { 17 | aurelia.use.developmentLogging(); 18 | } 19 | 20 | if (environment.testing) { 21 | aurelia.use.plugin('aurelia-testing'); 22 | } 23 | 24 | aurelia.start().then(() => aurelia.setRoot()); 25 | } 26 | -------------------------------------------------------------------------------- /src/post.html: -------------------------------------------------------------------------------- 1 | 129 | -------------------------------------------------------------------------------- /src/post.js: -------------------------------------------------------------------------------- 1 | import {Config} from './config'; 2 | import {MicropubAPI} from './micropub'; 3 | import {areEqual} from './utility'; 4 | import {Router} from 'aurelia-router'; 5 | 6 | export class PostDetails { 7 | static inject() { return [Router, MicropubAPI, Config]; } 8 | 9 | //TODO for this class 10 | // add visual confirmation when things are saved, cleared, etc 11 | // add ability to actually submit posts 12 | // 13 | 14 | constructor(Router, MicropubAPI, Config){ 15 | this.config = Config; 16 | this.mp = MicropubAPI; 17 | this.router = Router; 18 | this.saved_index = -1; 19 | this.notifications = []; 20 | this.notifications_id= 1; 21 | 22 | this.user = this.mp.get_user(); 23 | 24 | this.default_post = this.config.get('default_post'); 25 | this.default_post_config = this.config.get('default_post_config'); 26 | 27 | this.post = JSON.parse(JSON.stringify(this.default_post)); 28 | this.post_config = JSON.parse(JSON.stringify(this.default_post_config)); 29 | 30 | var files = {}; 31 | 32 | for(let i = 0; i < this.post_config.length; i++){ 33 | if(this.post_config[i].type == 'files'){ 34 | files[this.post_config[i]['name']] = []; 35 | } 36 | } 37 | this.files = files; 38 | 39 | this.syndicate_tos = []; 40 | 41 | this.originalPost = JSON.parse(JSON.stringify(this.post)); 42 | 43 | this.mp_configs = null 44 | this.mp.get_configs().then(data => { 45 | if(!data){ 46 | this.mp.get_configs(true,true).then(data => { 47 | this.mp_configs = data; 48 | }).catch(error => { 49 | console.log(error); 50 | }); 51 | } 52 | this.mp_configs = data; 53 | }).catch(error => { 54 | this.mp.get_configs(true,true).then(data => { 55 | this.mp_configs = data; 56 | }).catch(error => { 57 | console.log(error); 58 | }); 59 | }); 60 | } 61 | 62 | clearPostData(){ 63 | this.saved_index = -1; 64 | this.post = JSON.parse(JSON.stringify(this.default_post)); 65 | this.originalPost = JSON.parse(JSON.stringify(this.post)); 66 | 67 | this.files = {}; 68 | 69 | for(var i = 0; i < this.post_config.length; i++){ 70 | if(this.post_config[i].type == 'files'){ 71 | this.files[this.post_config[i]['name']] = []; 72 | } 73 | } 74 | 75 | } 76 | 77 | blankPost(){ 78 | this.post_config = JSON.parse(JSON.stringify(this.default_post_config)); 79 | this.clearPostData(); 80 | } 81 | 82 | 83 | activate(params, routeConfig) { 84 | this.routeConfig = routeConfig; 85 | 86 | if(params.num){ 87 | var recalled = this.mp.recall_saved(params.num); 88 | if(recalled){ 89 | this.post = recalled; 90 | this.saved_index = params.num; 91 | this.post_config = this.post.post_config; 92 | delete this.post.post_config; 93 | this.syndicate_tos = this.post['mp-syndicate-to']; 94 | delete this.post['mp-syndicate-to']; 95 | this.originalPost = JSON.parse(JSON.stringify(this.post)); 96 | 97 | this.files = {}; 98 | for(var i = 0; i < this.post_config.length; i++){ 99 | if(this.post_config[i].type == 'files'){ 100 | this.files[this.post_config[i]['name']] = []; 101 | } 102 | } 103 | 104 | } else { 105 | this.blank_post(); //not needed? 106 | this.router.navigate('/post'); 107 | } 108 | } else { 109 | 110 | for (var key in params) { 111 | 112 | // skip loop if the property is from prototype 113 | if (!params.hasOwnProperty(key) || !this.post.hasOwnProperty(key)) continue; 114 | 115 | for(var i = 0; i < this.post_config.length; i++){ 116 | if(this.post_config[i].name == key){ 117 | 118 | if(this.post_config[i].type == 'list'){ 119 | this.post[key] = [ params[key] ]; 120 | } else { 121 | this.post[key] = params[key]; 122 | } 123 | 124 | this.post_config[i].shown = true; 125 | break; 126 | } 127 | } 128 | 129 | } 130 | 131 | } 132 | 133 | return true; 134 | } 135 | 136 | 137 | clearPostConfirm(){ 138 | if(this.saved_index > -1){ 139 | if( confirm('This will delete the saved copy. Are you sure?')) { 140 | this.mp.remove_saved(this.saved_index); 141 | this.blankPost(); 142 | this.router.navigate('/post'); 143 | } 144 | } else { 145 | if (!areEqual(this.originalPost, this.post)){ 146 | if( confirm('Are you sure you want to clear the post?')) { 147 | this.blankPost(); 148 | } 149 | } else { 150 | this.blankPost(); 151 | } 152 | 153 | } 154 | } 155 | 156 | canDeactivate() { 157 | if (!areEqual(this.originalPost, this.post)){ 158 | return confirm('You have unsaved changes. Are you sure you wish to leave?'); 159 | } 160 | 161 | return true; 162 | } 163 | 164 | save() { 165 | 166 | this.post.post_config = this.post_config; 167 | this.post['mp-syndicate-to'] = this.syndicate_tos; 168 | 169 | if(this.saved_index > -1){ 170 | this.mp.remove_saved(this.saved_index); 171 | this.saved_index = -1; 172 | } 173 | this.mp.save(this.post); 174 | 175 | this.blankPost(); 176 | this.router.navigate('/post'); 177 | 178 | } 179 | 180 | doPost(){ 181 | //TODO this needs some sort of loading UI 182 | if(this.saved_index > -1){ 183 | this.mp.remove_saved(this.saved_index); 184 | this.saved_index = -1; 185 | } 186 | this.post.post_config = this.post_config; 187 | this.post['mp-syndicate-to'] = this.syndicate_tos; 188 | this.mp.send(this.post).then(data => { 189 | //console.log(data); 190 | this.addNotification("New Post created", data); 191 | this.blankPost(); 192 | }).catch(error => { 193 | delete this.post.post_config; 194 | }); 195 | } 196 | 197 | toggleField(field_name){ 198 | for(var i = 0; i < this.post_config.length; i++){ 199 | if(this.post_config[i].name == field_name){ 200 | this.post_config[i].shown = !this.post_config[i].shown; 201 | break; 202 | } 203 | } 204 | } 205 | 206 | addListItem(field_name){ 207 | 208 | for(var i = 0; i < this.post_config.length; i++){ 209 | if(this.post_config[i].name == field_name){ 210 | if(this.post_config[i].adding != '' ){ 211 | this.post[this.post_config[i].name].push(this.post_config[i].adding); 212 | this.post_config[i].adding = ''; 213 | } 214 | break; 215 | } 216 | } 217 | } 218 | 219 | removeListItem(field_name, option){ 220 | 221 | if (this.post.hasOwnProperty(field_name) && typeof this.post[field_name] === 'object' && this.post[field_name].constructor === Array){ 222 | 223 | for(var i = 0; i < this.post[field_name].length; i++){ 224 | if(this.post[field_name][i] == option){ 225 | this.post[field_name].splice(i, 1); 226 | break; 227 | } 228 | } 229 | } 230 | } 231 | 232 | 233 | canPost() { 234 | //return true; 235 | return navigator.onLine; 236 | } 237 | 238 | addNotification(message, url){ 239 | this.notifications.push({id:this.notifications_id, msg: message, url:url}); 240 | this.notifications_id += 1; 241 | } 242 | 243 | delNotification(id){ 244 | for(var i = 0; i < this.notifications.length; i++){ 245 | if(this.notifications[i].id == id){ 246 | this.notifications.splice(i,1); 247 | break; 248 | } 249 | } 250 | } 251 | 252 | getGeo(){ 253 | function setPos(position){ 254 | console.log( 'geo:'+position.coords.latitude + "," + position.coords.longitude); 255 | this.post.location = 'geo:'+position.coords.latitude + "," + position.coords.longitude 256 | } 257 | 258 | 259 | navigator.geolocation.getCurrentPosition(setPos.bind(this), 260 | function(message){ 261 | console.log(message); 262 | //error('error: ' + error.code + '\n' + 'message: ' + error.message + '\n'); 263 | }, 264 | { 265 | maximumAge: 3000, timeout: 5000, enableHighAccuracy: false 266 | }); 267 | } 268 | 269 | addFile(field_name){ 270 | 271 | if(this.files[field_name]){ 272 | for(var i = 0; i < this.files[field_name].length; i++){ 273 | let itm = this.files[field_name].item(i); 274 | let fr = new FileReader(); 275 | console.log(itm); 276 | console.log(fr); 277 | fr.onload = function(e) { 278 | console.log(fr.result); 279 | this.post[field_name].push( {'src' : fr.result, 'size': itm.size, 'lastModified': itm.lastModified, 'type': itm.type, 'name': itm.name, 'alt': ''} ); 280 | }.bind(this); 281 | fr.readAsDataURL(itm); 282 | } 283 | this.files[field_name] = []; 284 | } 285 | } 286 | 287 | removeFile(field_name, index) { 288 | if(this.post[field_name] && this.post[field_name][index]) { 289 | this.post[field_name].splice(index, 1); 290 | } 291 | 292 | } 293 | 294 | } 295 | -------------------------------------------------------------------------------- /src/resources/index.js: -------------------------------------------------------------------------------- 1 | export function configure(config) { 2 | //config.globalResources([]); 3 | } 4 | -------------------------------------------------------------------------------- /src/settings.html: -------------------------------------------------------------------------------- 1 | 196 | -------------------------------------------------------------------------------- /src/settings.js: -------------------------------------------------------------------------------- 1 | import {Config} from './config'; 2 | import {areEqual} from './utility'; 3 | import {MicropubAPI} from './micropub'; 4 | 5 | export class Settings { 6 | static inject() { return [Config, MicropubAPI]; } 7 | 8 | 9 | constructor(Config, MicropubAPI){ 10 | this.config = Config; 11 | this.mp = MicropubAPI; 12 | 13 | this.settings = { 14 | scope: this.config.get('scope'), 15 | post_encoding: this.config.get('post_encoding'), 16 | default_post: this.config.get('default_post'), 17 | default_post_config: this.config.get('default_post_config'), 18 | } 19 | 20 | this.software_version = this.config.get('software_version'); 21 | 22 | this.user = this.mp.get_user(); 23 | 24 | this.mp_configs = null 25 | this.mp.get_configs().then(data => { 26 | if(!data){ 27 | this.mp.get_configs(true,true).then(data => { 28 | this.mp_configs = data; 29 | }).catch(error => { 30 | console.log(error); 31 | }); 32 | } 33 | this.mp_configs = data; 34 | }).catch(error => { 35 | this.mp.get_configs(true,true).then(data => { 36 | this.mp_configs = data; 37 | }).catch(error => { 38 | console.log(error); 39 | }); 40 | }); 41 | 42 | this.originalSettings = JSON.parse(JSON.stringify(this.settings)); 43 | } 44 | 45 | 46 | 47 | canDeactivate() { 48 | if (!areEqual(this.originalSettings, this.settings)){ 49 | return confirm('You have unsaved changes. Are you sure you wish to leave?'); 50 | } 51 | 52 | return true; 53 | } 54 | 55 | save() { 56 | for(var i = 0; i < this.settings.default_post_config.length; i++){ 57 | var field_name = this.settings.default_post_config[i].name; 58 | 59 | if(!this.settings.default_post.hasOwnProperty(field_name)){ 60 | if(this.settings.default_post_config[i].type == 'list'){ 61 | this.settings.default_post[field_name] = []; 62 | } else if(this.settings.default_post_config[i].type == 'select'){ 63 | this.settings.default_post[field_name] = this.settings.default_post_config[i].options[0]; 64 | } else { 65 | this.settings.default_post[field_name] = ''; 66 | } 67 | 68 | } 69 | } 70 | 71 | for(var key in this.settings){ 72 | console.log(key); 73 | 74 | this.config.set(key, this.settings[key]); 75 | } 76 | this.originalSettings = JSON.parse(JSON.stringify(this.settings)); 77 | } 78 | 79 | revert() { 80 | 81 | this.settings = JSON.parse(JSON.stringify(this.originalSettings)); 82 | 83 | } 84 | 85 | reset() { 86 | this.config.reset(); 87 | 88 | this.settings = { 89 | scope: this.config.get('scope'), 90 | post_encoding: this.config.get('post_encoding'), 91 | default_post: this.config.get('default_post'), 92 | default_post_config: this.config.get('default_post_config'), 93 | } 94 | 95 | this.originalSettings = JSON.parse(JSON.stringify(this.settings)); 96 | } 97 | 98 | add_field() { 99 | this.settings.default_post_config.push( this.config.get('field_template')); 100 | } 101 | 102 | remove_field(field_name) { 103 | for(var i = 0; i < this.settings.default_post_config.length; i++){ 104 | if(this.settings.default_post_config[i].name == field_name){ 105 | if(this.settings.default_post_config[i].custom){ 106 | this.settings.default_post_config.splice(i,1); 107 | delete this.settings.default_post[field_name]; 108 | } 109 | 110 | break; 111 | } 112 | } 113 | 114 | } 115 | 116 | 117 | add_selection_item(field_name){ 118 | for(var i = 0; i < this.settings.default_post_config.length; i++){ 119 | if(this.settings.default_post_config[i].name == field_name){ 120 | 121 | var input_value = this.settings.default_post_config[i].adding_option.trim(); 122 | 123 | if( input_value != '' && this.settings.default_post_config[i].type == 'select' ) { 124 | 125 | this.settings.default_post_config[i].options.push(input_value); 126 | this.settings.default_post_config[i].adding_option = ''; 127 | 128 | if(this.settings.default_post_config[i].options.length == 0){ 129 | this.settings.default_post[field_name] = '';//should never happen 130 | } else { 131 | //always default to whatever is first in the list 132 | this.settings.default_post[field_name] = this.settings.default_post_config[i].options[0]; 133 | } 134 | } 135 | break; 136 | 137 | } 138 | } 139 | } 140 | 141 | 142 | remove_selection_item(field_name, option_val){ 143 | 144 | for(var i = 0; i < this.settings.default_post_config.length; i++){ 145 | if(this.settings.default_post_config[i].name == field_name){ 146 | 147 | if(this.settings.default_post_config[i].type == 'select' ){ 148 | 149 | for(var j = 0; j < this.settings.default_post_config[i].options.length; j++){ 150 | if(this.settings.default_post_config[i].options[j] == option_val){ 151 | 152 | this.settings.default_post_config[i].options.splice(j, 1); 153 | break; 154 | } 155 | } 156 | 157 | if(this.settings.default_post_config[i].options.length == 0){ 158 | this.settings.default_post[field_name] = ''; 159 | } else { 160 | //always default to whatever is first in the list 161 | this.settings.default_post[field_name] = this.settings.default_post_config[i].options[0]; 162 | } 163 | } 164 | break; 165 | } 166 | 167 | } 168 | } 169 | 170 | update_mp_configs(){ 171 | this.mp.get_configs(true).then(data => { 172 | if(!data){ 173 | this.mp.get_configs(true,true).then(data => { 174 | this.mp_configs = data; 175 | }).catch(error => { 176 | console.log(error); 177 | }); 178 | } 179 | this.mp_configs = data; 180 | }).catch(error => { 181 | this.mp.get_configs(true,true).then(data => { 182 | this.mp_configs = data; 183 | }).catch(error => { 184 | console.log(error); 185 | }); 186 | }); 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /src/styles.old: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 16 |
17 |
18 | 19 | .switch { 20 | position:relative; 21 | input { display:none; } 22 | 23 | .slider { 24 | background-color:grey; 25 | position:relative; 26 | display:block; 27 | overflow:hidden; 28 | font-weight:bold; 29 | color:black; 30 | border-radius:30px; 31 | border:4px solid grey; 32 | transition: .4s; 33 | height:1.7em; 34 | } 35 | 36 | input + .slider > .slider-on { 37 | color:gray; 38 | background-color:white; 39 | border-radius:10px; 40 | width:40%; 41 | overflow:hidden; 42 | margin-left:-25%; 43 | transition: .4s; 44 | } 45 | input + .slider>.slider-off { 46 | margin-left : 15%; 47 | } 48 | 49 | input:checked + .slider { 50 | background-color:green; 51 | border:4px solid green; 52 | transition: .4s; 53 | } 54 | 55 | input:checked + .slider>.slider-on { 56 | color:black; 57 | margin-left:20%; 58 | transition: .4s; 59 | } 60 | input:checked + .slider>.slider-off { 61 | width:10px; 62 | } 63 | 64 | } 65 | 66 | input, textarea{ 67 | margin:5px; 68 | border-radius: 15px; 69 | min-height:30px; 70 | padding:7px 12px; 71 | } 72 | 73 | .screen-main { 74 | background-color: $secondary-bg-color; 75 | position:relative; 76 | background: none repeat scroll 0 0 $secondary-bg-color; 77 | //border-radius: 32px; 78 | margin:auto 1%; 79 | 80 | //box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 81 | 82 | max-width:98%; 83 | border-color: $secondary-bg-color; 84 | list-style: outside none none; 85 | color:black; 86 | padding: 20px 3%; 87 | text-align:center; 88 | } 89 | 90 | 91 | .onoffswitch { 92 | position: relative; width: 90px; 93 | -webkit-user-select:none; -moz-user-select:none; -ms-user-select: none; 94 | } 95 | .onoffswitch-checkbox { 96 | display: none; 97 | } 98 | .onoffswitch-label { 99 | display: block; overflow: hidden; cursor: pointer; 100 | border: 2px solid #999999; border-radius: 20px; 101 | } 102 | .onoffswitch-inner { 103 | display: block; width: 200%; margin-left: -100%; 104 | -moz-transition: margin 0.3s ease-in 0s; -webkit-transition: margin 0.3s ease-in 0s; 105 | -o-transition: margin 0.3s ease-in 0s; transition: margin 0.3s ease-in 0s; 106 | } 107 | .onoffswitch-inner:before, .onoffswitch-inner:after { 108 | display: block; float: left; width: 50%; height: 30px; padding: 0; line-height: 30px; 109 | font-size: 14px; color: white; font-weight: bold; 110 | -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; 111 | } 112 | .onoffswitch-inner:before { 113 | content: "ON"; 114 | padding-left: 10px; 115 | background-color: #34A7C1; color: #FFFFFF; 116 | } 117 | .onoffswitch-inner:after { 118 | content: "OFF"; 119 | padding-right: 10px; 120 | background-color: #EEEEEE; color: #999999; 121 | text-align: right; 122 | } 123 | .onoffswitch-switch { 124 | display: block; width: 18px; margin: 6px; 125 | background: #FFFFFF; 126 | border: 2px solid #999999; border-radius: 20px; 127 | position: absolute; top: 0; bottom: 0; right: 56px; 128 | -moz-transition: all 0.3s ease-in 0s; -webkit-transition: all 0.3s ease-in 0s; 129 | -o-transition: all 0.3s ease-in 0s; transition: all 0.3s ease-in 0s; 130 | } 131 | .setting div input{ 132 | display: block; 133 | background: #FFFFFF; 134 | border: 2px solid #999999; border-radius: 20px; 135 | text-align:center; 136 | height:30px; 137 | } 138 | .onoffswitch.on .onoffswitch-label .onoffswitch-inner { 139 | margin-left: 0; 140 | } 141 | .onoffswitch.on .onoffswitch-label .onoffswitch-switch { 142 | right: 0px; 143 | } 144 | .setting{ 145 | position:relative; 146 | height: 35px; 147 | margin-bottom:20px; 148 | } 149 | .setting>div {float:left;} 150 | .setting>div.switch-desc{ 151 | margin-top:auto; 152 | margin-bottom:auto; 153 | height: 30px; padding: 0; line-height: 35px; 154 | font-size: 14px; color: black; font-weight: bold; 155 | margin-right:10px; 156 | } 157 | #PhotoPreview { 158 | width:90%; 159 | } 160 | #success div { 161 | background-color:#8ce196; 162 | border-radius:10px; 163 | padding: 5px 20px; 164 | } 165 | #error div { 166 | background-color:#e18c96; 167 | border-radius:10px; 168 | padding: 5px 20px; 169 | } 170 | #input-syndication-wrapper{ 171 | text-align:left; 172 | margin-left:20%; 173 | } 174 | #input-syndication-wrapper > div{ 175 | padding:4px; 176 | } 177 | .switch input { 178 | position: absolute; 179 | margin-left: -9999px; 180 | visibility: hidden; 181 | } 182 | .switch input + label { 183 | display: inline-block; 184 | position: relative; 185 | cursor: pointer; 186 | outline: none; 187 | user-select: none; 188 | } 189 | .switch input + label { 190 | padding: 2px; 191 | height: 30px; 192 | } 193 | .switch input + label:before, 194 | .switch input + label:after { 195 | display: inline-block; 196 | position: absolute; 197 | top: 1px; 198 | left: -60px; 199 | bottom: 1px; 200 | content: ""; 201 | } 202 | .switch input + label:before { 203 | right: 1px; 204 | background-color: #f1f1f1; 205 | border-radius: 30px; 206 | background-color: #dddddd; 207 | transition: background 0.4s; 208 | width:60px; 209 | } 210 | .switch input + label:after { 211 | width: 28px; 212 | background-color: #fff; 213 | border-radius: 100%; 214 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); 215 | transition: margin 0.4s; 216 | } 217 | .switch input:checked + label:before { 218 | background-color: #8ce196; 219 | } 220 | .switch input:checked + label:after { 221 | margin-left: 30px; 222 | } 223 | #control-buttons button { 224 | background-color: #8ce196; 225 | border-color: #8ce196; 226 | border-radius: 30px; 227 | height:30px; 228 | min-width:60px; 229 | margin-right:20px; 230 | cursor:pointer; 231 | } 232 | #control-buttons button[disabled] { 233 | background-color: lightgray; 234 | border-color: lightgray; 235 | } 236 | 237 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | //$secondary-bg-color: #e3e3e3; 2 | $secondary-bg-color: #a2d2d2; 3 | $card-bg-color: #FFF; 4 | 5 | $icons-active-fill: white; 6 | $icons-inactive-fill: #aaa; 7 | $icons-inactive-bg: gray; 8 | 9 | $first-palette-white: #e9fafb; 10 | $first-palette-bright: #56b5ba; 11 | $first-palette-dark: #075457; 12 | $first-palette-black: #000a0b; 13 | 14 | $second-palette-white: #f2feeb; 15 | $second-palette-bright: #9ae96b; 16 | $second-palette-dark: #753e09; 17 | $second-palette-black: #061000; 18 | 19 | $third-palette-white: #ecedfc; 20 | $third-palette-bright: #6d6fca; 21 | $third-palette-dark: #151765; 22 | $third-palette-black: #00010c; 23 | 24 | @import 'https://fonts.googleapis.com/css?family=Open+Sans:600|Source+Sans+Pro:600,400|Roboto:400'; 25 | 26 | html { 27 | -webkit-font-smoothing: antialiased; 28 | -moz-osx-font-smoothing: grayscale; 29 | } 30 | 31 | h1 { 32 | font-family: "Open Sans", sans-serif; 33 | font-size: 34px; 34 | font-weight: 600; 35 | line-height: 1.30; 36 | opacity: 1.00; 37 | margin-top: 0px; 38 | margin-bottom: 10px; 39 | letter-spacing: -0.72px; 40 | word-spacing: 0.00px; 41 | text-transform: none; 42 | } 43 | 44 | h2 { 45 | font-family: "Source Sans Pro", sans-serif; 46 | font-size: 28px; 47 | font-weight: 600; 48 | line-height: 1.25; 49 | opacity: 1.00; 50 | margin-top: 30px; 51 | margin-bottom: 5px; 52 | letter-spacing: -0.72px; 53 | word-spacing: 0.00px; 54 | text-transform: none; 55 | } 56 | 57 | h3 { 58 | font-family: "Source Sans Pro", sans-serif; 59 | font-size: 22px; 60 | font-weight: 400; 61 | line-height: 1.20; 62 | opacity: 1.00; 63 | margin-top: 30px; 64 | margin-bottom: 5px; 65 | letter-spacing: 0.00px; 66 | word-spacing: 0.00px; 67 | text-transform: none; 68 | } 69 | 70 | p { 71 | font-family: "Source Sans Pro", sans-serif; 72 | font-size: 18px; 73 | font-weight: 400; 74 | line-height: 1.60; 75 | //opacity: 0.70; 76 | margin-top: 0px; 77 | margin-bottom: 25px; 78 | //letter-spacing: -0.30px; 79 | //ord-spacing: -0.10px; 80 | text-transform: none; 81 | } 82 | 83 | 84 | .left { 85 | text-align: left 86 | } 87 | .center { 88 | text-align: center 89 | } 90 | .right { 91 | text-align: right 92 | } 93 | 94 | .red { 95 | color:red; 96 | } 97 | 98 | * { 99 | box-sizing: border-box; 100 | } 101 | 102 | .logo { 103 | float:left; 104 | 105 | img { 106 | width:25px; 107 | } 108 | } 109 | 110 | body { 111 | color: $second-palette-black; 112 | background-color: $third-palette-white; 113 | margin:0; 114 | padding:0; 115 | border:0; 116 | 117 | header { 118 | background-color: $first-palette-bright; 119 | padding:1%; 120 | 121 | .homelogo { 122 | width:100px; 123 | } 124 | 125 | .back-icon { 126 | img { 127 | width:50px; 128 | float:left; 129 | } 130 | } 131 | 132 | .hometitle { 133 | text-align:center; 134 | text-shadow: 2px 2px rgba(0, 0, 0, 0.2); 135 | display:block; 136 | } 137 | 138 | .settings-title { 139 | display:block; 140 | margin-top:15px; 141 | } 142 | 143 | .title { 144 | text-align:center; 145 | text-shadow: 2px 2px rgba(0, 0, 0, 0.2); 146 | display:inline; 147 | } 148 | 149 | .user { 150 | float:right; 151 | height:30px; 152 | 153 | img { 154 | height:30px; 155 | width:30px; 156 | } 157 | 158 | span { 159 | height:30px; 160 | padding:6px; 161 | float:right; 162 | } 163 | } 164 | } 165 | 166 | .main-wrapper { 167 | margin-left:auto; 168 | margin-right:auto; 169 | padding:1%; 170 | } 171 | 172 | textarea, input{ 173 | width:100%; 174 | } 175 | textarea { 176 | min-height: 100px; 177 | } 178 | 179 | header { 180 | text-align:center; 181 | } 182 | 183 | .notify-window { 184 | position:fixed; 185 | width:20%; 186 | right:0; 187 | bottom:0; 188 | background-color: lighten($first-palette-bright, 30); 189 | border-radius:20px 0 0 0; 190 | 191 | padding: 10px 15px ; 192 | 193 | .notify { 194 | margin-bottom:4px; 195 | 196 | .close { 197 | font-size:10px; 198 | float:left; 199 | display:block; 200 | text-align:center; 201 | margin-right:10px; 202 | width:14px; 203 | height:14px; 204 | background-color:lighten($first-palette-dark,20); 205 | box-shadow: 1px 1px 1px 1px $first-palette-dark; 206 | cursor:pointer; 207 | } 208 | } 209 | } 210 | 211 | .error{ 212 | text-align:center; 213 | background-color:#ffefc6; 214 | border-radius:6px; 215 | padding:2px; 216 | } 217 | 218 | 219 | 220 | } 221 | 222 | #login_form{ 223 | input { 224 | width:79%; 225 | padding:5px 10px; 226 | border-radius: 5px; 227 | margin-right:0; 228 | } 229 | } 230 | 231 | .toggle_buttons .toggle_button { 232 | cursor:pointer; 233 | float:left; 234 | width:100%; 235 | width:100%; 236 | padding:3px; 237 | 238 | 239 | .icon.inactive { 240 | 241 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.2), 0 6px 2px 0 rgba(0, 0, 0, 0.19); 242 | fill:$icons-inactive-fill; 243 | background-color:$icons-inactive-bg; 244 | 245 | img { 246 | opacity:0.3; 247 | } 248 | } 249 | 250 | .icon.active { 251 | box-shadow: 0 2px 2px 0 $icons-active-fill, 0 6px 2px 0 $icons-active-fill; 252 | fill:$icons-active-fill; 253 | } 254 | .heart.active{ 255 | background-color:#C75C5C; 256 | } 257 | .reply.active{ 258 | background-color:#77B3D4; 259 | } 260 | .location.active{ 261 | background-color:#76c2af; 262 | } 263 | 264 | 265 | .label{ 266 | font-size: 8pt; 267 | overflow:hidden; 268 | text-align:center; 269 | width:100%; 270 | } 271 | .icon{ 272 | border-radius: 30px; 273 | width: 50px; 274 | height: 50px; 275 | margin-right:auto; 276 | margin-left:auto; 277 | } 278 | } 279 | 280 | .post_inputs{ 281 | input, textarea { 282 | padding:10px; 283 | border-radius:5px; 284 | } 285 | } 286 | 287 | 288 | //grid system 289 | .grid-container { 290 | width: 100%; 291 | max-width:1200px; 292 | 293 | .row:before, 294 | .row:after { 295 | content:""; 296 | display:table; 297 | clear:both; 298 | } 299 | 300 | .row { 301 | [class*='col-'] { 302 | float: left; 303 | min-height: 1px; 304 | /*-- gutter --*/ 305 | padding: 12px; 306 | } 307 | } 308 | 309 | .col-1 { width:8.33%; } 310 | .col-2 { width:16.67%; } 311 | .col-3 { width:25.00%; } 312 | .col-4 { width:33.33%; } 313 | .col-5 { width:41.67%; } 314 | .col-6 { width:50.00%; } 315 | .col-7 { width:58.33%; } 316 | .col-8 { width:66.67%; } 317 | .col-9 { width:75.00%; } 318 | .col-10 { width:83.33%; } 319 | .col-11 { width:91.67%; } 320 | .col-12 { width:100%; } 321 | 322 | } 323 | .list_values { 324 | list-style-type: none; 325 | .list_value { 326 | float:left; 327 | background-color:$first-palette-white; 328 | border-radius:3px; 329 | border: 1px solid $first-palette-bright; 330 | margin-right:5px; 331 | padding:3px 0; 332 | span { 333 | padding:4px 7px 4px 4px; 334 | } 335 | //TODO: make this an icon or something 336 | .list_item_remove { 337 | background-color:$first-palette-bright; 338 | padding:4px; 339 | height:10px; 340 | width:10px; 341 | overflow:hidden; 342 | cursor:pointer; 343 | } 344 | } 345 | } 346 | 347 | .upload_objects { 348 | list-style-type: none; 349 | li { 350 | float:left; 351 | background-color:$first-palette-white; 352 | border-radius:3px; 353 | border: 1px solid $first-palette-bright; 354 | margin-right:5px; 355 | padding:3px 3px 3px 0; 356 | text-align:center; 357 | div { 358 | font-weight:bold; 359 | .list_item_remove { 360 | background-color:$first-palette-bright; 361 | border-radius:3px 0; 362 | padding:4px; 363 | height:10px; 364 | width:10px; 365 | overflow:hidden; 366 | cursor:pointer; 367 | } 368 | } 369 | img { 370 | max-width:50px; 371 | max-height:50px; 372 | } 373 | } 374 | } 375 | 376 | 377 | .main-menu { 378 | .card { 379 | background-color: $card-bg-color; 380 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.2), 0 6px 2px 0 rgba(0, 0, 0, 0.19); 381 | //border-radius:20px; 382 | margin-top:14px; 383 | img { 384 | padding:15px; 385 | } 386 | .saved-card-data { 387 | height:100px; 388 | } 389 | .saved-menu-label { 390 | font-size:20pt; 391 | } 392 | .menu-label { 393 | font-size:20pt; 394 | margin-top:auto; 395 | margin-bottom:auto; 396 | display:block; 397 | text-align:center; 398 | margin-top:30px; 399 | } 400 | } 401 | 402 | .saved-card { 403 | overflow:hidden; 404 | img { 405 | width:45px; 406 | padding:1px; 407 | margin-left:15px; 408 | } 409 | } 410 | } 411 | 412 | .setting_box { 413 | background-color: $card-bg-color; 414 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.2), 0 6px 2px 0 rgba(0, 0, 0, 0.19); 415 | //border-radius:20px; 416 | margin:2%; 417 | margin-left:auto; 418 | margin-right:auto; 419 | } 420 | 421 | button { 422 | border-width: 1px; 423 | border-style: solid; 424 | background-color: $first-palette-bright; 425 | border-color: darken($first-palette-bright, 10); 426 | border-radius:4px; 427 | margin:2px; 428 | font-weight: 600; 429 | cursor:pointer; 430 | display:inline-block; 431 | font-size:14px; 432 | line-height: 1.4; 433 | padding:6px 12px; 434 | text-align:center; 435 | vertical-align:middle; 436 | white-space: nowrap; 437 | 438 | } 439 | button[disabled] { 440 | background-color: lighten($first-palette-bright, 20); 441 | border-color: lighten($first-palette-bright, 30); 442 | cursor:default; 443 | } 444 | 445 | 446 | 447 | 448 | @media all and (max-width:900px){ 449 | .grid-container { 450 | .col-1 { width: 16.67%; } 451 | .col-2 { width: 33.33%; } 452 | .col-3 { width: 50%; } 453 | .col-4 { width: 66.67%; } 454 | .col-5 { width: 83.33%; } 455 | .col-6 { width: 100%; } 456 | .col-7 { width: 100%; } 457 | .col-8 { width: 100%; } 458 | .col-9 { width: 100%; } 459 | .col-10 { width: 100%; } 460 | .col-11 { width: 100%; } 461 | .col-12 { width: 100%; } 462 | 463 | .sm-col-1 { width: 16.67%; } 464 | .sm-col-2 { width: 33.33%; } 465 | .sm-col-3 { width: 50%; } 466 | .sm-col-4 { width: 66.67%; } 467 | .sm-col-5 { width: 83.33%; } 468 | .sm-col-6 { width: 100%; } 469 | .sm-col-7 { width: 100%; } 470 | .sm-col-8 { width: 100%; } 471 | .sm-col-9 { width: 100%; } 472 | .sm-col-10 { width: 100%; } 473 | .sm-col-11 { width: 100%; } 474 | .sm-col-12 { width: 100%; } 475 | } 476 | 477 | body header .hometitle { 478 | font-size: 25pt; 479 | } 480 | } 481 | 482 | @media all and (max-width:600px){ 483 | .grid-container { 484 | .col-1 { width: 25%; } 485 | .col-2 { width: 50%; } 486 | .col-3 { width: 75%; } 487 | .col-4 { width: 100%; } 488 | .col-5 { width: 100%; } 489 | .col-6 { width: 100%; } 490 | .col-7 { width: 100%; } 491 | .col-8 { width: 100%; } 492 | .col-9 { width: 100%; } 493 | .col-10 { width: 100%; } 494 | .col-11 { width: 100%; } 495 | .col-12 { width: 100%; } 496 | 497 | .x-sm-col-1 { width: 25%; } 498 | .x-sm-col-2 { width: 50%; } 499 | .x-sm-col-3 { width: 75%; } 500 | .x-sm-col-4 { width: 100%; } 501 | .x-sm-col-5 { width: 100%; } 502 | .x-sm-col-6 { width: 100%; } 503 | .x-sm-col-7 { width: 100%; } 504 | .x-sm-col-8 { width: 100%; } 505 | .x-sm-col-9 { width: 100%; } 506 | .x-sm-col-10 { width: 100%; } 507 | .x-sm-col-11 { width: 100%; } 508 | .x-sm-col-12 { width: 100%; } 509 | } 510 | .right {text-align:center;} 511 | .left {text-align:center;} 512 | 513 | body header .hometitle { 514 | font-size: 20pt; 515 | } 516 | 517 | } 518 | 519 | -------------------------------------------------------------------------------- /src/undelete.html: -------------------------------------------------------------------------------- 1 | 56 | -------------------------------------------------------------------------------- /src/undelete.js: -------------------------------------------------------------------------------- 1 | import {Config} from './config'; 2 | import {MicropubAPI} from './micropub'; 3 | import {areEqual} from './utility'; 4 | import {Router} from 'aurelia-router'; 5 | 6 | export class EditDetails { 7 | static inject() { return [Router, MicropubAPI, Config]; } 8 | 9 | 10 | constructor(Router, MicropubAPI, Config){ 11 | this.config = Config; 12 | this.mp = MicropubAPI; 13 | this.router = Router; 14 | this.notifications = []; 15 | this.notifications_id= 1; 16 | 17 | this.user = this.mp.get_user(); 18 | 19 | this.url = ''; 20 | 21 | } 22 | 23 | clearPostData(){ 24 | this.url = ''; 25 | } 26 | 27 | blankPost(){ 28 | this.clearPostData(); 29 | } 30 | 31 | 32 | activate(params, routeConfig) { 33 | this.routeConfig = routeConfig; 34 | 35 | if(params.url){ 36 | this.url = params.url; 37 | } 38 | return true; 39 | } 40 | 41 | 42 | clearPostConfirm(){ 43 | if (!areEqual(this.originalPost, this.post)){ 44 | if( confirm('Are you sure you want to clear the url?')) { 45 | this.blankPost(); 46 | } 47 | } 48 | } 49 | 50 | canDeactivate() { 51 | if (this.url){ 52 | return confirm('You have not submitted. Are you sure you wish to leave?'); 53 | } 54 | 55 | return true; 56 | } 57 | 58 | 59 | doPost(){ 60 | if(this.url){ 61 | this.mp.send_undelete(this.url).then(data => { 62 | console.log(data); 63 | this.addNotification("Post Undeleted", data); 64 | this.blankPost(); 65 | }).catch(error => { 66 | console.log(error); 67 | }); 68 | } 69 | } 70 | 71 | 72 | 73 | 74 | canPost() { 75 | //return true; 76 | return navigator.onLine; 77 | } 78 | 79 | addNotification(message, url){ 80 | this.notifications.push({id:this.notifications_id, msg: message, url:url}); 81 | this.notifications_id += 1; 82 | } 83 | 84 | delNotification(id){ 85 | for(var i = 0; i < this.notifications.length; i++){ 86 | if(this.notifications[i].id == id){ 87 | this.notifications.splice(i,1); 88 | break; 89 | } 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/utility.js: -------------------------------------------------------------------------------- 1 | export function areEqual(obj1, obj2) { 2 | for( var key in obj1){ 3 | if(obj1.hasOwnProperty(key)){ 4 | if(!obj2.hasOwnProperty(key)){ 5 | return false; 6 | } 7 | if(typeof obj1[key] === 'string'){ 8 | if(obj1[key] !== obj2[key]){ 9 | return false; 10 | } 11 | } else if(typeof obj1[key] === 'object' && obj1[key].constructor === Array){ 12 | if(obj1[key].length != obj2[key].length){ 13 | return false; 14 | } 15 | for(var i = 0; i < obj1[key].length; i++){ 16 | if(!areEqual(obj1[key][i], obj2[key][i])){ 17 | return false; 18 | } 19 | } 20 | 21 | } else { 22 | if(!areEqual(obj1[key], obj2[key])){ 23 | return false; 24 | } 25 | } 26 | } 27 | } 28 | return true; 29 | 30 | }; 31 | 32 | export function getFormattedDate(){ 33 | var now = new Date(); 34 | 35 | var formatted_out = now.getFullYear() + "-"; 36 | 37 | // ugh zero indexed months 38 | if(now.getMonth() < 9){ 39 | formatted_out += "0"; 40 | } 41 | formatted_out += (now.getMonth()+1) + "-"; 42 | 43 | if(now.getDate() < 10){ 44 | formatted_out += "0"; 45 | } 46 | formatted_out += now.getDate() + "T"; 47 | 48 | if(now.getHours() < 10){ 49 | formatted_out += "0"; 50 | } 51 | formatted_out += now.getHours() + ":"; 52 | 53 | if(now.getMinutes() < 10){ 54 | formatted_out += "0"; 55 | } 56 | formatted_out += now.getMinutes() + ":"; 57 | 58 | if(now.getSeconds() < 10){ 59 | formatted_out += "0"; 60 | } 61 | formatted_out += now.getSeconds(); 62 | 63 | var tz_offset = now.getTimezoneOffset(); 64 | 65 | if(tz_offset > 0){ 66 | formatted_out += "-"; 67 | } else { 68 | formatted_out += "+"; 69 | } 70 | 71 | var offset_hours = Math.abs(tz_offset) / 60; 72 | var offset_mins = Math.abs(tz_offset) % 60; 73 | 74 | if(offset_hours < 10){ 75 | formatted_out += "0"; 76 | } 77 | formatted_out += offset_hours + ":"; 78 | 79 | 80 | if(offset_mins < 10){ 81 | formatted_out += "0"; 82 | } 83 | formatted_out += offset_mins ; 84 | 85 | return formatted_out 86 | }; 87 | 88 | export function serialize(obj) { 89 | var str = []; 90 | for(var p in obj) 91 | if (obj.hasOwnProperty(p)) { 92 | str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); 93 | } 94 | return str.join("&"); 95 | } 96 | -------------------------------------------------------------------------------- /styles.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": "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", 4 | "sources": ["src/styles.scss"], 5 | "names": [], 6 | "file": "styles.css" 7 | } 8 | -------------------------------------------------------------------------------- /test/aurelia-karma.js: -------------------------------------------------------------------------------- 1 | (function(global) { 2 | var karma = global.__karma__; 3 | var requirejs = global.requirejs 4 | var locationPathname = global.location.pathname; 5 | 6 | if (!karma || !requirejs) { 7 | return; 8 | } 9 | 10 | function normalizePath(path) { 11 | var normalized = [] 12 | var parts = path 13 | .split('?')[0] // cut off GET params, used by noext requirejs plugin 14 | .split('/') 15 | 16 | for (var i = 0; i < parts.length; i++) { 17 | if (parts[i] === '.') { 18 | continue 19 | } 20 | 21 | if (parts[i] === '..' && normalized.length && normalized[normalized.length - 1] !== '..') { 22 | normalized.pop() 23 | continue 24 | } 25 | 26 | normalized.push(parts[i]) 27 | } 28 | 29 | return normalized.join('/') 30 | } 31 | 32 | function patchRequireJS(files, originalLoadFn, locationPathname) { 33 | var IS_DEBUG = /debug\.html$/.test(locationPathname) 34 | 35 | requirejs.load = function (context, moduleName, url) { 36 | url = normalizePath(url) 37 | 38 | if (files.hasOwnProperty(url) && !IS_DEBUG) { 39 | url = url + '?' + files[url] 40 | } 41 | 42 | if (url.indexOf('/base') !== 0) { 43 | url = '/base/' + url; 44 | } 45 | 46 | return originalLoadFn.call(this, context, moduleName, url) 47 | } 48 | 49 | let originalDefine = global.define; 50 | global.define = function(name, deps, m) { 51 | if (typeof name === 'string') { 52 | originalDefine('/base/src/' + name, [name], function (result) { return result; }); 53 | } 54 | 55 | return originalDefine(name, deps, m); 56 | } 57 | } 58 | 59 | function requireTests() { 60 | var TEST_REGEXP = /(spec)\.js$/i; 61 | var allTestFiles = ['/base/test/unit/setup.js']; 62 | 63 | Object.keys(window.__karma__.files).forEach(function(file) { 64 | if (TEST_REGEXP.test(file)) { 65 | allTestFiles.push(file); 66 | } 67 | }); 68 | 69 | require(allTestFiles, window.__karma__.start); 70 | } 71 | 72 | karma.loaded = function() {}; // make it async 73 | patchRequireJS(karma.files, requirejs.load, locationPathname); 74 | requireTests(); 75 | })(window); 76 | -------------------------------------------------------------------------------- /test/unit/app.spec.js: -------------------------------------------------------------------------------- 1 | import {App} from '../../src/app'; 2 | 3 | describe('the app', () => { 4 | it('says hello', () => { 5 | expect(new App().message).toBe('Hello World!'); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /test/unit/setup.js: -------------------------------------------------------------------------------- 1 | import 'aurelia-polyfills'; 2 | import {initialize} from 'aurelia-pal-browser'; 3 | initialize(); 4 | -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | var CACHE_NAME = 'inkstone-v1.1.10'; 2 | var BASE_DIR = '/inkstone'; 3 | 4 | // The files we want to cache 5 | var urlsToCache = [ 6 | BASE_DIR + '/', 7 | BASE_DIR + '/manifest.json', 8 | 9 | BASE_DIR + '/img/png/inkstone144.png', 10 | BASE_DIR + '/img/png/inkstone192.png', 11 | BASE_DIR + '/img/svg/inkstone.svg', 12 | BASE_DIR + '/img/svg/inkling.svg', 13 | 14 | BASE_DIR + '/scripts/app-bundle.js', 15 | BASE_DIR + '/scripts/app-bundle.js.map', 16 | BASE_DIR + '/scripts/require.js', 17 | BASE_DIR + '/scripts/text.js', 18 | BASE_DIR + '/scripts/vendor-bundle.js', 19 | 20 | BASE_DIR + '/img/icons/circle-icons/audio.svg', 21 | BASE_DIR + '/img/icons/circle-icons/bookmark.svg', 22 | BASE_DIR + '/img/icons/circle-icons/bubble.svg', 23 | BASE_DIR + '/img/icons/circle-icons/calendar.svg', 24 | BASE_DIR + '/img/icons/circle-icons/check.svg', 25 | BASE_DIR + '/img/icons/circle-icons/document.svg', 26 | BASE_DIR + '/img/icons/circle-icons/film.svg', 27 | BASE_DIR + '/img/icons/circle-icons/heart.svg', 28 | BASE_DIR + '/img/icons/circle-icons/lens.svg', 29 | BASE_DIR + '/img/icons/circle-icons/location.svg', 30 | BASE_DIR + '/img/icons/circle-icons/memcard.svg', 31 | BASE_DIR + '/img/icons/circle-icons/mic.svg', 32 | BASE_DIR + '/img/icons/circle-icons/pencil.svg', 33 | BASE_DIR + '/img/icons/circle-icons/person.svg', 34 | BASE_DIR + '/img/icons/circle-icons/photo.svg', 35 | BASE_DIR + '/img/icons/circle-icons/power.svg', 36 | BASE_DIR + '/img/icons/circle-icons/recycle.svg', 37 | BASE_DIR + '/img/icons/circle-icons/search.svg', 38 | BASE_DIR + '/img/icons/circle-icons/settings.svg', 39 | BASE_DIR + '/img/icons/circle-icons/uparrow.svg' 40 | 41 | ]; 42 | 43 | // Set the callback for the install step 44 | self.addEventListener('install', function(event) { 45 | // Perform install steps 46 | event.waitUntil( 47 | caches.open(CACHE_NAME) 48 | .then(function(cache) { 49 | console.log('Opened cache'); 50 | return cache.addAll(urlsToCache); 51 | }) 52 | ); 53 | }); 54 | 55 | self.addEventListener('fetch', function(event) { 56 | event.respondWith( 57 | caches.match(event.request) 58 | .then(function(response) { 59 | // Cache hit - return response 60 | if (response) { 61 | return response; 62 | } 63 | 64 | return fetch(event.request); 65 | } 66 | ) 67 | ); 68 | }); 69 | 70 | self.addEventListener('activate', function(event) { 71 | 72 | event.waitUntil( 73 | caches.keys().then(function(cacheNames) { 74 | return Promise.all( 75 | cacheNames.map(function(cacheName) { 76 | if(cacheName !== CACHE_NAME) { 77 | return caches.delete(cacheName); 78 | } 79 | }) 80 | ); 81 | }) 82 | 83 | ); 84 | }); 85 | 86 | 87 | --------------------------------------------------------------------------------