Compare commits

..

No commits in common. "ad53c7b64b7c2339cb0ec7142ba748b07389d045" and "1d891ea734626135d76966e5f226725abfa668cb" have entirely different histories.

93 changed files with 11937 additions and 23618 deletions

6
.gitignore vendored
View File

@ -7,9 +7,3 @@ pids
*.seed *.seed
*.pid.lock *.pid.lock
.env .env
# exclude everything
staticFiles/*
# exception to the rule
!staticFiles/assets/

View File

@ -1,63 +0,0 @@
#!/bin/bash
echo '-------'
echo 'Starting Database Restore'
echo '-------'
#get Latest database backup
# Unzip File
# gzip -dk file.gz
BACKUPDIR="/home/mab/databaseBackupSolidScribe"
#DEVDBPASS="Crama!Lama*Jamma###88383!!!!!345345956245i"
#DEVDBPASS="RootPass1234!"
DEVDBPASS="ReallySecureRootPass123!"
# LazaLinga&33Can't!Do!That34
cd $BACKUPDIR
# -t sort by modification time, newest first
# -A --almost-all, do not list implied . and ..
LASTZIPPEDFILE=$(ls -At *.gz | head -n1)
# -k keep file after unzip
# -d Decompress
# -v verbose
echo "Unzipping $LASTZIPPEDFILE"
gunzip -dkv $LASTZIPPEDFILE
BACKUPFILE=$(ls -At *.sql | head -n1)
#Fix to replace incompatible DB type
echo "Updating table name in -> $BACKUPFILE"
#sed -i $BACKUPFILE -e 's/utf8mb4_0900_ai_ci/utf8mb4_unicode_ci/g'
#Fix encoding for dev DB and exclude system tables
sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' $BACKUPFILE
sed -r '/INSERT INTO `(sys|mysql)`/d' $BACKUPFILE > $BACKUPFILE
echo "Removing and syncing static files"
rm -r /home/mab/ss/staticFiles/*
rsync -e 'ssh -p 13328' -hazC --update mab@solidscribe.com:/home/mab/pi/staticFiles /home/mab/ss/
echo "Updating Database"
mysql -u root --password="$DEVDBPASS" < $BACKUPFILE
## Optimize Database Tables
# mysqlcheck --all-databases
mysqlcheck --all-databases -o -u root --password="$DEVDBPASS" --silent
# mysqlcheck --all-databases --auto-repair
# mysqlcheck --all-databases --analyze
# Fix an issues with DB after messing around with it
mysql_upgrade -u root --password="$DEVDBPASS"
#clean up extracted and modified SQL dumps
rm *.sql
echo '-------'
echo "Applied Prod database to Dev. LastFile: $BACKUPFILE"
echo '-------'

View File

@ -1,31 +1,18 @@
#!/bin/bash #!/bin/bash
# Take all variables in .env and turn them into local variables for this script BACKUPDIR="/home/mab/databaseBackupPi"
source ~/.env
BACKUPDIR="/home/mab/databaseBackupSolidScribe"
mkdir -p $BACKUPDIR mkdir -p $BACKUPDIR
cd $BACKUPDIR cd $BACKUPDIR
NOW=$(date +"%Y-%m-%d_%H-%M") NOW=$(date +"%Y-%m-%d_%H-%M")
ssh mab@solidscribe.com -p 13328 "mysqldump --all-databases --single-transaction --user root -p$PROD_DB_PASS" > "backup-$NOW.sql" ssh mab@solidscribe.com -p 13328 "mysqldump --all-databases --user root -pRootPass1234!" > "backup-$NOW.sql"
gzip "backup-$NOW.sql"
# cp "backup-$NOW.sql" "/mnt/Windows Data/DatabaseBackups/backup-$NOW.sql" cp "backup-$NOW.sql" "/mnt/Windows Data/DatabaseBackups/backup-$NOW.sql"
echo "Database Backup Complete on $NOW" echo "Database Backup Complete on $NOW"
##
#Restore DB #Restore DB
##
# copy file over, run restore # copy file over, run restore
# scp -P 13328 backup-2019-12-04_03-00.sql mab@avidhabit.com:/home/mab # scp -P 13328 backup-2019-12-04_03-00.sql mab@avidhabit.com:/home/mab
# mysql -u root -p < backup-2019-12-04_03-00.sql # mysql -u root -p < backup-2019-12-04_03-00.sql
##
# Crontab setup
##
# 0 2 * * * /bin/bash /home/mab/ss/backupDatabase.sh 1> /home/mab/databaseBackupLog.txt

12
client/.babelrc Normal file
View File

@ -0,0 +1,12 @@
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-vue-jsx", "transform-runtime"]
}

9
client/.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

16
client/.gitignore vendored
View File

@ -1,20 +1,9 @@
.DS_Store .DS_Store
node_modules node_modules/
/dist /dist/
# local env files
.env.local
.env.*.local
*.pem
*.crt
*.key
# Log files
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log*
# Editor directories and files # Editor directories and files
.idea .idea
@ -23,4 +12,3 @@ pnpm-debug.log*
*.ntvs* *.ntvs*
*.njsproj *.njsproj
*.sln *.sln
*.sw?

10
client/.postcssrc.js Normal file
View File

@ -0,0 +1,10 @@
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
"plugins": {
"postcss-import": {},
"postcss-url": {},
// to edit target browsers: use "browserslist" field in package.json
"autoprefixer": {}
}
}

View File

@ -1,19 +1,21 @@
# client # client2
## Project setup > client2
```
## Build Setup
``` bash
# install dependencies
npm install npm install
```
### Compiles and hot-reloads for development # serve with hot reload at localhost:8080
``` npm run dev
npm run serve
```
### Compiles and minifies for production # build for production with minification
```
npm run build npm run build
# build for production and view the bundle analyzer report
npm run build --report
``` ```
### Customize configuration For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

41
client/build/build.js Normal file
View File

@ -0,0 +1,41 @@
'use strict'
require('./check-versions')()
process.env.NODE_ENV = 'production'
const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')
const spinner = ora('building for production...')
spinner.start()
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, (err, stats) => {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
chunks: false,
chunkModules: false
}) + '\n\n')
if (stats.hasErrors()) {
console.log(chalk.red(' Build failed with errors.\n'))
process.exit(1)
}
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})

View File

@ -0,0 +1,54 @@
'use strict'
const chalk = require('chalk')
const semver = require('semver')
const packageConfig = require('../package.json')
const shell = require('shelljs')
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
const versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
]
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
const warnings = []
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (let i = 0; i < warnings.length; i++) {
const warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}

101
client/build/utils.js Normal file
View File

@ -0,0 +1,101 @@
'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const packageConfig = require('../package.json')
exports.assetsPath = function (_path) {
const assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
const output = []
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
exports.createNotifierCallback = () => {
const notifier = require('node-notifier')
return (severity, errors) => {
if (severity !== 'error') return
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
notifier.notify({
title: packageConfig.name,
message: severity + ': ' + error.name,
subtitle: filename || '',
icon: path.join(__dirname, 'logo.png')
})
}
}

View File

@ -0,0 +1,22 @@
'use strict'
const utils = require('./utils')
const config = require('../config')
const isProduction = process.env.NODE_ENV === 'production'
const sourceMapEnabled = isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap
module.exports = {
loaders: utils.cssLoaders({
sourceMap: sourceMapEnabled,
extract: isProduction
}),
cssSourceMap: sourceMapEnabled,
cacheBusting: config.dev.cacheBusting,
transformToRequire: {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href'
}
}

View File

@ -0,0 +1,82 @@
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
},
node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}

View File

@ -0,0 +1,96 @@
'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
const devWebpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
// cheap-module-eval-source-map is faster for development
devtool: config.dev.devtool,
// these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel: 'warning',
historyApiFallback: {
rewrites: [
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
],
},
disableHostCheck: true,
hot: true,
contentBase: false, // since we use CopyWebpackPlugin.
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,
quiet: true, // necessary for FriendlyErrorsPlugin
watchOptions: {
poll: config.dev.poll,
}
},
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// publish the new Port, necessary for e2e tests
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port
// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})

View File

@ -0,0 +1,145 @@
'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const env = require('../config/prod.env')
const webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true,
usePostCSS: true
})
},
devtool: config.build.productionSourceMap ? config.build.devtool : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
// Setting the following option to `false` will not extract CSS from codesplit chunks.
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
allChunks: true,
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// keep module.id stable when vendor modules does not change
new webpack.HashedModuleIdsPlugin(),
// enable scope hoisting
new webpack.optimize.ModuleConcatenationPlugin(),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks (module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig

7
client/config/dev.env.js Normal file
View File

@ -0,0 +1,7 @@
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})

69
client/config/index.js Normal file
View File

@ -0,0 +1,69 @@
'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.
const path = require('path')
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// Various Dev Server settings
host: '0.0.0.0', // can be overwritten by process.env.HOST
port: 8444, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
/**
* Source Maps
*/
// https://webpack.js.org/configuration/devtool/#development
devtool: 'cheap-module-eval-source-map',
// If you have problems debugging vue-files in devtools,
// set this to false - it *may* help
// https://vue-loader.vuejs.org/en/options.html#cachebusting
cacheBusting: true,
cssSourceMap: true
},
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
/**
* Source Maps
*/
productionSourceMap: true,
// https://webpack.js.org/configuration/devtool/#production
devtool: '#source-map',
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
}
}

View File

@ -0,0 +1,4 @@
'use strict'
module.exports = {
NODE_ENV: '"production"'
}

View File

@ -1,9 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="/api/static/assets/favicon.ico" type="image/ico"/> <link rel="icon" href="/api/static/assets/favicon.ico" type="image/ico"/>
<link rel="shortcut icon" href="/api/static/assets/favicon.ico" type="image/x-icon"/> <link rel="shortcut icon" href="/api/static/assets/favicon.ico" type="image/x-icon"/>
@ -12,20 +11,14 @@
<link rel="manifest" href="/api/static/assets/manifest.json"> <link rel="manifest" href="/api/static/assets/manifest.json">
<title>Solid Scribe - An easy, encrypted Note App</title> <title>Solid Scribe - An easy, encrypted Note App</title>
<!-- <title><%= htmlWebpackPlugin.options.title %></title> -->
</head> </head>
<body> <body>
<noscript>
<strong>We're sorry but Solid Scribe doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"> <div id="app">
<!-- placeholder data for scrapers with no JS --> <!-- placeholder data for scrapers with no JS -->
<style> <style>
body { body {
background-color: #212221; background-color: #212221;
color: #aeaeae; color: #aeaeae;
height: 100vh;
width: 100%;
} }
.centered { .centered {
position: fixed; position: fixed;

View File

@ -1,19 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

25513
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +1,66 @@
{ {
"name": "solidscribe", "name": "client2",
"version": "0.1.0", "version": "1.0.0",
"description": "client2",
"author": "max",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"build": "vue-cli-service build" "start": "npm run dev",
"build": "node build/build.js"
}, },
"dependencies": { "dependencies": {
"axios": "^1.1.3", "axios": "^0.19.2",
"core-js": "^3.6.5",
"es6-promise": "^4.2.8", "es6-promise": "^4.2.8",
"fomantic-ui-css": "^2.9.0", "fomantic-ui-css": "^2.8.6",
"vue": "^2.6.11", "vue": "^2.5.2",
"vue-chartjs": "^5.0.1", "vue-router": "^3.3.4",
"vue-router": "^3.2.0",
"vuedraggable": "^2.24.3",
"vuex": "^3.4.0" "vuex": "^3.4.0"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "^5.0.8", "autoprefixer": "^7.1.2",
"@vue/cli-plugin-router": "^5.0.8", "babel-core": "^6.22.1",
"@vue/cli-plugin-vuex": "^5.0.8", "babel-helper-vue-jsx-merge-props": "^2.0.3",
"@vue/cli-service": "^5.0.8", "babel-loader": "^7.1.1",
"vue-template-compiler": "^2.6.11" "babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-plugin-transform-vue-jsx": "^3.5.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"chalk": "^2.0.1",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.28.0",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^1.1.4",
"friendly-errors-webpack-plugin": "^1.6.1",
"html-webpack-plugin": "^2.30.1",
"node-notifier": "^5.1.2",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.2.0",
"portfinder": "^1.0.26",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.8",
"postcss-url": "^7.2.1",
"rimraf": "^2.6.0",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^0.5.8",
"vue-loader": "^13.3.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.5.2",
"webpack": "^3.6.0",
"webpack-bundle-analyzer": "^2.9.0",
"webpack-dev-server": "^2.9.1",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",
"last 2 versions", "last 2 versions",
"not dead" "not ie <= 8"
] ]
} }

View File

@ -1,2 +0,0 @@
User-agent: *
Disallow:

View File

@ -60,7 +60,6 @@
import axios from 'axios' import axios from 'axios'
export default { export default {
name: 'App',
components: { components: {
'global-site-menu': require('@/components/GlobalSiteMenu.vue').default, 'global-site-menu': require('@/components/GlobalSiteMenu.vue').default,
'global-notification':require('@/components/GlobalNotificationComponent.vue').default, 'global-notification':require('@/components/GlobalNotificationComponent.vue').default,
@ -200,11 +199,6 @@ export default {
this.blockUntilNextRequest = true this.blockUntilNextRequest = true
}) })
//Track users active sessions
this.$io.on('update_active_user_count', countData => {
this.$store.commit('setActiveSessions', countData)
})
}, },
computed: { computed: {
loggedIn () { loggedIn () {

View File

@ -53,7 +53,7 @@ helpers.timeAgo = (time) => {
if (typeof format[2] == 'string') { if (typeof format[2] == 'string') {
return format[list_choice] return format[list_choice]
} else { } else {
return Math.floor(seconds / format[2]) + ' ' + format[1] + ' ' + token return Math.floor(seconds / format[2]) + ' ' + format[1]// + ' ' + token
} }
} }
} }

View File

@ -3,7 +3,7 @@
font-family: 'Roboto'; font-family: 'Roboto';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(./roboto-latin.woff2) format('woff2'); src: local('Roboto'), local('Roboto-Regular'), url(/api/static/assets/roboto-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
} }
/* latin */ /* latin */
@ -11,20 +11,10 @@
font-family: 'Roboto'; font-family: 'Roboto';
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(./roboto-latin-bold.woff2) format('woff2'); src: local('Roboto Bold'), local('Roboto-Bold'), url(/api/static/assets/roboto-latin-bold.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
} }
body {
margin: 0;
padding: 0;
overflow-x: hidden;
min-width: 320px;
background: #FFFFFF;
font-family: 'Roboto', system-ui, -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 14px;
line-height: 1.4285em;
color: rgba(0, 0, 0, 0.87);
}
:root { :root {
@ -53,7 +43,6 @@ html {
height:100%; height:100%;
padding: 0; padding: 0;
margin: 0; margin: 0;
background: var(--body_bg_color);
} }
a:hover { a:hover {
text-decoration: underline; text-decoration: underline;
@ -91,12 +80,9 @@ div.ui.basic.segment.no-fluf-segment {
/* OVERWRITE DEFAULT SEMANTIC STYLES FOR CUSTOM/NIGHT MODES*/ /* OVERWRITE DEFAULT SEMANTIC STYLES FOR CUSTOM/NIGHT MODES*/
body { body {
color: var(--text_color); color: var(--text_color);
background: none; background-color: var(--body_bg_color);
font-family: 'Roboto', 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: 'Roboto', 'Helvetica Neue', Arial, Helvetica, sans-serif;
} }
#app {
background: var(--body_bg_color);
}
.ui.segment { .ui.segment {
color: var(--text_color); color: var(--text_color);
@ -146,9 +132,6 @@ body {
.ui.dividing.header { .ui.dividing.header {
border-bottom-color: var(--dark_border_color); border-bottom-color: var(--dark_border_color);
} }
.ui.dividing.header > .sub.header {
color: var(--dark_border_color);
}
.ui.icon.input > i.icon { .ui.icon.input > i.icon {
color: var(--text_color); color: var(--text_color);
} }
@ -177,21 +160,9 @@ div.ui.basic.green.label {
border-color: var(--dark_border_color) !important; border-color: var(--dark_border_color) !important;
} }
/*Overwrites for modifiable theme color */ /*Overwrites for modifiable theme color */
i.green.icon.icon.icon.icon, i.green.icon.icon.icon.icon.icon { i.green.icon.icon.icon.icon {
color: var(--main-accent); color: var(--main-accent);
} }
.button {
box-shadow: 2px 2px 4px -2px rgba(40, 40, 40, 0.89) !important;
transition: all 0.9s ease;
position: relative;
}
.button:hover {
box-shadow: 3px 2px 3px -2px rgba(40, 40, 40, 0.95) !important;
}
.button:active {
transform: translateY(1px);
}
.ui.green.buttons, .ui.green.button, .ui.green.button:hover { .ui.green.buttons, .ui.green.button, .ui.green.button:hover {
background-color: var(--main-accent); background-color: var(--main-accent);
} }
@ -205,9 +176,6 @@ i.green.icon.icon.icon.icon, i.green.icon.icon.icon.icon.icon {
.ui.grid > .green.row, .ui.grid > .green.column, .ui.grid > .row > .green.column { .ui.grid > .green.row, .ui.grid > .green.column, .ui.grid > .row > .green.column {
background-color: var(--main-accent); background-color: var(--main-accent);
} }
.ui.green.header {
color: var(--main-accent);
}
/* OVERWRITE DEFAULT SEMANTIC STYLES FOR CUSTOM/NIGHT MODES*/ /* OVERWRITE DEFAULT SEMANTIC STYLES FOR CUSTOM/NIGHT MODES*/
@ -312,7 +280,7 @@ i.green.icon.icon.icon.icon, i.green.icon.icon.icon.icon.icon {
border: none; border: none;
/*height: calc(100% - 69px);*/ /*height: calc(100% - 69px);*/
min-height: 300px; min-height: 500px;
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
/*margin-bottom: 15px;*/ /*margin-bottom: 15px;*/
@ -331,9 +299,6 @@ i.green.icon.icon.icon.icon, i.green.icon.icon.icon.icon.icon {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
max-width: 1100px; max-width: 1100px;
box-shadow: 0 8px 24px rgba(0,0,0,0.1);
} }
.squire-box::selection, .squire-box::selection,
.squire-box::-moz-selection { .squire-box::-moz-selection {
@ -355,14 +320,9 @@ i.green.icon.icon.icon.icon, i.green.icon.icon.icon.icon.icon {
background-color: rgba(255, 255, 255, 0.2); background-color: rgba(255, 255, 255, 0.2);
} }
.note-card-text code,
.squire-box code,
.note-card-text pre, .note-card-text pre,
.squire-box pre { .squire-box pre {
/*word-wrap: break-word;*/ /*word-wrap: break-word;*/
display: inline-block;
border-left: 2px solid var(--main-accent);
padding-left: 15px;
} }
.note-card-text p, .note-card-text p,
.squire-box p { .squire-box p {
@ -397,37 +357,8 @@ i.green.icon.icon.icon.icon, i.green.icon.icon.icon.icon.icon {
.squire-box ol, .squire-box ol,
.note-card-text ul, .note-card-text ul,
.squire-box ul { .squire-box ul {
margin: 3px 0; margin: 8px 0 0 0;
display: block;
} }
/* Add border 1 indent level */
.note-card-text > ol > ol,
.squire-box > ol > ol,
.note-card-text > ul > ul,
.squire-box > ul > ul
{
border-left: 1px solid var(--border_color);
}
.note-card-text ol > ol,
.squire-box ol > ol,
.note-card-text ul > ul,
.squire-box ul > ul {
list-style-type: upper-alpha;
}
ol {
counter-reset: item;
}
ol li {
display: block;
}
ol li:before {
content: counters(item, ".") ".";
counter-increment: item;
padding-right: 10px;
}
.note-card-text ul > li, .note-card-text ul > li,
.squire-box ul > li { .squire-box ul > li {
position: relative; position: relative;
@ -554,6 +485,10 @@ padding-right: 10px;
/* adjust checkboxes for mobile. Make them a little bigger, easier to click */ /* adjust checkboxes for mobile. Make them a little bigger, easier to click */
@media only screen and (max-width: 740px) { @media only screen and (max-width: 740px) {
.squire-box {
min-height: calc(100vh - 122px);
}
.ui.button.shrinking { .ui.button.shrinking {
font-size: 0.85714286rem; font-size: 0.85714286rem;
margin: 0 3px; margin: 0 3px;
@ -620,10 +555,6 @@ padding-right: 10px;
.ui.white.button { .ui.white.button {
background: #FFF; background: #FFF;
} }
.white.row {
background-color: rgba(255, 255, 255, 0.9);
}
.input-floating-button { .input-floating-button {
position: absolute; position: absolute;
top: 19px; top: 19px;
@ -935,59 +866,3 @@ padding-right: 10px;
-webkit-transform-origin: left center; -webkit-transform-origin: left center;
transform-origin: left center; transform-origin: left center;
} }
@media only screen and (max-width: 740px) {
/*hide tooltips on mobile*/
[data-tooltip]:hover:before,
[data-tooltip]:hover:after {
visibility: visible;
opacity: 0;
}
}
.glint:after {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 100%;
opacity: 0;
pointer-events: none;
z-index: 1;
background: linear-gradient(
130deg,
rgba(255,255,255,0) 45%,
rgba(255,255,255,1) 50%,
var(--main-accent) 55%,
rgba(255,255,255,0) 60%
);
animation: glint-animation 0.8s linear 1;
animation-delay: 0.9s;
}
@keyframes glint-animation {
0% {
left: -100%;
opacity: 1;
}
100% {
left: 100%;
opacity: 0;
}
}
.shade {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0,0,0,0.7);
z-index: 1000;
}

View File

@ -460,6 +460,7 @@ function fixContainer ( container, root ) {
var doc = container.ownerDocument; var doc = container.ownerDocument;
var wrapper = null; var wrapper = null;
var i, l, child, isBR; var i, l, child, isBR;
var config = root.__squire__._config;
for ( i = 0, l = children.length; i < l; i += 1 ) { for ( i = 0, l = children.length; i < l; i += 1 ) {
child = children[i]; child = children[i];
@ -1265,9 +1266,7 @@ var keys = {
37: 'left', 37: 'left',
39: 'right', 39: 'right',
46: 'delete', 46: 'delete',
191: '/',
219: '[', 219: '[',
220: '\\',
221: ']' 221: ']'
}; };
@ -1763,7 +1762,7 @@ var keyHandlers = {
} }
}, },
space: function ( self, _, range ) { space: function ( self, _, range ) {
var node; var node, parent;
var root = self._root; var root = self._root;
self._recordUndoState( range ); self._recordUndoState( range );
if ( self._config.addLinks ) { if ( self._config.addLinks ) {
@ -2117,7 +2116,7 @@ var cleanTree = function cleanTree ( node, config, preserveWS ) {
break; break;
} }
} }
data = data.replace( /^[ \r\n]+/g, sibling ? ' ' : '' ); data = data.replace( /^[ \t\r\n]+/g, sibling ? ' ' : '' );
} }
if ( endsWithWS ) { if ( endsWithWS ) {
walker.currentNode = child; walker.currentNode = child;
@ -2132,7 +2131,7 @@ var cleanTree = function cleanTree ( node, config, preserveWS ) {
break; break;
} }
} }
data = data.replace( /[ \r\n]+$/g, sibling ? ' ' : '' ); data = data.replace( /[ \t\r\n]+$/g, sibling ? ' ' : '' );
} }
if ( data ) { if ( data ) {
child.data = data; child.data = data;
@ -2226,35 +2225,26 @@ var cleanupBRs = function ( node, root, keepForBlankLine ) {
// The (non-standard but supported enough) innerText property is based on the // The (non-standard but supported enough) innerText property is based on the
// render tree in Firefox and possibly other browsers, so we must insert the // render tree in Firefox and possibly other browsers, so we must insert the
// DOM node into the document to ensure the text part is correct. // DOM node into the document to ensure the text part is correct.
var setClipboardData = var setClipboardData = function ( clipboardData, node, root, config ) {
function ( event, contents, root, willCutCopy, toPlainText, plainTextOnly ) { var body = node.ownerDocument.body;
var clipboardData = event.clipboardData; var willCutCopy = config.willCutCopy;
var doc = event.target.ownerDocument;
var body = doc.body;
var node = createElement( doc, 'div' );
var html, text; var html, text;
node.appendChild( contents ); // Firefox will add an extra new line for BRs at the end of block when
// calculating innerText, even though they don't actually affect display.
// So we need to remove them first.
cleanupBRs( node, root, true );
node.setAttribute( 'style',
'position:fixed;overflow:hidden;bottom:100%;right:100%;' );
body.appendChild( node );
html = node.innerHTML; html = node.innerHTML;
text = node.innerText || node.textContent;
if ( willCutCopy ) { if ( willCutCopy ) {
html = willCutCopy( html ); html = willCutCopy( html );
} }
if ( toPlainText ) {
text = toPlainText( html );
} else {
// Firefox will add an extra new line for BRs at the end of block when
// calculating innerText, even though they don't actually affect
// display, so we need to remove them first.
cleanupBRs( node, root, true );
node.setAttribute( 'style',
'position:fixed;overflow:hidden;bottom:100%;right:100%;' );
body.appendChild( node );
text = node.innerText || node.textContent;
text = text.replace( / /g, ' ' ); // Replace nbsp with regular space
body.removeChild( node );
}
// Firefox (and others?) returns unix line endings (\n) even on Windows. // Firefox (and others?) returns unix line endings (\n) even on Windows.
// If on Windows, normalise to \r\n, since Notepad and some other crappy // If on Windows, normalise to \r\n, since Notepad and some other crappy
// apps do not understand just \n. // apps do not understand just \n.
@ -2262,18 +2252,18 @@ var setClipboardData =
text = text.replace( /\r?\n/g, '\r\n' ); text = text.replace( /\r?\n/g, '\r\n' );
} }
if ( !plainTextOnly && text !== html ) {
clipboardData.setData( 'text/html', html ); clipboardData.setData( 'text/html', html );
}
clipboardData.setData( 'text/plain', text ); clipboardData.setData( 'text/plain', text );
event.preventDefault();
body.removeChild( node );
}; };
var onCut = function ( event ) { var onCut = function ( event ) {
var clipboardData = event.clipboardData;
var range = this.getSelection(); var range = this.getSelection();
var root = this._root; var root = this._root;
var self = this; var self = this;
var startBlock, endBlock, copyRoot, contents, parent, newContents; var startBlock, endBlock, copyRoot, contents, parent, newContents, node;
// Nothing to do // Nothing to do
if ( range.collapsed ) { if ( range.collapsed ) {
@ -2285,7 +2275,7 @@ var onCut = function ( event ) {
this.saveUndoState( range ); this.saveUndoState( range );
// Edge only seems to support setting plain text as of 2016-03-11. // Edge only seems to support setting plain text as of 2016-03-11.
if ( !isEdge && event.clipboardData ) { if ( !isEdge && clipboardData ) {
// Clipboard content should include all parents within block, or all // Clipboard content should include all parents within block, or all
// parents up to root if selection across blocks // parents up to root if selection across blocks
startBlock = getStartBlockOfRange( range, root ); startBlock = getStartBlockOfRange( range, root );
@ -2305,8 +2295,10 @@ var onCut = function ( event ) {
parent = parent.parentNode; parent = parent.parentNode;
} }
// Set clipboard data // Set clipboard data
setClipboardData( node = this.createElement( 'div' );
event, contents, root, this._config.willCutCopy, null, false ); node.appendChild( contents );
setClipboardData( clipboardData, node, root, this._config );
event.preventDefault();
} else { } else {
setTimeout( function () { setTimeout( function () {
try { try {
@ -2321,10 +2313,14 @@ var onCut = function ( event ) {
this.setSelection( range ); this.setSelection( range );
}; };
var _onCopy = function ( event, range, root, willCutCopy, toPlainText, plainTextOnly ) { var onCopy = function ( event ) {
var startBlock, endBlock, copyRoot, contents, parent, newContents; var clipboardData = event.clipboardData;
var range = this.getSelection();
var root = this._root;
var startBlock, endBlock, copyRoot, contents, parent, newContents, node;
// Edge only seems to support setting plain text as of 2016-03-11. // Edge only seems to support setting plain text as of 2016-03-11.
if ( !isEdge && event.clipboardData ) { if ( !isEdge && clipboardData ) {
// Clipboard content should include all parents within block, or all // Clipboard content should include all parents within block, or all
// parents up to root if selection across blocks // parents up to root if selection across blocks
startBlock = getStartBlockOfRange( range, root ); startBlock = getStartBlockOfRange( range, root );
@ -2349,21 +2345,13 @@ var _onCopy = function ( event, range, root, willCutCopy, toPlainText, plainText
parent = parent.parentNode; parent = parent.parentNode;
} }
// Set clipboard data // Set clipboard data
setClipboardData( event, contents, root, willCutCopy, toPlainText, plainTextOnly ); node = this.createElement( 'div' );
node.appendChild( contents );
setClipboardData( clipboardData, node, root, this._config );
event.preventDefault();
} }
}; };
var onCopy = function ( event ) {
_onCopy(
event,
this.getSelection(),
this._root,
this._config.willCutCopy,
null,
false
);
};
// Need to monitor for shift key like this, as event.shiftKey is not available // Need to monitor for shift key like this, as event.shiftKey is not available
// in paste event. // in paste event.
function monitorShiftKey ( event ) { function monitorShiftKey ( event ) {
@ -2693,8 +2681,7 @@ var sanitizeToDOMFragment = function ( html, isPaste, self ) {
ALLOW_UNKNOWN_PROTOCOLS: true, ALLOW_UNKNOWN_PROTOCOLS: true,
WHOLE_DOCUMENT: false, WHOLE_DOCUMENT: false,
RETURN_DOM: true, RETURN_DOM: true,
RETURN_DOM_FRAGMENT: true, RETURN_DOM_FRAGMENT: true
FORCE_BODY: false
}) : null; }) : null;
return frag ? doc.importNode( frag, true ) : doc.createDocumentFragment(); return frag ? doc.importNode( frag, true ) : doc.createDocumentFragment();
}; };
@ -2978,6 +2965,16 @@ proto.setSelection = function ( range ) {
// needing restore on focus. // needing restore on focus.
if ( !this._isFocused ) { if ( !this._isFocused ) {
enableRestoreSelection.call( this ); enableRestoreSelection.call( this );
} else if ( isAndroid && !this._restoreSelection ) {
// Android closes the keyboard on removeAllRanges() and doesn't
// open it again when addRange() is called, sigh.
// Since Android doesn't trigger a focus event in setSelection(),
// use a blur/focus dance to work around this by letting the
// selection be restored on focus.
// Need to check for !this._restoreSelection to avoid infinite loop
enableRestoreSelection.call( this );
this.blur();
this.focus();
} else { } else {
// iOS bug: if you don't focus the iframe before setting the // iOS bug: if you don't focus the iframe before setting the
// selection, you can end up in a state where you type but the input // selection, you can end up in a state where you type but the input
@ -2987,15 +2984,7 @@ proto.setSelection = function ( range ) {
this._win.focus(); this._win.focus();
} }
var sel = getWindowSelection( this ); var sel = getWindowSelection( this );
if ( sel && sel.setBaseAndExtent ) { if ( sel ) {
sel.setBaseAndExtent(
range.startContainer,
range.startOffset,
range.endContainer,
range.endOffset,
);
} else if ( sel ) {
// This is just for IE11
sel.removeAllRanges(); sel.removeAllRanges();
sel.addRange( range ); sel.addRange( range );
} }
@ -3171,7 +3160,7 @@ proto._updatePath = function ( range, force ) {
// selectionchange is fired synchronously in IE when removing current selection // selectionchange is fired synchronously in IE when removing current selection
// and when setting new selection; keyup/mouseup may have processing we want // and when setting new selection; keyup/mouseup may have processing we want
// to do first. Either way, send to next event loop. // to do first. Either way, send to next event loop.
proto._updatePathOnEvent = function () { proto._updatePathOnEvent = function ( event ) {
var self = this; var self = this;
if ( self._isFocused && !self._willUpdatePath ) { if ( self._isFocused && !self._willUpdatePath ) {
self._willUpdatePath = true; self._willUpdatePath = true;
@ -3891,9 +3880,10 @@ var increaseBlockQuoteLevel = function ( frag ) {
}; };
var decreaseBlockQuoteLevel = function ( frag ) { var decreaseBlockQuoteLevel = function ( frag ) {
var root = this._root;
var blockquotes = frag.querySelectorAll( 'blockquote' ); var blockquotes = frag.querySelectorAll( 'blockquote' );
Array.prototype.filter.call( blockquotes, function ( el ) { Array.prototype.filter.call( blockquotes, function ( el ) {
return !getNearest( el.parentNode, frag, 'BLOCKQUOTE' ); return !getNearest( el.parentNode, root, 'BLOCKQUOTE' );
}).forEach( function ( el ) { }).forEach( function ( el ) {
replaceWith( el, empty( el ) ); replaceWith( el, empty( el ) );
}); });
@ -4174,14 +4164,7 @@ proto._getHTML = function () {
proto._setHTML = function ( html ) { proto._setHTML = function ( html ) {
var root = this._root; var root = this._root;
var node = root; var node = root;
var sanitizeToDOMFragment = this._config.sanitizeToDOMFragment;
if ( typeof sanitizeToDOMFragment === 'function' ) {
var frag = sanitizeToDOMFragment( html, false, this );
empty( node );
node.appendChild( frag );
} else {
node.innerHTML = html; node.innerHTML = html;
}
do { do {
fixCursor( node, root ); fixCursor( node, root );
} while ( node = getNextBlock( node, root ) ); } while ( node = getNextBlock( node, root ) );
@ -4189,7 +4172,8 @@ proto._setHTML = function ( html ) {
}; };
proto.getHTML = function ( withBookMark ) { proto.getHTML = function ( withBookMark ) {
var html, range; var brs = [],
root, node, fixer, html, l, range;
if ( withBookMark && ( range = this.getSelection() ) ) { if ( withBookMark && ( range = this.getSelection() ) ) {
this._saveRangeToBookmark( range ); this._saveRangeToBookmark( range );
} }
@ -4984,7 +4968,6 @@ Squire.rangeDoesEndAtBlockBoundary = rangeDoesEndAtBlockBoundary;
Squire.expandRangeToBlockBoundaries = expandRangeToBlockBoundaries; Squire.expandRangeToBlockBoundaries = expandRangeToBlockBoundaries;
// Clipboard.js exports // Clipboard.js exports
Squire.onCopy = _onCopy;
Squire.onPaste = onPaste; Squire.onPaste = onPaste;
// Editor.js exports // Editor.js exports

View File

@ -20,7 +20,7 @@
.image-placeholder { .image-placeholder {
width: 100%; width: 100%;
height: 100%; height: 100%;
max-height: 75px; max-height: 100px;
} }
.image-placeholder:after { .image-placeholder:after {
content: 'No Image'; content: 'No Image';
@ -89,14 +89,7 @@
<!-- image and text --> <!-- image and text -->
<div class="six wide center aligned middle aligned column"> <div class="six wide center aligned middle aligned column">
<a :href="linkUrl" target="_blank" > <a :href="linkUrl" target="_blank" >
<img v-if="item.file_location" class="attachment-image" <img v-if="item.file_location" class="attachment-image" :src="`/api/static/thumb_${item.file_location}`">
onerror="
this.onerror=null;
this.src='/api/static/assets/marketing/void.svg';
this.classList.add('image-placeholder');
this.insertAdjacentText('afterend', 'Image not found');
"
:src="`/api/static/thumb_${item.file_location}`">
<span v-else> <span v-else>
<img class="image-placeholder" loading="lazy" src="/api/static/assets/marketing/void.svg"> <img class="image-placeholder" loading="lazy" src="/api/static/assets/marketing/void.svg">
No Image No Image
@ -178,9 +171,6 @@
this.checkKeyup() this.checkKeyup()
}) })
}, },
updated: function(){
this.checkKeyup()
},
methods: { methods: {
checkKeyup(){ checkKeyup(){
let elm = this.$refs.edit let elm = this.$refs.edit

View File

@ -1,51 +1,45 @@
<template> <template>
<div> <div :style="{ 'background-color':allStyles['noteBackground'], 'color':allStyles['noteText']}">
<div class="ui basic segment">
<div class="ui grid"> <div class="ui grid">
<div class="ui sixteen wide column"> <div class="ui sixteen wide center aligned column">
<div class="ui dividing header"> <div class="ui fluid button" v-on:click="clearStyles">
Reset Background Color and Icon
</div>
<div class="ui labeled basic icon button" v-on:click="clearStyles">
<i class="refresh icon"></i> <i class="refresh icon"></i>
Reset Clear All Styles
</div> </div>
</div> </div>
<div class="sixteen wide column rounded" :style="{ 'background-color':allStyles['noteBackground'], 'color':allStyles['noteText']}"> <div class="row">
<div class="ui dividing header" :style="{ 'color':allStyles['noteText']}"> <div class="sixteen wide column">
<i class="fill drip icon"></i> <br>
Background Color <p>Note Color</p>
</div>
<div v-for="color in colors" <div v-for="color in colors"
class="color-button" class="color-button"
:style="{ backgroundColor:color }" :style="{ backgroundColor:color }"
v-on:click="chosenColor(color)" v-on:click="chosenColor(color)"
></div> ></div>
</div> </div>
</div>
<div class="row">
<div class="sixteen wide column"> <div class="sixteen wide column">
<div class="ui dividing header"> <p>Note Icon
<span v-if="allStyles.noteIcon" > <span v-if="allStyles.noteIcon" >
<i :class="`large ${allStyles.noteIcon} icon`" :style="{ 'color':allStyles.iconColor }"></i> <i :class="`large ${allStyles.noteIcon} icon`" :style="{ 'color':allStyles.iconColor }"></i>
</span> </span>
Note Icon </p>
</div>
<div v-for="icon in icons" class="icon-button" v-on:click="chosenIcon(icon)" > <div v-for="icon in icons" class="icon-button" v-on:click="chosenIcon(icon)" >
<i :class="`large ${icon} icon`"></i> <i :class="`large ${icon} icon`" :style="{ 'color':allStyles.iconColor }"></i>
</div>
</div> </div>
</div> </div>
<div class="row">
<div class="sixteen wide column"> <div class="sixteen wide column">
<div class="ui dividing header"> <p>Icon Color</p>
<span v-if="allStyles.noteIcon" >
<i :class="`large ${allStyles.noteIcon} icon`" :style="{ 'color':allStyles.iconColor }"></i>
</span>
Icon Color
</div>
<div v-for="color in getReducedColors()" <div v-for="color in getReducedColors()"
class="color-button" class="color-button"
:style="{ backgroundColor:color }" :style="{ backgroundColor:color }"
@ -53,7 +47,8 @@
> >
</div> </div>
</div> </div>
</div>
</div>
</div> </div>
</div> </div>
@ -152,24 +147,20 @@
} }
</script> </script>
<style type="text/css" scoped> <style type="text/css" scoped>
.icon-button, .color-button { .icon-button {
height: 40px; height: 40px;
width: calc(15% - 1px); width: calc(10% - 7px);
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
font-size: 1.3em; font-size: 1.3em;
border: 1px solid grey;
text-align: center;
padding: 5px 0px 0 0;
border-radius: 4px;
box-shadow: 0px 1px 3px 0px #3e3e3e;
margin: 2px 2px 0 0;
box-sizing: border-box;
} }
.color-button { .color-button {
width: calc(10% - 4px); display: inline-block;
} width: calc(10% - 7px);
.rounded { height: 30px;
border-radius: 5px; border-radius: 30px;
box-shadow: 0px 1px 3px 0px #3e3e3e;
margin: 7px 7px 0 0;
cursor: pointer;
} }
</style> </style>

View File

@ -19,7 +19,7 @@
padding: 1em 5px; padding: 1em 5px;
cursor: pointer; cursor: pointer;
} }
.popup-row > p { .popup-row > span {
/*width: calc(100% - 50px);*/ /*width: calc(100% - 50px);*/
display: inline-block; display: inline-block;
text-align: left; text-align: left;
@ -85,18 +85,6 @@
animation: progressBar 3s linear; animation: progressBar 3s linear;
animation-fill-mode: both; animation-fill-mode: both;
} }
.time-display {
display: inline-block;
width: calc(100% - 25px);
/*text-align: right;*/
color: white;
font-size: 0.7em;
margin: 0 0 0 25px;
}
.text-display {
display: inline-block;
width: 100%;
}
@keyframes progressBar { @keyframes progressBar {
0% { width: 0; } 0% { width: 0; }
@ -113,11 +101,7 @@
<div class="meter"> <div class="meter">
<span><span class="progress"></span></span> <span><span class="progress"></span></span>
</div> </div>
<p class="text-display"> <span><i class="small info circle icon"></i>{{ item }}</span>
<i class="small info circle icon"></i>
{{ item.text }}
<span class="time-display">{{ item.time }}</span>
</p>
</div> </div>
</div> </div>
</template> </template>
@ -135,8 +119,8 @@
} }
}, },
beforeMount(){ beforeMount(){
this.$bus.$on('notification', notificationText => { this.$bus.$on('notification', info => {
this.displayNotification(notificationText) this.displayNotification(info)
}) })
}, },
mounted(){ mounted(){
@ -147,17 +131,8 @@
}, },
methods: { methods: {
displayNotification(notificationText){ displayNotification(newNotification){
this.notifications.push(newNotification)
const date = new Date()
const time = date.toLocaleTimeString()
const notification = {
text: notificationText,
time: time
}
this.notifications.unshift(notification)
clearTimeout(this.totalTimeout) clearTimeout(this.totalTimeout)
this.totalTimeout = setTimeout(() => { this.totalTimeout = setTimeout(() => {
this.dismiss() this.dismiss()

View File

@ -1,28 +1,26 @@
<style scoped> <style scoped>
.slotholder { .slotholder {
height: 100vh; height: 100vh;
width: 180px; width: 155px;
display: block; display: block;
float: left; float: left;
overflow: hidden;
} }
.global-menu { .global-menu {
width: 180px; width: 155px;
/* background: #221f2b; */
background: #221f2b; background: #221f2b;
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
display: block; display: block;
position: fixed; position: fixed;
z-index: 900; z-index: 111;
top: 0; top: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
} }
.menu-logo-display { .menu-logo-display {
width: 27px; width: 27px;
margin: 5px 0 0 55px; margin: 5px 0 0 41px;
display: inline-block; display: inline-block;
height: auto; height: auto;
} }
@ -44,8 +42,7 @@
.menu-section {} .menu-section {}
.menu-section + .menu-section { .menu-section + .menu-section {
/* border-top: 1px solid #534c68; */ border-top: 1px solid #534c68;
border-top: 1px solid #534c68e3;
} }
.menu-button { .menu-button {
cursor: pointer; cursor: pointer;
@ -55,6 +52,9 @@
text-decoration: none; text-decoration: none;
} }
.router-link-active i {
/*color: #16ab39;*/
}
.router-link-active { .router-link-active {
background-color: #534c68; background-color: #534c68;
} }
@ -66,32 +66,28 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
background-color: rgba(0,0,0,0.7); background-color: rgba(0,0,0,0.7);
z-index: 899; z-index: 100;
cursor: pointer; cursor: pointer;
} }
.top-menu-bar { .top-menu-bar {
/*color: var(--text_color);*/ /*color: var(--text_color);*/
/*width: 100%;*/ /*width: 100%;*/
position: fixed; position: fixed;
bottom: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
z-index: 999; z-index: 999;
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
border-bottom: 1px solid;
border-color: var(--border_color);
/*padding: 5px 1rem 5px;*/ /*padding: 5px 1rem 5px;*/
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
width: 100vw; width: 100vw;
border-top: 1px solid var(--dark_border_color);
display: flex;
margin: 0;
padding: 0;
} }
.place-holder { .place-holder {
width: 100%; width: 100%;
/*height: 40px;*/ height: 40px;
height: 0;
} }
.logo-display { .logo-display {
width: 27px; width: 27px;
@ -107,49 +103,19 @@
text-align: center; text-align: center;
color: #8c80ae; color: #8c80ae;
cursor: pointer; cursor: pointer;
background-color: var(--menu-background);
} }
.mobile-button { .mobile-button {
padding: 5px 0 0;
margin: 0;
cursor: pointer;
font-size: 0.6em;
color: var(--menu-text);
text-align: center;
flex-basis: 100%;
line-height: 1.8em;
}
.mobile-button + .mobile-button {
border-left: 1px solid var(--dark_border_color);
}
.mobile-button i {
font-size: 2em;
margin: 0 auto;
padding: 0;
width: 100%;
}
.mobile-button svg {
margin: 0 46% 0;
display: inline-block; display: inline-block;
width: 15px; font-size: 2em;
} padding: 6px 3px 5px;
.mobile-button:active, .mobile-button:focus, .mobile-button:hover { cursor: pointer;
text-decoration: none;
} }
.mobile-button.active { .mobile-button.active {
background-color: transparent; background-color: transparent;
} }
.single-line-text { .mobile-button i {
width: calc(100%); margin: 0;
/*margin: 5px 10px;*/
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
.faded {
color: var(--dark_border_color);
} }
</style> </style>
@ -162,24 +128,12 @@
<!-- collapsed menu, appears as a bar --> <!-- collapsed menu, appears as a bar -->
<div class="top-menu-bar" v-if="(collapsed || mobile) && !menuOpen"> <div class="top-menu-bar" v-if="(collapsed || mobile) && !menuOpen">
<!-- logo --> <div class="mobile-button">
<router-link v-if="loggedIn" class="mobile-button" exact-active-class="active" to="/notes" v-on:click.native="emitReloadEvent()"> <i class="green link bars icon" v-on:click="collapseMenu"></i>
<logo class="logo-display" color="var(--main-accent)" />
Notes
</router-link>
<!-- new note -->
<div v-if="loggedIn" class="mobile-button">
<span v-if="!disableNewNote" @click="createNote">
<i class="green plus icon"></i>
New Note
</span>
<span v-if="disableNewNote">
<i class="grey plus icon"></i>
Working
</span>
</div> </div>
<div class="mobile-button"></div>
<!-- open straight to note --> <!-- open straight to note -->
<router-link <router-link
v-if="loggedIn && $store.getters.totals && $store.getters.totals['quickNote']" v-if="loggedIn && $store.getters.totals && $store.getters.totals['quickNote']"
@ -187,7 +141,6 @@
class="mobile-button" class="mobile-button"
:to="`/notes/open/${$store.getters.totals['quickNote']}`"> :to="`/notes/open/${$store.getters.totals['quickNote']}`">
<i class="green sticky note outline icon"></i> <i class="green sticky note outline icon"></i>
Scratch Pad
</router-link> </router-link>
<!-- create new and redirect to new note id --> <!-- create new and redirect to new note id -->
@ -197,21 +150,27 @@
exact-active-class="active" exact-active-class="active"
class="mobile-button"> class="mobile-button">
<i class="green sticky note outline icon"></i> <i class="green sticky note outline icon"></i>
Scratch Pad
</a> </a>
<router-link v-if="loggedIn" class="mobile-button" exact-active-class="active" to="/notes" v-on:click.native="emitReloadEvent()">
<logo class="logo-display" color="var(--main-accent)" />
</router-link>
<router-link v-if="loggedIn" class="mobile-button" exact-active-class="active" to="/attachments"> <router-link v-if="loggedIn" class="mobile-button" exact-active-class="active" to="/attachments">
<i class="green open folder outline icon"></i> <i class="green open folder outline icon"></i>
Files
</router-link> </router-link>
<!-- menu --> <div class="mobile-button"></div>
<div class="mobile-button" v-on:click="collapseMenu">
<i class="green link bars icon" ></i>
Menu
</div>
<!-- mobile create note button -->
<span v-if="loggedIn">
<span v-if="!disableNewNote" @click="createNote" class="mobile-button">
<i class="green plus icon"></i>
</span>
<span v-if="disableNewNote" class="mobile-button">
<i class="grey plus icon"></i>
</span>
</span>
</div> </div>
@ -229,12 +188,12 @@
<div class="menu-section" v-if="loggedIn"> <div class="menu-section" v-if="loggedIn">
<div v-if="!disableNewNote" @click="createNote" class="menu-item menu-item menu-button"> <div v-if="!disableNewNote" @click="createNote" class="menu-item menu-item menu-button">
<div class="ui green fluid compact button"> <div class="ui green button">
<i class="plus icon"></i>New Note <i class="plus icon"></i>New Note
</div> </div>
</div> </div>
<div v-if="disableNewNote" class="menu-item menu-item menu-button"> <div v-if="disableNewNote" class="menu-item menu-item menu-button">
<div class="ui basic fluid compact button"> <div class="ui basic button">
<i class="plus loading icon"></i>New Note <i class="plus loading icon"></i>New Note
</div> </div>
</div> </div>
@ -247,19 +206,14 @@
</router-link> </router-link>
<div> <div>
<div class="menu-item menu-button sub" v-on:click="updateFastFilters(3)" v-if="$store.getters.totals && ($store.getters.totals['sharedToNotes'] > 0 || $store.getters.totals['sharedFromNotes'] > 0)"> <div class="menu-item menu-button sub" v-on:click="updateFastFilters(3)" v-if="$store.getters.totals && ($store.getters.totals['sharedToNotes'] > 0 || $store.getters.totals['sharedFromNotes'] > 0)">
<i class="grey paper plane outline icon"></i>Shared <i class="grey mail outline icon"></i>Inbox
<counter v-if="$store.getters.totals && $store.getters.totals['sharedToNotes']" class="float-right" number-id="sharedToNotes" />
</div> </div>
<div class="menu-item menu-button sub" v-on:click="updateFastFilters(2)" v-if="$store.getters.totals && $store.getters.totals['archivedNotes'] > 0"> <div class="menu-item menu-button sub" v-on:click="updateFastFilters(2)" v-if="$store.getters.totals && $store.getters.totals['archivedNotes'] > 0">
<i class="grey archive icon"></i>Archived <i class="grey archive icon"></i>Archived
<!-- <span>{{ $store.getters.totals['archivedNotes'] }}</span> -->
<counter v-if="$store.getters.totals && $store.getters.totals['archivedNotes']" class="float-right" number-id="archivedNotes" />
</div> </div>
<div class="menu-item menu-button sub" v-on:click="updateFastFilters(4)" v-if="$store.getters.totals && $store.getters.totals['trashedNotes'] > 0"> <div class="menu-item menu-button sub" v-on:click="updateFastFilters(4)" v-if="$store.getters.totals && $store.getters.totals['trashedNotes'] > 0">
<i class="grey trash alternate outline icon"></i>Trashed <i class="grey trash alternate outline icon"></i>Trashed
<counter v-if="$store.getters.totals && $store.getters.totals['trashedNotes']" class="float-right" number-id="trashedNotes" />
</div> </div>
<!-- <div class="menu-item sub">Show Only <i class="caret down icon"></i></div> --> <!-- <div class="menu-item sub">Show Only <i class="caret down icon"></i></div> -->
<!-- <div v-on:click="updateFastFilters(0)" class="menu-item menu-button sub"><i class="grey linkify icon"></i>Links</div> --> <!-- <div v-on:click="updateFastFilters(0)" class="menu-item menu-button sub"><i class="grey linkify icon"></i>Links</div> -->
@ -312,22 +266,12 @@
<span v-if="$store.getters.getIsNightMode == 0"> <span v-if="$store.getters.getIsNightMode == 0">
<i class="moon outline icon"></i>Black Theme</span> <i class="moon outline icon"></i>Black Theme</span>
<span v-if="$store.getters.getIsNightMode == 1"> <span v-if="$store.getters.getIsNightMode == 1">
<i class="moon outline icon"></i>Flux Theme</span>
<span v-if="$store.getters.getIsNightMode == 2">
<i class="moon outline icon"></i>Light Theme</span> <i class="moon outline icon"></i>Light Theme</span>
</div> </div>
</div> </div>
<div class="menu-section" v-if="loggedIn">
<router-link class="menu-item menu-button" exact-active-class="active" to="/settings">
<i class="cog icon"></i>Settings
</router-link>
</div>
<div class="menu-section" v-if="loggedIn">
<router-link class="menu-item menu-button" exact-active-class="active" to="/metrictrack">
<i class="calendar check outlin icon"></i>Metric Track
</router-link>
</div>
<div class="menu-section"> <div class="menu-section">
<router-link class="menu-item menu-button" exact-active-class="active" to="/help"> <router-link class="menu-item menu-button" exact-active-class="active" to="/help">
<i class="question circle outline icon"></i>Help <i class="question circle outline icon"></i>Help
@ -335,27 +279,14 @@
</div> </div>
<div class="menu-section" v-if="loggedIn"> <div class="menu-section" v-if="loggedIn">
<div class="menu-item menu-button" v-on:click="logout()"> <router-link class="menu-item menu-button" exact-active-class="active" to="/settings">
<i class="log out icon"></i>Log Out <i class="cog icon"></i>Settings
</div> </router-link>
</div> </div>
<!-- Tags --> <div class="menu-section" v-if="loggedIn">
<div class="menu-section" v-if="gotTags()"> <div class="menu-item menu-button" v-on:click="logout()">
<div class="menu-item"> <i class="log out icon"></i>Log Out
<i class="green tags icon"></i>
Tags
</div>
</div>
<div v-if="gotTags()">
<div class="menu-section"
v-for="(data, tag) in $store.getters.totals['tags']">
<router-link class="menu-item menu-button" :to="`/search/tags/${tag}`">
<span class="single-line-text">
<!-- <i class="small grey tag icon"></i> -->
<span class="float-right">{{ data.uses }}</span>
<span class="faded"> #</span> {{ tag }}</span>
</router-link>
</div> </div>
</div> </div>
@ -418,16 +349,6 @@
}, },
}, },
methods: { methods: {
gotTags(){
if(this.loggedIn && this.$store.getters.totals && this.$store.getters.totals.tags
&& Object.keys(this.$store.getters.totals.tags).length
){
return true
}
return false
},
logout() { logout() {
this.$router.push('/') this.$router.push('/')
@ -526,11 +447,8 @@
location.reload(true) location.reload(true)
}, },
getVersionIcon(){ getVersionIcon(){
if(!this.version){
return 'radiation alternate'
}
const icons = ['cat','crow','dog','dove','dragon','fish','frog','hippo','horse','kiwi bird','otter','spider', 'smile', 'robot', 'hat wizard', 'microchip', 'atom', 'grin tongue squint', 'radiation', 'ghost', 'dna', 'burn', 'brain', 'moon', 'torii gate'] const icons = ['cat','crow','dog','dove','dragon','fish','frog','hippo','horse','kiwi bird','otter','spider', 'smile', 'robot', 'hat wizard', 'microchip', 'atom', 'grin tongue squint', 'radiation', 'ghost', 'dna', 'burn', 'brain', 'moon', 'torii gate']
const index = ( parseInt(String(this.version).replace(/\./g,'')) % (icons.length)) const index = ( parseInt(this.version.replace(/\./g,'')) % (icons.length))
return icons[index] return icons[index]
} }

View File

@ -1,60 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa" target="_blank" rel="noopener">pwa</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@ -39,9 +39,9 @@
.loading-container { .loading-container {
text-align: center; text-align: center;
width: 100%; width: 100%;
/*min-height: 100px;*/ min-height: 100px;
margin: 20px 0; margin: 20px 0;
/*padding: 40px;*/ padding: 40px;
border-radius: 7px; border-radius: 7px;
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
} }

View File

@ -1,10 +1,10 @@
<template> <template>
<div> <div v-on:keyup.enter="login()">
<!-- thicc form display --> <!-- thicc form display -->
<div v-if="!thin" class="ui large form" v-on:keyup.enter="register()"> <div v-if="!thin" class="ui large form">
<div class="field"> <div class="field">
<div class="ui input"> <div class="ui input">
<input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail"> <input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail">
@ -22,20 +22,15 @@
</div> </div>
<div class="sixteen wide field"> <div class="sixteen wide field">
<div class="ui fluid buttons"> <div class="ui fluid buttons">
<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="login()" class="ui green button">
<div v-on:click="register()" class="ui green button" :class="{ 'disabled':(username.length == 0 || password.length == 0)}">
<i class="plug icon"></i>
Sign Up
</div>
<div class="or"></div>
<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="login()" class="ui button">
<i class="power icon"></i> <i class="power icon"></i>
Login Login
</div> </div>
<div class="or"></div>
<div v-on:click="register()" class="ui button">
<i class="plug icon"></i>
Sign Up
</div>
</div> </div>
</div> </div>
<div class="sixteen wide column"> <div class="sixteen wide column">
@ -49,27 +44,7 @@
</div> </div>
<!-- Thin form display --> <!-- Thin form display -->
<div v-if="thin" class="ui small form" v-on:keyup.enter="login()"> <div v-if="thin" class="ui small form">
<div v-if="!require2FA" class="field"><!-- hide this field if someone is logging in with 2FA -->
<div class="ui grid">
<div class="ui sixteen wide center aligned column">
<div v-on:click="register()" class="ui green button">
<i class="plug icon"></i>
Sign Up Now!
</div>
</div>
</div>
</div>
<div class="field"><!-- hide this field if someone is logging in with 2FA -->
<div class="ui grid">
<div class="ui sixteen wide center aligned column">
Or Login
</div>
</div>
</div>
<div class="equal width fields"> <div class="equal width fields">
<div class="field"> <div class="field">
<div class="ui input"> <div class="ui input">
@ -86,6 +61,13 @@
<input v-model="authToken" ref="authForm" type="text" name="authToken" placeholder="Authorization Token"> <input v-model="authToken" ref="authForm" type="text" name="authToken" placeholder="Authorization Token">
</div> </div>
</div> </div>
<!-- hide this field if someone is logging in with 2FA -->
<div class="field" v-if="!require2FA">
<div v-on:click="register()" class="ui fluid green button">
<i class="plug icon"></i>
Sign Up
</div>
</div>
<div class="field"> <div class="field">
<div v-on:click="login()" class="ui fluid button"> <div v-on:click="login()" class="ui fluid button">
<i class="power icon"></i> <i class="power icon"></i>
@ -161,14 +143,7 @@
register(){ register(){
if( this.username.length == 0 || this.password.length == 0 ){ if( this.username.length == 0 || this.password.length == 0 ){
this.$bus.$emit('notification', 'Unable to Sign Up - Username and Password Required')
if(this.$route.name == 'LoginPage'){
this.$bus.$emit('notification', 'Both a Username and Password are Required')
return
}
//Login section
this.$router.push('/login')
return return
} }
@ -228,6 +203,7 @@
<style type="text/css" scoped="true"> <style type="text/css" scoped="true">
.small-terms { .small-terms {
display: inline-block; display: inline-block;
text-align: right;
width: 100%; width: 100%;
font-size: 0.9em; font-size: 0.9em;
} }

View File

@ -32,22 +32,22 @@
class="darken-accent" class="darken-accent"
id="path3813-4" id="path3813-4"
d="m 56.22733,165.36641 -55.56249926,15.875 8e-7,63.5 47.62499846,11.90625 v 27.78125 l -47.76066333,-13.9757 0.13566407,10.00695 55.56249926,15.875 v -47.625 l -47.6249985,-11.90625 -8e-7,-47.625 47.7606633,-13.94121 c 0.135664,-2.30629 -0.135664,-9.87129 -0.135664,-9.87129 z" d="m 56.22733,165.36641 -55.56249926,15.875 8e-7,63.5 47.62499846,11.90625 v 27.78125 l -47.76066333,-13.9757 0.13566407,10.00695 55.56249926,15.875 v -47.625 l -47.6249985,-11.90625 -8e-7,-47.625 47.7606633,-13.94121 c 0.135664,-2.30629 -0.135664,-9.87129 -0.135664,-9.87129 z"
:style="`fill:${displayColor};fill-opacity:1;stroke:${strokeColor};stroke-width:${strokeWidth};stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1`" /> :style="`fill:${displayColor};fill-opacity:1;stroke:none;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1`" />
<path <path
class="brighten-accent" class="brighten-accent"
id="path4563" id="path4563"
d="m 20.508581,220.92891 c 15.265814,-14.23899 27.809717,-7.68002 39.687499,3.96875 v -7.9375 C 51.75093,200.8366 37.512584,206.01499 20.508581,205.05391 Z" d="m 20.508581,220.92891 c 15.265814,-14.23899 27.809717,-7.68002 39.687499,3.96875 v -7.9375 C 51.75093,200.8366 37.512584,206.01499 20.508581,205.05391 Z"
:style="`fill:${displayColor};fill-opacity:1;stroke:${strokeColor};stroke-width:${strokeWidth}px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1`" /> :style="`fill:${displayColor};fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1`" />
<path <path
class="brighten-accent" class="brighten-accent"
id="path4563-6" id="path4563-6"
d="m 111.78985,220.92891 c -15.265834,-14.23899 -27.809737,-7.68002 -39.68752,3.96875 v -7.9375 c 8.445151,-16.12356 22.683497,-10.94517 39.68752,-11.90625 z" d="m 111.78985,220.92891 c -15.265834,-14.23899 -27.809737,-7.68002 -39.68752,3.96875 v -7.9375 c 8.445151,-16.12356 22.683497,-10.94517 39.68752,-11.90625 z"
:style="`display:inline;fill:${displayColor};fill-opacity:1;stroke:${strokeColor};stroke-width:${strokeWidth}px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1`" /> :style="`display:inline;fill:${displayColor};fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1`" />
<path <path
class="brighten-accent" class="brighten-accent"
id="path3813-4-2" id="path3813-4-2"
d="m 76.07108,165.36641 55.5625,15.875 v 63.5 l -47.625,11.90625 v 27.78125 l 47.76067,-13.9757 -0.13567,10.00695 -55.5625,15.875 v -47.625 l 47.625,-11.90626 V 189.17891 L 75.93542,175.2377 c -0.13567,-2.30629 0.13566,-9.87129 0.13566,-9.87129 z" d="m 76.07108,165.36641 55.5625,15.875 v 63.5 l -47.625,11.90625 v 27.78125 l 47.76067,-13.9757 -0.13567,10.00695 -55.5625,15.875 v -47.625 l 47.625,-11.90626 V 189.17891 L 75.93542,175.2377 c -0.13567,-2.30629 0.13566,-9.87129 0.13566,-9.87129 z"
:style="`display:inline;fill:${displayColor};fill-opacity:1;stroke:${strokeColor};stroke-width:${strokeWidth};stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1`" /> :style="`display:inline;fill:${displayColor};fill-opacity:1;stroke:none;stroke-width:0.52916676;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1`" />
</g> </g>
</svg> </svg>
</template> </template>
@ -56,28 +56,13 @@
export default { export default {
name: 'LoadingIcon', name: 'LoadingIcon',
props:[ props:[ 'color' ],
'color', // hex value for setting colorr
'stroke' // enable or disable stroke
],
data(){ data(){
return { return {
displayColor: '#21BA45', //Default green color displayColor: '#21BA45', //Default green color
strokeWidth: '0.5',
strokeColor: 'none',
} }
},
beforeCreate(){
}, },
created(){ created(){
if(this.stroke){
this.strokeWidth = 0.4
this.strokeColor = 'rgba(0,0,0,0.9)'
}
//Set color if passed //Set color if passed
if(this.color){ if(this.color){
this.displayColor = this.color this.displayColor = this.color
@ -94,7 +79,4 @@
filter: saturate(145%); filter: saturate(145%);
-webkit-filter: saturate(145%); -webkit-filter: saturate(145%);
} }
g > path {
filter: drop-shadow(1px 1px 1px black);
}
</style> </style>

View File

@ -1,27 +0,0 @@
<style type="text/css" scoped></style>
</style>
<template>
<div>
I'm a calednar yo
</div>
</template>
<script>
export default {
props: [
'graphOptions', // options associated with this graph
'tempChartDays', // number of days to display
'cycleData', // all users metric data
],
data: function(){
return {
openModel:true,
}
},
methods: {
closeModel(){
},
}
}
</script>

View File

@ -1,164 +0,0 @@
<style type="text/css" scoped>
.modal-content {
position: fixed;
top: 40%;
left: 50%;
/* bring your own prefixes */
transform: translate(-50%, -40%);
z-index: 300;
padding: 1em;
box-sizing: border-box;
width: 50%;
max-height: 100%;
/*overflow: hidden;*/
overflow-y: scroll;
font-weight: normal;
}
.modal-content.fullscreen {
width: 96%;
height: 100%;
max-height: 100%;
}
.close-container {
position: fixed;
top: 5px;
right: 5px;
z-index: 320;
}
/* Shrink button text for mobile */
@media only screen and (max-width: 740px) {
.modal-content {
width: 100%;
padding-bottom: 55px;
}
}
.modal-content.right-side {
width: 60%;
max-height: none;
height: 100vh;
padding: 0;
margin: 0;
top: 0;
bottom: 0;
left: 0;
left: auto;
transform: translate(0, 0);
}
.close-container-right-side {
position: fixed;
top: 5px;
left: calc(60% + 2px);
z-index: 320;
}
.shade {
position: fixed;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #0000007d;
z-index: 299;
backdrop-filter: blur(2px);
}
.fade-out-top {
animation: fade-out-top 0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
}
.fade-out {
animation: fade-out 0.3s ease-out both;
}
@keyframes fade-out-top {
0% {
/*transform: translate(-50%, -50%);*/
opacity: 1;
}
100% {
/*transform: translate(-50%, -70%);*/
opacity: 0;
}
}
@keyframes fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.fade-in {
/*animation: fade-in 0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;*/
}
@keyframes fade-in {
0% {
transform: translate(-50%, -70%);
opacity: 0;
}
100% {
transform: translate(-50%, -50%);
opacity: 1;
}
}
</style>
<template>
<div v-if="openModel">
<div class="modal-content" :class="{ 'fade-out-top':(animateOut), 'fade-in':(!animateOut), 'fullscreen':(fullscreen)}">
<slot></slot>
</div>
<!-- full screen close button -->
<div class="close-container" v-if="fullscreen && clickOutClose !== false">
<div class="ui green icon button" v-on:click="closeModel">
<i class="close icon"></i>
</div>
</div>
<div class="shade" v-on:click="closeModel" v-on:mouseenter=" hoverOutClose?closeModel():null " :class="{ 'fade-out':(animateOut) }"></div>
</div>
</template>
<script>
export default {
props: [
'fullscreen', //Make the model really big
'clickOutClose', //Set to false to prevent closing of modal by clicking out
'hoverOutClose', //Close if cursor leaves modal
],
data: function(){
return {
openModel:true,
animateOut:false,
}
},
methods: {
closeModel(){
//Don't allow closing by clicking out
if(this.clickOutClose === false){
return
}
//Set stups to close model, animate out
this.animateOut = true
setTimeout( () => {
this.openModel = false
this.$emit('close')
//Once close event is sent, reset to default state
this.animateOut = false
this.openModel = true
}, 800)
},
}
}
</script>

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,12 @@
<template> <template>
<div class="note-title-display-card" <div class="note-title-display-card"
:style="{'background-color':color, 'color':fontColor, 'border-color':color }" :style="{'background-color':color, 'color':fontColor, 'border-color':color }"
:class="{ :class="{'currently-open':(currentlyOpen || showWorking), 'bgboy':triggerClosedAnimation, 'title-view':titleView }"
'currently-open':(currentlyOpen || showWorking), >
'ring':triggerClosedAnimation,
'title-view':titleView
}">
<!-- Show title and snippet below it --> <!-- Show title and snippet below it -->
<div class="overflow-hidden note-card-text" @click.stop="cardClicked" v-if="!titleView"> <div class="overflow-hidden note-card-text" @click="cardClicked" v-if="!titleView">
<span v-if="note.title == '' && note.subtext == ''"> <span v-if="note.title == '' && note.subtext == ''">
Empty Note Empty Note
@ -23,10 +20,23 @@
<span v-if="note.title.length > 0" <span v-if="note.title.length > 0"
class="big-text"><p>{{ note.title }}</p></span> class="big-text"><p>{{ note.title }}</p></span>
<span class="tags" v-if="note.tags"> <!-- Sub text display -->
<span v-for="tag in (note.tags.split(','))" class="little-tag" v-on:click.stop="$emit('tagClick', tag.split(':')[1] )">#{{ tag.split(':')[0] }}</span> <span v-if="note.subtext.length > 0"
<br> class="small-text"
</span> v-html="note.subtext"></span>
<!-- Not indexed warning -->
<!-- <span v-if="note.indexed != 1">
<span class="green label">Not Indexed</span>
</span> -->
<div class="ui fluid basic button" v-if="note.encrypted == 1">
<i class="green lock icon"></i>
Locked
</div>
<!-- Shared Details --> <!-- Shared Details -->
<span class="subtext" v-if="note.shared == 2"> <span class="subtext" v-if="note.shared == 2">
@ -47,85 +57,28 @@
</span> </span>
</span> </span>
<!-- Sub text display -->
<span v-if="note.subtext.length > 0"
class="small-text"
v-html="note.subtext"></span>
<!-- Not indexed warning -->
<!-- <span v-if="note.indexed != 1">
<span class="green label">Not Indexed</span>
</span> -->
<!-- <div class="ui fluid basic button" v-if="note.encrypted == 1">
<i class="green lock icon"></i>
Locked
</div> -->
</div> </div>
<!-- slim card view --> <div v-if="titleView" class="single-line-text" @click="cardClicked">
<div v-if="titleView" class="thin-container" @click="cardClicked"> <span class="title-line" v-if="note.title.length > 0">{{ note.title }}<br></span>
<span class="sub-line" v-if="note.subtext.length > 0">{{ removeHtml(note.subtext) }}</span>
<!-- icon --> <span v-if="note.title.length == 0 && note.title.length == 0">Empty Note</span>
<span v-if="noteIcon" class="thin-icon">
<i :class="`${noteIcon} icon`" :style="{ 'color':iconColor }"></i>
</span>
<!-- title -->
<span class="thin-title" v-if="note.title.length > 0">{{ note.title }}</span>
<!-- snippet -->
<span class="thick-sub" v-if="note.subtext.length > 0 && note.title.length == 0">
{{ removeHtml(note.subtext) }}
</span>
<span class="thin-sub" v-else-if="note.subtext.length > 0">
{{ removeHtml(note.subtext) }}
</span>
<span v-else-if="note.title.length == 0 && removeHtml(note.subtext).length == 0">
Empty Note
</span>
<!-- tags -->
<span v-if="note.tags" class="thin-tags" >
<span v-for="tag in (note.tags.split(','))" class="little-tag" v-on:click="$emit('tagClick', tag.split(':')[1] )">#{{ tag.split(':')[0] }}
</span>
</span>
<!-- edited -->
<span class="thin-right">
{{$helpers.timeAgo( note.updated )}}
<i class="green link ellipsis vertical icon"></i>
</span>
</div> </div>
<!-- Toolbar on the bottom --> <!-- Toolbar on the bottom -->
<div class="tool-bar" @click.self="cardClicked" v-if="!titleView"> <div class="tool-bar" @click.self="cardClicked" v-if="!titleView">
<div class="icon-bar">
<div v-if="getThumbs.length > 0"> <span class="tags" v-if="note.tags">
<div class="tiny-thumb-box" v-on:click="openEditAttachment"> <span v-for="tag in (note.tags.split(','))" class="little-tag" v-on:click="$emit('tagClick', tag.split(':')[1] )">{{ tag.split(':')[0] }}</span>
<img v-for="thumb in getThumbs" <br>
class="tiny-thumb" </span>
:src="`/api/static/thumb_${thumb}`"
onerror="
this.onerror=null;
this.src='/api/static/assets/marketing/void.svg';
"
/>
</div>
</div>
<div class="icon-bar" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }"> <span class="time-ago-display" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }">
<span class="time-ago-display">
{{$helpers.timeAgo( note.updated )}} {{$helpers.timeAgo( note.updated )}}
</span> </span>
<span class="teeny-buttons"> <span class="teeny-buttons" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }">
<span v-if="!note.trashed"> <span v-if="!note.trashed">
@ -162,13 +115,19 @@
</i> </i>
<delete-button class="teeny-button" :note-id="note.id" /> <delete-button class="teeny-button" :note-id="note.id" />
</span> </span>
</span> </span>
</div> </div>
<div v-if="getThumbs.length > 0">
<div class="tiny-thumb-box" v-on:click="openEditAttachment">
<img v-for="thumb in getThumbs" class="tiny-thumb" :src="`/api/static/thumb_${thumb}`">
</div>
</div>
</div> </div>
<!-- tag edit menu -->
<side-slide-menu v-if="showTagSlideMenu" v-on:close="toggleTags(false)" :full-shadow="true" :skip-history="true"> <side-slide-menu v-if="showTagSlideMenu" v-on:close="toggleTags(false)" :full-shadow="true" :skip-history="true">
<div class="ui basic segment"> <div class="ui basic segment">
<note-tag-edit :noteId="note.id" :key="'display-tags-for-note-'+note.id"/> <note-tag-edit :noteId="note.id" :key="'display-tags-for-note-'+note.id"/>
@ -227,12 +186,10 @@
}, },
pinNote(){ //togglePinned() <- old name pinNote(){ //togglePinned() <- old name
this.showWorking = true this.showWorking = true
this.note.pinned = this.note.pinned == 1 ? 0:1 let postData = {'pinned': !this.note.pinned, 'noteId':this.note.id}
let postData = {'pinned': this.note.pinned, 'noteId':this.note.id}
axios.post('/api/note/setpinned', postData) axios.post('/api/note/setpinned', postData)
.then(data => { .then(data => {
this.showWorking = false this.showWorking = false
// this event is triggered by the server after note is saved
// this.$bus.$emit('update_single_note', this.note.id) // this.$bus.$emit('update_single_note', this.note.id)
}) })
.catch(error => { this.$bus.$emit('notification', 'Failed to Pin Note') }) .catch(error => { this.$bus.$emit('notification', 'Failed to Pin Note') })
@ -248,10 +205,11 @@
//Show message so no one worries where note went //Show message so no one worries where note went
let message = 'Moved to Archive' let message = 'Moved to Archive'
if(postData.archived != 1){ if(postData.archived != 1){
message = 'Moved out of Archive' message = 'Moved to main list'
} }
this.$bus.$emit('notification', message) this.$bus.$emit('notification', message)
this.$bus.$emit('update_single_note', this.note.id)
// this.$bus.$emit('update_single_note', this.note.id)
}) })
.catch(error => { this.$bus.$emit('notification', 'Failed to Archive Note') }) .catch(error => { this.$bus.$emit('notification', 'Failed to Archive Note') })
}, },
@ -266,10 +224,9 @@
//Show message so no one worries where note went //Show message so no one worries where note went
let message = 'Moved to Trash' let message = 'Moved to Trash'
if(postData.trashed == 0){ if(postData.trashed == 0){
message = 'Moved out of Trash' message = 'Moved to main list'
} }
this.$bus.$emit('notification', message) this.$bus.$emit('notification', message)
this.$bus.$emit('update_single_note', this.note.id)
}) })
.catch(error => { this.$bus.$emit('notification', 'Failed to Trash Note') }) .catch(error => { this.$bus.$emit('notification', 'Failed to Trash Note') })
@ -286,11 +243,11 @@
justClosed(){ justClosed(){
// Scroll note into view // Scroll note into view
this.$el.scrollIntoView({ // this.$el.scrollIntoView({
behavior: 'smooth', // behavior: 'smooth',
block: 'center', // block: 'center',
inline: 'center' // inline: 'center'
}) // })
//After scroll, trigger green outline animation //After scroll, trigger green outline animation
setTimeout(() => { setTimeout(() => {
@ -299,7 +256,7 @@
setTimeout(()=>{ setTimeout(()=>{
//After 3 seconds, hide it //After 3 seconds, hide it
this.triggerClosedAnimation = false this.triggerClosedAnimation = false
}, 1500) }, 3000)
}, 500) }, 500)
@ -376,11 +333,13 @@
.teeny-buttons { .teeny-buttons {
float: right; float: right;
width: 65%;
text-align: right; text-align: right;
} }
.time-ago-display { .time-ago-display {
font-size: 11px; width: 35%;
font-weight: bold; float: left;
text-align: center;
} }
.tags { .tags {
width: 100%; width: 100%;
@ -405,7 +364,9 @@
/*Strict font sizes for card display*/ /*Strict font sizes for card display*/
.small-text { .small-text {
max-height: 267px;
width: 100%; width: 100%;
overflow: hidden;
display: inline-block; display: inline-block;
} }
.small-text, .small-text > p, .small-text > h1, .small-text > h2 { .small-text, .small-text > p, .small-text > h1, .small-text > h2 {
@ -453,10 +414,10 @@
.note-title-display-card { .note-title-display-card {
position: relative; position: relative;
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
/*The subtle shadow*/ /*The subtle shadow*/
/*box-shadow: 0px 1px 2px 1px rgba(210, 211, 211, 0.46);*/
box-shadow: 2px 2px 6px 0 rgba(0,0,0,.15); box-shadow: 2px 2px 6px 0 rgba(0,0,0,.15);
transition: box-shadow, border-color ease 0.5s, transform linear 0.5s; transition: box-shadow ease 0.5s, transform linear 0.1s;
margin: 5px; margin: 5px;
/*padding: 0.7em 1em;*/ /*padding: 0.7em 1em;*/
border-radius: .28571429rem; border-radius: .28571429rem;
@ -465,7 +426,7 @@
/*width: calc(33.333% - 10px);*/ /*width: calc(33.333% - 10px);*/
width: calc(25% - 10px); width: calc(25% - 10px);
/*min-width: 190px;*/ /*min-width: 190px;*/
/*min-height: 130px;*/ min-height: 130px;
/*transition: box-shadow 0.3s;*/ /*transition: box-shadow 0.3s;*/
box-sizing: border-box; box-sizing: border-box;
cursor: pointer; cursor: pointer;
@ -474,72 +435,32 @@
letter-spacing: 0.05rem; letter-spacing: 0.05rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch;
text-align: left; text-align: left;
min-height: 100px;
max-height: 450px;
} }
.note-title-display-card:hover { .note-title-display-card:hover {
box-shadow: 0 8px 15px rgba(0,0,0,0.3); /*box-shadow: 0px 2px 2px 1px rgba(210, 211, 211, 0.8);*/
border-color: var(--main-accent); /*transform: translateY(-2px);*/
box-shadow: 0 8px 24px rgba(0,0,0,0.1);
} }
.note-title-display-card.title-view { .note-title-display-card.title-view {
width: 100%; width: 100%;
min-height: 20px; min-height: 20px;
max-width: none; max-width: none;
padding: 10px;
margin: 0;
/*overflow: hidden;*/
border-radius: 0;
border: none;
/*box-shadow: 0px 0px 1px 1px rgba(210, 211, 211, 0.46);*/ /*box-shadow: 0px 0px 1px 1px rgba(210, 211, 211, 0.46);*/
} }
.title-view + .title-view {
border-top: 1px solid var(--border_color);
}
.thin-container.single-line-text { .single-line-text {
width: calc(100% - 25px); width: calc(100% - 25px);
/*margin: 5px 10px;*/ margin: 5px 10px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
box-sizing: border-box; box-sizing: border-box;
} }
.title-line {
.thin-container .thin-title {
font-weight: bold; font-weight: bold;
font-size: 1.2em; font-size: 1.2em;
} padding: 0 20px 0 0;
.thin-container .thin-sub {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
opacity: 0.85;
}
.thin-container .thick-sub {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
line-clamp: 3;
-webkit-box-orient: vertical;
opacity: 0.85;
}
.thin-container .thin-tags {
float: left;
margin-top: 3px;
}
.thin-container .thin-right {
float: right;
color: var(--dark_border_color);
}
.thin-container .thin-icon {
float: right;
} }
.icon-bar { .icon-bar {
@ -547,7 +468,6 @@
padding: 5px 10px 0; padding: 5px 10px 0;
opacity: 1; opacity: 1;
width: 100%; width: 100%;
background-color: rgba(200, 200, 200, 0.2);
} }
.hover-hide { .hover-hide {
opacity: 0.0; opacity: 0.0;
@ -556,6 +476,7 @@
.little-tag { .little-tag {
font-size: 0.7em; font-size: 0.7em;
padding: 5px 5px; padding: 5px 5px;
border: 1px solid var(--border_color);
margin: 0 3px 5px 0; margin: 0 3px 5px 0;
border-radius: 3px; border-radius: 3px;
white-space: nowrap; white-space: nowrap;
@ -565,8 +486,6 @@
line-height: 0.8em; line-height: 0.8em;
text-overflow: ellipsis; text-overflow: ellipsis;
float: left; float: left;
color: var(--main-accent);
opacity: 0.8;
} }
.tiny-thumb-box { .tiny-thumb-box {
max-height: 70px; max-height: 70px;
@ -727,36 +646,4 @@
animation: bgin 4s cubic-bezier(0.19, 1, 0.22, 1) 1; animation: bgin 4s cubic-bezier(0.19, 1, 0.22, 1) 1;
} }
/*switch between ring or BG boy to change save animation*/
.ring {
position: relative;
}
.ring::after {
content: '';
width: 10px;
height: 10px;
border-radius: 100%;
border: 6px solid #00FFCB;
position: absolute;
z-index: 800;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation: ring 1.5s 1;
}
@keyframes ring {
0% {
width: 10px;
height: 10px;
opacity: 1;
}
100% {
width: 420px;
height: 420px;
opacity: 0;
}
}
</style> </style>

View File

@ -1,116 +0,0 @@
<template>
<div>
<div class="ui right floated basic shrinking icon button" v-on:click="showPasteInputArea">
<i class="paste icon"></i>
Paste
</div>
<div class="shade" v-if="showPasteArea" @click.prevent="close">
<div class="ui stackable grid full-height" @click.prevent="close">
<div class="four wide column"></div>
<div class="eight wide middle aligned center aligned column">
<div class="ui raised segment">
<div class="ui dividing header">
<i class="green paste icon"></i>
Paste & automatically Save
</div>
<div class="ui fluid action input">
<input
id="pastetextarea"
type="text"
ref="pastearea"
@paste.prevent="onPaste"
@keyup.enter.prevent="onEnter"
placeholder="Paste Here">
<button class="ui green labeled icon button" @click.prevent="onEnter">
<i class="save icon"></i>
Save
</button>
</div>
</div>
</div>
<div class="four wide column"></div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'PasteButton',
props: {},
data () {
return {
showPasteArea: false,
}
},
methods: {
close(){
this.showPasteArea = false
},
onEnter(e){
const text = this.$refs.pastearea.value
this.saveText(text)
},
onPaste(e){
// Get pasted data via clipboard API
const clipboardData = e.clipboardData || window.clipboardData
const pastedData = String(clipboardData.getData('Text')).trim()
this.saveText(pastedData)
},
saveText(text){
this.showPasteArea = false
if(!text){
this.$bus.$emit('notification', 'Nothing to save.')
return;
}
axios.post('/api/quick-note/update', { 'pushText':text } )
.then( response => {
this.$bus.$emit('notification', 'Saved To Scratch Pad')
})
.catch(error => {
this.$bus.$emit('notification', 'Failed to Save')
})
},
showPasteInputArea(){
// Show text area and focus its contents
this.showPasteArea = true
this.$nextTick(() => {
const aux = document.getElementById('pastetextarea')
aux.focus();
})
// auto hide after 1 Minute
setTimeout(() => {
this.showPasteArea = false
}, 60*1000)
},
}
}
</script>
<style scoped lang="css">
.paste-text-container {
background-color: green;
position: absolute;
width: 50vw;
height: 80vh;
display: inline-block;
}
.full-height {
height: 100vh;
}
</style>

View File

@ -35,6 +35,7 @@
<i class="search icon"></i> <i class="search icon"></i>
</div> </div>
<div class="floating-button" v-if="searchTerm.length > 0"> <div class="floating-button" v-if="searchTerm.length > 0">
<i class="big link grey close icon" v-on:click="clear()"></i> <i class="big link grey close icon" v-on:click="clear()"></i>
</div> </div>

View File

@ -1,9 +1,9 @@
<style type="text/css" scoped> <style type="text/css" scoped>
.slide-container { .slide-container {
position: absolute; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 50%;
bottom: 0; bottom: 0;
z-index: 1020; z-index: 1020;
overflow: hidden; overflow: hidden;
@ -27,7 +27,7 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
color: red; color: red;
background-color: rgba(0,0,0,0.5); /*background-color: rgba(0,0,0,0.5);*/
/*background: linear-gradient(90deg, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0) 55%);*/ /*background: linear-gradient(90deg, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0) 55%);*/
z-index: 1019; z-index: 1019;
overflow: hidden; overflow: hidden;
@ -88,19 +88,19 @@
<div class="slide-container" :style="{ 'background-color':bgColor, 'color':textColor}"> <div class="slide-container" :style="{ 'background-color':bgColor, 'color':textColor}">
<!-- close menu on bottom -->
<div class="note-menu">
<nm-button more-class="right" icon="close" text="close" :show-text="true" v-on:click.native="close" />
</div>
<!-- content of the editor --> <!-- content of the editor -->
<div class="slide-content"> <div class="slide-content">
<slot></slot> <slot></slot>
</div> </div>
<!-- close menu on bottom -->
<div class="note-menu">
<nm-button more-class="right" icon="close" text="close" :show-text="true" v-on:click.native="close" />
</div> </div>
<!-- <div class="slide-shadow" :class="{'full-shadow':fullShadow}" v-on:click="close"></div> --> </div>
<div class="slide-shadow" :class="{'full-shadow':fullShadow}" v-on:click="close"></div>
</div> </div>
<!-- </transition> --> <!-- </transition> -->

File diff suppressed because one or more lines are too long

View File

@ -113,7 +113,6 @@
<style type="text/css"> <style type="text/css">
.button-fix { .button-fix {
display: inline-block; display: inline-block;
float: left;
} }
.hover-row:hover { .hover-row:hover {
cursor: pointer; cursor: pointer;

View File

@ -2,43 +2,22 @@
.colors { .colors {
position: fixed; position: fixed;
z-index: 1023; z-index: 1023;
top: 35px; top: 5px;
/*height: 100px;*/ /*height: 100px;*/
width: 400px; width: 400px;
left: 20%; left: 20%;
} }
.colors-container { .colors-container {
/*max-width: 360px;*/ max-width: 370px;
display: flex;
/*flex-direction: column;*/
flex-wrap: wrap;
justify-content: center;
align-items: stretch;
align-content: stretch;
height: 250px;
width: 100%;
} }
.dot { .dot {
/*display: inline-block;*/ display: inline-block;
width: 30px;
border-radius: 30px;
box-shadow: 0px 0px 0px 1px inset #3e3e3e;
margin: 0 0 2px 2px;
cursor: pointer;
flex-basis: 9%;
height: 30px; height: 30px;
text-align: center; border-radius: 30px;
} box-shadow: 0px 1px 3px 0px #3e3e3e;
.dot > i { margin: 7px 7px 0 0;
margin: 9px 0 0 0; cursor: pointer;
color: white;
text-shadow:
1px 1px 2px #3e3e3e,
1px -1px 2px #3e3e3e,
-1px 1px 2px #3e3e3e,
-1px -1px 2px #3e3e3e
;
} }
.shade { .shade {
position: fixed; position: fixed;
@ -51,16 +30,12 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
} }
.big-shadow {
box-shadow: 0px 4px 5px 1px #a8a8a8;
}
@media only screen and (max-width: 740px) { @media only screen and (max-width: 740px) {
.colors { .colors {
position: fixed; position: fixed;
left: 5px; left: 0;
right: -5px; right: 0;
top: 5px; top: 0;
width: 95%;
} }
} }
</style> </style>
@ -68,15 +43,13 @@
<template> <template>
<div> <div>
<div class="colors"> <div class="colors">
<div class="ui segment big-shadow"> <div class="ui raised segment">
<h3>Select Text Color</h3>
<div class="colors-container"> <div class="colors-container">
<span <span
v-for="(color,index) in colors" v-for="(color,index) in colors"
class="dot" class="dot"
v-on:click="onColorClick(index)" v-on:click="onColorClick(index)"
:style="`background-color: ${color};`"> :style="`background-color: ${color};`">
<i v-if="lastUsedColor == color" class="check icon"></i>
</span> </span>
</div> </div>
</div> </div>
@ -92,7 +65,6 @@
components:{ components:{
'nm-button':require('@/components/NoteMenuButtonComponent.vue').default 'nm-button':require('@/components/NoteMenuButtonComponent.vue').default
}, },
props: [ 'lastUsedColor' ],
data: function(){ data: function(){
return { return {
hover: false, hover: false,

View File

@ -4,6 +4,7 @@
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import 'es6-promise/auto' //Vuex likes promises
import store from './stores/mainStore'; import store from './stores/mainStore';
import App from './App' import App from './App'
@ -13,19 +14,19 @@ import router from './router'
// import 'fomantic-ui-css/semantic.css'; // import 'fomantic-ui-css/semantic.css';
//Required site and reset CSS //Required site and reset CSS
import 'fomantic-ui-css/components/reset.min.css' import 'fomantic-ui-css/components/reset.css'
import 'fomantic-ui-css/components/site.css' //modified to remove included LATO fonts import 'fomantic-ui-css/components/site.css' //modified to remove included LATO fonts
//Only include parts that are used //Only include parts that are used
import 'fomantic-ui-css/components/button.min.css' import 'fomantic-ui-css/components/button.css'
import 'fomantic-ui-css/components/container.min.css' import 'fomantic-ui-css/components/container.css'
import 'fomantic-ui-css/components/form.min.css' import 'fomantic-ui-css/components/form.css'
import 'fomantic-ui-css/components/grid.min.css' import 'fomantic-ui-css/components/grid.css'
import 'fomantic-ui-css/components/header.min.css' import 'fomantic-ui-css/components/header.css'
import 'fomantic-ui-css/components/icon.css' //Modified to remove brand icons import 'fomantic-ui-css/components/icon.css' //Modified to remove brand icons
import 'fomantic-ui-css/components/input.min.css' import 'fomantic-ui-css/components/input.css'
import 'fomantic-ui-css/components/segment.min.css' import 'fomantic-ui-css/components/segment.css'
import 'fomantic-ui-css/components/label.min.css' import 'fomantic-ui-css/components/label.css'
//Overwrite and site styles and themes and good stuff //Overwrite and site styles and themes and good stuff
@ -35,7 +36,7 @@ require('./assets/roboto-latin.woff2')
require('./assets/roboto-latin-bold.woff2') require('./assets/roboto-latin-bold.woff2')
require('./assets/squire.js')
//Import socket io, init using nginx configured socket path //Import socket io, init using nginx configured socket path
import io from 'socket.io-client'; import io from 'socket.io-client';
@ -65,7 +66,9 @@ Vue.use(Vuex)
Vue.config.productionTip = false Vue.config.productionTip = false
new Vue({ new Vue({
el: '#app',
router, router,
store, store,
render: h => h(App), components: { App },
}).$mount('#app') template: '<App/>'
})

View File

@ -9,8 +9,6 @@ const SquireButtonFunctions = {
activeList: false, activeList: false,
activeToDo: false, activeToDo: false,
activeColor: null, activeColor: null,
activeCode: false,
activeSubTitle: false,
// //
lastUsedColor: null, lastUsedColor: null,
} }
@ -30,8 +28,6 @@ const SquireButtonFunctions = {
this.activeToDo = false this.activeToDo = false
this.activeColor = null this.activeColor = null
this.activeUnderline = false this.activeUnderline = false
this.activeCode = false
this.activeSubTitle = false
if(e.path.indexOf('>U>') > -1 || e.path.search(/U$/) > -1){ if(e.path.indexOf('>U>') > -1 || e.path.search(/U$/) > -1){
this.activeUnderline = true this.activeUnderline = true
@ -42,21 +38,15 @@ const SquireButtonFunctions = {
if(e.path.indexOf('>I') > -1){ if(e.path.indexOf('>I') > -1){
this.activeItalics = true this.activeItalics = true
} }
if(e.path.indexOf('fontSize=1.4em') > -1){ if(e.path.indexOf('fontSize') > -1){
this.activeTitle = true this.activeTitle = true
} }
if(e.path.indexOf('fontSize=0.9em') > -1){
this.activeSubTitle = true
}
if(e.path.indexOf('OL>LI') > -1){ if(e.path.indexOf('OL>LI') > -1){
this.activeList = true this.activeList = true
} }
if(e.path.indexOf('UL>LI') > -1){ if(e.path.indexOf('UL>LI') > -1){
this.activeToDo = true this.activeToDo = true
} }
if(e.path.indexOf('CODE') > -1){
this.activeCode= true
}
const colorIndex = e.path.indexOf('color=') const colorIndex = e.path.indexOf('color=')
if(colorIndex > -1){ if(colorIndex > -1){
//Get all digigs after color index, then limit to 3 //Get all digigs after color index, then limit to 3
@ -153,12 +143,6 @@ const SquireButtonFunctions = {
this.editor.italic() this.editor.italic()
} }
}, },
modifyCode(){
this.selectLineIfNoSelect()
this.editor.toggleCode()
},
undoCustom(){ undoCustom(){
//The same as pressing CTRL + Z //The same as pressing CTRL + Z
// this.editor.focus() // this.editor.focus()
@ -357,21 +341,9 @@ const SquireButtonFunctions = {
}, },
setText(inText){ setText(inText){
this.editor.setHTML(inText) this.editor.setHTML(inText)
// this.noteText = this.editor._getHTML() // this.noteText = this.editor._getHTML()
// this.diffNoteText = this.editor._getHTML() // this.diffNoteText = this.editor._getHTML()
//Make sure all list items have draggable property
let container = document.getElementById('squire-id')
let listItems = container.getElementsByTagName('li')
for(let itemIndex in listItems){
// console.log(listItems[itemIndex])
// listItems[itemIndex].setAttribute('draggable','true')
}
// console.log(listItems)
}, },
getText(){ getText(){
@ -404,26 +376,6 @@ const SquireButtonFunctions = {
this.$router.go(-1) this.$router.go(-1)
}, },
indentText(){
// Lists use increase list level, increase quote breaks numbering
if(this.activeList || this.activeToDo){
this.editor.increaseListLevel()
return
}
this.editor.increaseQuoteLevel()
},
outdentText(){
// Lists use increase list level, increase quote breaks numbering
if(this.activeList || this.activeToDo){
this.editor.decreaseListLevel()
return
}
this.editor.decreaseQuoteLevel()
},
}, },
} }

View File

@ -36,32 +36,6 @@
Other Files Other Files
</router-link> </router-link>
<router-link
v-if="$store.getters.totals && $store.getters.totals['archivedNotes']"
exact-active-class="green"
class="ui basic button shrinking"
to="/attachments/type/archived">
<i class="archive icon"></i>
Archived
</router-link>
<router-link
v-if="$store.getters.totals && $store.getters.totals['trashedNotes']"
exact-active-class="green"
class="ui basic button shrinking"
to="/attachments/type/trashed">
<i class="trash icon"></i>
Trashed
</router-link>
<router-link
v-if="$store.getters.totals && $store.getters.totals['sharedToNotes']"
exact-active-class="green"
class="ui basic button shrinking"
to="/attachments/type/shared">
<i class="send icon"></i>
Show Shared
</router-link>
</div> </div>
<div class="sixteen wide column" v-if="searchParams.noteId"> <div class="sixteen wide column" v-if="searchParams.noteId">
@ -191,12 +165,6 @@
this.searchParams.attachmentType = this.$route.params.type this.searchParams.attachmentType = this.$route.params.type
} }
// include files from shared notes or selected notes
this.searchParams.includeShared = false
if(this.$route.params.type == 'shared'){
this.searchParams.includeShared = true
}
//Set noteId in if in URL //Set noteId in if in URL
if(this.$route.params.id){ if(this.$route.params.id){
this.searchParams.noteId = this.$route.params.id this.searchParams.noteId = this.$route.params.id

File diff suppressed because one or more lines are too long

View File

@ -11,29 +11,7 @@
-moz-animation: fadeorama 16s ease infinite; -moz-animation: fadeorama 16s ease infinite;
animation: fadeorama 16s ease infinite; animation: fadeorama 16s ease infinite;
height: 350px; height: 350px;
text-shadow:
1px 1px 1px rgba(69,69,69,0.1),
-1px -1px 1px rgba(69,69,69,0.1),
-1px 1px 1px rgba(69,69,69,0.1),
1px -1px 1px rgba(69,69,69,0.1)
;
} }
.shine {
position: absolute;
width: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: none;
}
.spotlight {
background: rgba(255,255,255,0);
background: radial-gradient(circle at bottom, var(--main-accent) 0%, rgba(255,255,255,0) 60%);
z-index: 200;
}
.logo-display { .logo-display {
width: 140px; width: 140px;
height: auto; height: auto;
@ -46,10 +24,6 @@
font-size: 4rem; font-size: 4rem;
text-align: center; text-align: center;
} }
div#app div.lightly-padded.home-main div.ui.centered.vertically.divided.stackable.grid div.row.hero.fadeBg div.sixteen.wide.middle.aligned.center.column h2.massive-text svg.logo-display path {
stroke: black !important;
stroke-width: 1px !important;
}
.blinking { .blinking {
animation:blinkingText 1.5s linear infinite; animation:blinkingText 1.5s linear infinite;
} }
@ -127,11 +101,10 @@
<!-- <div class="one wide large screen only column"></div> --> <!-- <div class="one wide large screen only column"></div> -->
<!-- desktop column - large screen only --> <!-- desktop column - large screen only -->
<div class="sixteen wide middle aligned center aligned column" style="z-index: 500;"> <div class="sixteen wide middle aligned center aligned column">
<h2 class="massive-text"> <h2 class="massive-text">
<!-- <img class="logo-display" loading="lazy" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo"> --> <img class="logo-display" loading="lazy" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo">
<logo class="logo-display" color="var(--main-accent)" stroke="true" />
<br> <br>
Solid Scribe Solid Scribe
</h2> </h2>
@ -146,37 +119,32 @@
<img loading="lazy" width="90%" src="/api/static/assets/marketing/notebook.svg" alt="The Venus fly laptop about to capture another victim"> <img loading="lazy" width="90%" src="/api/static/assets/marketing/notebook.svg" alt="The Venus fly laptop about to capture another victim">
</div> --> </div> -->
<div v-for="i in jewelFacets" class="shine" :style="shineStyle(i)" v-bind:key="i"></div>
<div class="shine spotlight"></div>
</div> </div>
<!-- All marketing images if you need to review --> <!-- All marketing images if you need to review -->
<div v-if="false" class="sixteen wide column"> <div v-if="false" class="sixteen wide column">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/add.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/add.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/gardening.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/gardening.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/growth.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/growth.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/icecream.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/icecream.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/investing.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/investing.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/onboarding.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/onboarding.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/robot.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/robot.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/solution.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/solution.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/watching.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/watching.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/cloud.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/cloud.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/grandma.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/grandma.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/hamburger.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/hamburger.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/idea.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/idea.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/notebook.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/notebook.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/plan.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/plan.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/secure.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/secure.svg" alt="">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/void.svg"> <img loading="lazy" width="10%" src="/api/static/assets/marketing/void.svg" alt="">
</div> </div>
<!-- Go to notes button --> <!-- Go to notes button -->
<div class="row" v-if="$parent.loggedIn"> <div class="row" v-if="$parent.loggedIn">
<div class="sixteen wide middle algined center aligned column"> <div class="sixteen wide middle algined center aligned column">
<h3>You are already logged in</h3>
<router-link class="ui huge green labeled icon button" to="/notes"> <router-link class="ui huge green labeled icon button" to="/notes">
<i class="external alternate icon"></i>Go to Notes <i class="external alternate icon"></i>Go to Notes
</router-link> </router-link>
@ -199,33 +167,13 @@
<!-- Overview --> <!-- Overview -->
<div class="middle aligned centered row"> <div class="middle aligned centered row">
<div class="six wide column"> <div class="six wide column">
<h2 class="ui dividing header">Powerful text editing and privacy</h2> <h2>Powerful text editing and privacy</h2>
<h3>Easily edit, share and organize thousands of notes.</h3> <h3>Easily edit, share and organize thousands of notes.</h3>
<h3>Feel safe knowing no one can read your notes but you.</h3> <h3>Feel safe knowing no one can read your notes but you.</h3>
<!-- <h3>Tools to organize and collaborate on thousands of notes while maintaining security and respecting your privacy.</h3> --> <!-- <h3>Tools to organize and collaborate on thousands of notes while maintaining security and respecting your privacy.</h3> -->
</div> </div>
<div class="four wide column"> <div class="four wide column">
<svg-displayer file="idea" alt="Explosion of New Ideas" /> <img loading="lazy" width="100%" src="/api/static/assets/marketing/idea.svg" alt="Explosion of New Ideas">
</div>
</div>
<!-- theme selector -->
<div class="ui white row">
<div class="sixteen wide middle aligned column">
<div class="ui container">
<h2 style="color: var(--main-accent);">
Pick your theme
</h2>
<h3 v-if="$parent.loggedIn">Go to settings to change theme</h3>
<div
v-for="color in themeColors"
v-bind:key="color"
class="ui small basic button"
:style="`background: linear-gradient(0deg, ${color} 4%, rgba(0,0,0,0) 5%);`"
v-on:click="setAccentColor(color)">
<logo style="width: 20px; height: auto;" :color="color" />
</div>
</div>
</div> </div>
</div> </div>
@ -235,42 +183,42 @@
<!-- note features --> <!-- note features -->
<div class="six wide column"> <div class="six wide column">
<h1 class="ui center aligned dividing header"><i class="small green sliders horizontal icon"></i>App Features</h1> <h1 class="ui center aligned header"><i class="sliders horizontal icon"></i>App Features</h1>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey sticky note icon"></i> <i class="grey sticky note icon"></i>
<i class="bottom left corner teal plus icon"></i> <i class="bottom left corner teal plus icon"></i>
</i> </i>
Create a million notes! Create as many notes as you want
<div class="sub header">Create unlimited notes up to 5,000,000 characters long.</div> <div class="sub header">Create unlimited notes up to 5,000,000 characters long.</div>
</div> </div>
</h2> </h2>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey tags icon"></i> <i class="grey tags icon"></i>
<i class="bottom left corner purple plus icon"></i> <i class="bottom left corner purple plus icon"></i>
</i> </i>
Tag Notes Tag Notes
<div class="sub header">Add and edit tags on notes then search or sort by tag.</div> <div class="sub header">Easily add and edit tags on notes then search or sort by tag.</div>
</div> </div>
</h2> </h2>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey search icon"></i> <i class="grey search icon"></i>
<i class="bottom left corner orange font icon"></i> <i class="bottom left corner orange font icon"></i>
</i> </i>
Search Note Text Search Note Text
<div class="sub header">Search all notes, files, links and tags.</div> <div class="sub header">Easily search all notes, files, links and tags.</div>
</div> </div>
</h2> </h2>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey search icon"></i> <i class="grey search icon"></i>
@ -281,7 +229,7 @@
</div> </div>
</h2> </h2>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey cloud moon icon"></i> <i class="grey cloud moon icon"></i>
@ -295,8 +243,8 @@
<!-- editing features --> <!-- editing features -->
<div class="six wide column"> <div class="six wide column">
<h1 class="ui center aligned dividing header"><i class="small green sliders horizontal icon"></i>Editing Features</h1> <h1 class="ui center aligned header"><i class="sliders horizontal icon"></i>Editing Features</h1>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey list icon"></i> <i class="grey list icon"></i>
@ -306,7 +254,7 @@
<div class="sub header">Create To Do lists that are always synced, work on mobile and can be sorted.</div> <div class="sub header">Create To Do lists that are always synced, work on mobile and can be sorted.</div>
</div> </div>
</h2> </h2>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey file icon"></i> <i class="grey file icon"></i>
@ -316,7 +264,7 @@
<div class="sub header">Bold, Underline, Title, Add Links, Add Tables, Color Text, Color Background and more.</div> <div class="sub header">Bold, Underline, Title, Add Links, Add Tables, Color Text, Color Background and more.</div>
</div> </div>
</h2> </h2>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey file icon"></i> <i class="grey file icon"></i>
@ -326,7 +274,7 @@
<div class="sub header">Color the background of notes and add colored icons to make them stand out.</div> <div class="sub header">Color the background of notes and add colored icons to make them stand out.</div>
</div> </div>
</h2> </h2>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey images icon"></i> <i class="grey images icon"></i>
@ -336,7 +284,7 @@
<div class="sub header">Upload images to notes, add search text to the images to find them later.</div> <div class="sub header">Upload images to notes, add search text to the images to find them later.</div>
</div> </div>
</h2> </h2>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey users icon"></i> <i class="grey users icon"></i>
@ -353,38 +301,38 @@
<div class="middle aligned centered row"> <div class="middle aligned centered row">
<!-- privacy features --> <!-- privacy features -->
<div class="six wide column"> <div class="six wide column">
<h1 class="ui center aligned dividing header"><i class="small green sliders horizontal icon"></i>Privacy Features</h1> <h1 class="ui center aligned header"><i class="sliders horizontal icon"></i>Privacy Features</h1>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey lock icon"></i> <i class="grey lock icon"></i>
<i class="bottom left corner yellow key icon"></i> <i class="bottom left corner yellow key icon"></i>
</i> </i>
Secure Notes All Note Text is Encrypted
<div class="sub header">All note text is encrypted. No one can read your notes. None of your data is shared.</div> <div class="sub header">All note text is encrypted. No one can read your notes. None of your data is shared.</div>
</div> </div>
</h2> </h2>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey search icon"></i> <i class="grey search icon"></i>
<i class="bottom left corner orange font icon"></i> <i class="bottom left corner orange font icon"></i>
</i> </i>
Private Search Note Search is Encrypted
<div class="sub header">Search the contents of all your notes without compromising security.</div> <div class="sub header">Easily search the contents of all your notes without compromising security.</div>
</div> </div>
</h2> </h2>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey share alternate icon"></i> <i class="grey share alternate icon"></i>
<i class="bottom left corner share icon"></i> <i class="bottom left corner share icon"></i>
</i> </i>
Encrypted Sharing Encrypted Note Sharing
<div class="sub header">Shared notes are still encrypted, only readable by you and the shared users.</div> <div class="sub header">Shared notes are still encrypted, only readable by you and the shared users.</div>
</div> </div>
</h2> </h2>
<h2 class="ui header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey tv icon"></i> <i class="grey tv icon"></i>
@ -397,7 +345,7 @@
</div> </div>
<div class="six wide column"> <div class="six wide column">
<svg-displayer file="onboarding" alt="Observe this chart" /> <img loading="lazy" width="100%" src="/api/static/assets/marketing/onboarding.svg" alt="">
</div> </div>
</div> </div>
@ -405,7 +353,8 @@
<div class="middle aligned centered row"> <div class="middle aligned centered row">
<div class="four wide right aligned column"> <div class="four wide right aligned column">
<svg-displayer file="secure" alt="So dang secure" /> <img loading="lazy" width="100%" src="/api/static/assets/marketing/secure.svg" alt="marketing mumbo jumbo">
</div> </div>
<div class="six wide column"> <div class="six wide column">
<h2>Only you can read your notes. </h2> <h2>Only you can read your notes. </h2>
@ -419,13 +368,13 @@
<h3>Works on mobile or desktop browsers. <br>Behaves like an installed app on mobile phones.</h3> <h3>Works on mobile or desktop browsers. <br>Behaves like an installed app on mobile phones.</h3>
</div> </div>
<div class="four wide right aligned column"> <div class="four wide right aligned column">
<svg-displayer file="cloud" alt="Girl falling into the spiral of digital chaos" /> <img loading="lazy" width="100%" src="/api/static/assets/marketing/cloud.svg" alt="Girl falling into the spiral of digital chaos">
</div> </div>
</div> </div>
<div class="middle aligned centered row"> <div class="middle aligned centered row">
<div class="four wide right aligned column"> <div class="four wide right aligned column">
<svg-displayer file="robot" alt="Murder Robot in office environment" /> <img loading="lazy" width="100%" src="/api/static/assets/marketing/robot.svg" alt="Shrunken man near giant tablet">
</div> </div>
<div class="six wide column"> <div class="six wide column">
<h2>Secure Data Sharing</h2> <h2>Secure Data Sharing</h2>
@ -444,7 +393,7 @@
<a href="https://pi-hole.net/" target="_blank">Pi-hole</a> on the network.</h3> <a href="https://pi-hole.net/" target="_blank">Pi-hole</a> on the network.</h3>
</div> </div>
<div class="four wide column"> <div class="four wide column">
<svg-displayer file="icecream" alt="Emergence of a 4th dimensional being perceived as a large ice cream" /> <img loading="lazy" width="100%" src="/api/static/assets/marketing/icecream.svg" alt="Emergence of a 4th dimensional being perceived as a large ice cream ">
</div> </div>
</div> </div>
@ -493,12 +442,7 @@
<div v-if="true" class="middle aligned centered row"> <div v-if="true" class="middle aligned centered row">
<div class="six wide column"> <div class="six wide column">
<h3> <h2>Solid Scribe was created by one passionate developer</h2>
<a target="_blank" href="https://www.maxg.cc">Solid Scribe was created by Max Gialanella</a>
</h3>
<p><a target="_blank" href="https://www.maxg.cc">Check out my Resume</a></p>
<p>OR</p>
<p><a target="_blank" href="http://blog.maxg.cc">Check out my Programming Blog</a></p>
<p> <p>
I was tired of all my data being owned by big companies, having it farmed out for marketing, and leaving the contents of my life exposed to corporations. I was tired of all my data being owned by big companies, having it farmed out for marketing, and leaving the contents of my life exposed to corporations.
</p> </p>
@ -506,10 +450,9 @@
If you want to give it a shot, feel free to make an account. There are no ads. None of this data is shared or public. I don't make any money. If you want to give it a shot, feel free to make an account. There are no ads. None of this data is shared or public. I don't make any money.
</p> </p>
<p> <p>
If you see anything broken or want to see a feature implemented; I'm open to suggestions. <i class="thumbs up icon"></i> If you see anything broken or want to see a feature implemented, I'm open to suggestions. <i class="thumbs up icon"></i>
</p> </p>
<p>Email me at <a href="mailto:maxgialanella@pm.me">Max.Gialanella@pm.me</a></p> <p>If you want to help me out, I would love a small Bitcoin donation.</p>
<p>If you want to help me out with hosting this application, I would love a small Bitcoin donation.</p>
<p> <p>
<a href="https://btc3.trezor.io/address/3QYnnNKnYTcU82F8NJ1BrmzGU2zRndTyEG" target="_blank"> <a href="https://btc3.trezor.io/address/3QYnnNKnYTcU82F8NJ1BrmzGU2zRndTyEG" target="_blank">
<img loading="lazy" width="160px" src="/api/static/assets/marketing/wallet.png" alt="3QYnnNKnYTcU82F8NJ1BrmzGU2zRndTyEG"> <img loading="lazy" width="160px" src="/api/static/assets/marketing/wallet.png" alt="3QYnnNKnYTcU82F8NJ1BrmzGU2zRndTyEG">
@ -518,12 +461,12 @@
<p>Awesomely Generic Marketing Images - <a target="_blank" href="https://undraw.co/">https://unDraw.co/</a></p> <p>Awesomely Generic Marketing Images - <a target="_blank" href="https://undraw.co/">https://unDraw.co/</a></p>
</div> </div>
<div class="four wide column"> <div class="four wide column">
<svg-displayer file="watching" alt="Drinking the blood of the elderly" /> <img loading="lazy" width="100%" src="/api/static/assets/marketing/watching.svg" alt="Drinking the blood of the elderly">
</div> </div>
</div> </div>
<div class="center aligned sixteen wide column"> <div class="center aligned sixteen wide column">
<router-link to="/terms">Solid Scribe Terms of Use</router-link> <router-link to="/terms"></i>Solid Scribe Terms of Use</router-link>
</div> </div>
@ -536,28 +479,11 @@ export default {
name: 'WelcomePage', name: 'WelcomePage',
components: { components: {
'login-form':require('@/components/LoginFormComponent.vue').default, 'login-form':require('@/components/LoginFormComponent.vue').default,
'logo':require('@/components/LogoComponent.vue').default,
'svg-displayer':require('@/components/SvgDisplayer.vue').default,
}, },
data(){ data(){
return { return {
height: null, height: null,
realInformation: false, realInformation: false,
jewelFacets: 15,
themeColors: [
'#21BA45', //Green
'#b5cc18', //Lime
'#00b5ad', //Teal
'#2185d0', //Blue
'#7128b9', //Violet
'#a333c8', //Purple
'#e03997', //Pink
'#db2828', //Red
'#f2711c', //Orange
'#fbbd08', //Yellow
'#767676', //Grey
'#303030', //Black-almost
],
} }
}, },
beforeCreate(){ beforeCreate(){
@ -571,40 +497,6 @@ export default {
}, },
methods: { methods: {
shineStyle(i){
const farMax = 95 //85
const farMin = 83
const farOut = (Math.floor(Math.random() * (farMax - farMin + 1)) + farMin)
// const rotation = 360/this.jewelFacets
const rotMax = 360/this.jewelFacets
const rotMin = 320/this.jewelFacets
const rotation = (Math.floor(Math.random() * (rotMax - rotMin + 1)) + rotMin)
let style = `
background: linear-gradient(
${(i+1)*(rotation)}deg,
rgba(255,255,255,0) ${farOut}%,
rgba(255,255,255,0.1) ${farOut+1}%,
rgba(255,255,255,0.0) ${farOut+10}%
)
;`
// Remove whitespace - Make it 1 line
return style.replace(/\s+/g, '')
},
setAccentColor(color){
let root = document.documentElement
root.style.setProperty('--main-accent', color)
localStorage.setItem('main-accent', color)
if(!color || color == '#21BA45'){
localStorage.removeItem('main-accent')
}
},
showRealInformation(){ showRealInformation(){

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
<div class="ui grid" ref="content"> <div class="ui grid" ref="content">
<div class="sixteen wide column"> <div class="sixteen wide column">
<!-- :class="{ 'sixteen wide column':showOneColumn 'sixteen wide column':!showOneColumn}" --> <!-- :class="{ 'sixteen wide column':showOneColumn(), 'sixteen wide column':!showOneColumn() }" -->
<div class="ui stackable grid"> <div class="ui stackable grid">
@ -12,12 +12,6 @@
<search-input /> <search-input />
</div> </div>
<div class="sixteen wide column" v-if="$store.getters.totals && $store.getters.totals['showTrackMetricsButton']">
<router-link class="ui fluid green button" to="/metrictrack">
<i class="calendar check outlin icon"></i>Metric Track
</router-link>
</div>
<div class="ten wide column" :class="{ 'sixteen wide column':$store.getters.getIsUserOnMobile }"> <div class="ten wide column" :class="{ 'sixteen wide column':$store.getters.getIsUserOnMobile }">
<div class="ui basic button shrinking" <div class="ui basic button shrinking"
@ -29,17 +23,14 @@
</div> </div>
<tag-display <tag-display
v-if="$store.getters.totals && Object.keys($store.getters.totals['tags'] || {}).length"
:user-tags="$store.getters.totals['tags']"
:active-tags="searchTags" :active-tags="searchTags"
v-on:tagClick="tagId => toggleTagFilter(tagId)" v-on:tagClick="tagId => toggleTagFilter(tagId)"
/> />
<paste-button /> <div class="ui basic shrinking icon button" v-on:click="toggleTitleView()" v-if="$store.getters.totals && $store.getters.totals['totalNotes'] > 0">
<i v-if="titleView" class="th icon"></i>
<span class="ui grey text text-fix"> <i v-if="!titleView" class="bars icon"></i>
Active Sessions {{ $store.getters.getActiveSessions }} </div>
</span>
</div> </div>
@ -54,7 +45,7 @@
</div> </div>
<div class="sixteen wide column" v-if="searchTerm.length > 0 && !showLoading"> <div class="sixteen wide column" v-if="searchTerm.length > 0 && !loadingInProgress">
<h2 class="ui header"> <h2 class="ui header">
<div class="content"> <div class="content">
{{ searchResultsCount.toLocaleString() }} notes with keyword "{{ searchTerm }}" {{ searchResultsCount.toLocaleString() }} notes with keyword "{{ searchTerm }}"
@ -66,15 +57,11 @@
</div> </div>
<div v-if="fastFilters['onlyArchived'] == 1" class="sixteen wide column"> <div v-if="fastFilters['onlyArchived'] == 1" class="sixteen wide column">
<h2> <h2>Archived Notes</h2>
<i class="green archive icon"></i>
Archived Notes</h2>
</div> </div>
<div class="sixteen wide column" v-if="fastFilters['onlyShowTrashed'] == 1"> <div class="sixteen wide column" v-if="fastFilters['onlyShowTrashed'] == 1">
<h2> <h2>Trash
<i class="green trash alternate outline icon"></i>
Trashed Notes
<span>({{ $store.getters.totals['trashedNotes'] }})</span> <span>({{ $store.getters.totals['trashedNotes'] }})</span>
<div class="ui right floated basic button" data-tooltip="This doesn't work yet"> <div class="ui right floated basic button" data-tooltip="This doesn't work yet">
<i class="poo storm icon"></i> <i class="poo storm icon"></i>
@ -84,8 +71,7 @@
</div> </div>
<div class="sixteen wide column" v-if="fastFilters['onlyShowSharedNotes'] == 1"> <div class="sixteen wide column" v-if="fastFilters['onlyShowSharedNotes'] == 1">
<h2><i class="green paper plane outline icon"></i> <h2>Shared Notes</h2>
Shared Notes</h2>
</div> </div>
<div class="sixteen wide column" v-if="tagSuggestions.length > 0"> <div class="sixteen wide column" v-if="tagSuggestions.length > 0">
@ -96,57 +82,6 @@
</div> </div>
</div> </div>
<!-- Note title card display -->
<div class="sixteen wide column">
<h3 v-if="$store.getters.totals && $store.getters.totals['totalNotes'] == 0 && fastFilters['notesHome'] == 1">
No Notes Yet. <br>Thats ok.<br><br> <br>
<img loading="lazy" width="25%" src="/api/static/assets/marketing/hamburger.svg" alt="Create a new note"><br>
Create one when you feel ready.
</h3>
<!-- Go to one wide column, do not do this on mobile interface -->
<div :class="{'one-column':( showOneColumn), 'floating-list':( isFloatingList ), 'hidden-floating-list':(collapseFloatingList)}" v-on:scroll="onScroll">
<div class="ui basic fitted right aligned segment" v-if="isFloatingList">
<div class="ui small basic green left floated button" v-on:click="closeAllNotes()" v-if="openNotes.length >= 1">
<i class="close icon"></i>
Close Notes
</div>
<div class="ui small green button" v-on:click="collapseFloatingList = true">
<i class="caret square left outline icon"></i>
Hide List
</div>
</div>
<!-- render each section based on notes in set -->
<div v-for="section,index in noteSections" v-if="section.length > 0" class="note-card-section">
<h5 class="ui tiny dividing header"><i :class="`green ${sectionData[index][0]} icon`"></i>{{ sectionData[index][1] }}</h5>
<div class="note-card-display-area">
<note-title-display-card
v-on:tagClick="tagId => toggleTagFilter(tagId)"
v-for="note in section"
:ref="'note-'+note.id"
:onClick="openNote"
:data="note"
:title-view="titleView || isFloatingList"
:currently-open="openNotes.includes(note.id)"
:key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated + note.archived + note.pinned + note.trashed"
/>
</div>
</div>
<div class="loading-section" v-if="showLoading">
<loading-icon message="Decrypting Notes" />
</div>
</div>
</div>
<!-- found attachments --> <!-- found attachments -->
<div class="sixteen wide column" v-if="foundAttachments.length > 0"> <div class="sixteen wide column" v-if="foundAttachments.length > 0">
<h5 class="ui tiny dividing header"><i class="green folder open outline icon"></i> Files ({{ foundAttachments.length }})</h5> <h5 class="ui tiny dividing header"><i class="green folder open outline icon"></i> Files ({{ foundAttachments.length }})</h5>
@ -158,24 +93,51 @@
/> />
</div> </div>
</div> <!-- Note title card display -->
<div class="sixteen wide column">
<div class="show-hidden-note-list-button" <h3 v-if="$store.getters.totals && $store.getters.totals['totalNotes'] == 0 && fastFilters['notesHome'] == 1">
v-if="collapseFloatingList && openNotes.length > 0" v-on:click="collapseFloatingList = false"> No Notes Yet. <br>Thats ok.<br><br> <br>
<i class="caret square right outline icon"></i> <img loading="lazy" width="25%" src="/api/static/assets/marketing/hamburger.svg" alt="Create a new note"><br>
</div> Create one when you feel ready.
</h3>
<!-- flexbox note container evenly spaces open notes --> <!-- Go to one wide column, do not do this on mobile interface -->
<div class="note-panel-container" :class="{ 'note-panel-fullwidth':collapseFloatingList}" v-if="openNotes.length"> <div :class="{'one-column':( showOneColumn() )}">
<note-input-panel
v-for="noteId in openNotes" <!-- render each section based on notes in set -->
v-if="noteId != null" <div v-for="section,index in noteSections" v-if="section.length > 0" class="note-card-section">
:key="noteId" <h5 class="ui tiny dividing header"><i :class="`green ${sectionData[index][0]} icon`"></i>{{ sectionData[index][1] }}</h5>
:noteid="noteId"
:url-data="$route.params" <div class="note-card-display-area">
:open-notes="openNotes.length" <note-title-display-card
v-on:tagClick="tagId => toggleTagFilter(tagId)"
v-for="note in section"
:ref="'note-'+note.id"
:onClick="openNote"
:data="note"
:title-view="titleView"
:currently-open="activeNoteId1 == note.id"
:key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated"
/> />
</div> </div>
</div>
<loading-icon v-if="loadingInProgress" message="Decrypting Notes" />
</div>
</div>
</div>
<note-input-panel
v-if="activeNoteId1 != null"
:key="activeNoteId1"
:noteid="activeNoteId1"
:url-data="$route.params"
/>
</div> </div>
</template> </template>
@ -185,7 +147,7 @@
import axios from 'axios' import axios from 'axios'
export default { export default {
name: 'NotesPage', name: 'SearchBar',
components: { components: {
'note-input-panel': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'), 'note-input-panel': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'),
@ -194,9 +156,9 @@
// 'fast-filters': require('@/components/FastFilters.vue').default, // 'fast-filters': require('@/components/FastFilters.vue').default,
'search-input': require('@/components/SearchInput.vue').default, 'search-input': require('@/components/SearchInput.vue').default,
'attachment-display': require('@/components/AttachmentDisplayCard').default, 'attachment-display': require('@/components/AttachmentDisplayCard').default,
'counter':require('@/components/AnimatedCounterComponent.vue').default,
'tag-display':require('@/components/TagDisplayComponent.vue').default, 'tag-display':require('@/components/TagDisplayComponent.vue').default,
'loading-icon':require('@/components/LoadingIconComponent.vue').default, 'loading-icon':require('@/components/LoadingIconComponent.vue').default,
'paste-button':require('@/components/PasteButton.vue').default,
}, },
data () { data () {
return { return {
@ -206,8 +168,6 @@
searchResultsCount: 0, searchResultsCount: 0,
searchTags: [], searchTags: [],
notes: [], notes: [],
openNotes: [],
collapseFloatingList: false,
highlights: [], highlights: [],
searchDebounce: null, searchDebounce: null,
fastFilters: {}, fastFilters: {},
@ -215,10 +175,10 @@
//Load up notes in batches //Load up notes in batches
firstLoadBatchSize: 10, //First set of rapidly loaded notes firstLoadBatchSize: 10, //First set of rapidly loaded notes
batchSize: 20, //Size of batch loaded when user scrolls through current batch batchSize: 25, //Size of batch loaded when user scrolls through current batch
batchOffset: 0, //Tracks the current batch that has been loaded batchOffset: 0, //Tracks the current batch that has been loaded
loadingBatchTimeout: null, //Limit how quickly batches can be loaded loadingBatchTimeout: null, //Limit how quickly batches can be loaded
showLoading: false, loadingInProgress: false,
scrollLoadEnabled: true, scrollLoadEnabled: true,
//Clear button is not visible //Clear button is not visible
@ -264,39 +224,37 @@
this.$parent.loginGateway() this.$parent.loginGateway()
//If user is on title view,
this.titleView = this.$store.getters.getIsUserOnMobile
this.$io.on('new_note_created', noteId => { this.$io.on('new_note_created', noteId => {
// Push new note to top of list and animate //Do not update note if its open
this.updateSingleNote(noteId) if(this.activeNoteId1 != noteId){
this.$store.dispatch('fetchAndUpdateUserTotals') this.$store.dispatch('fetchAndUpdateUserTotals')
this.updateSingleNote(noteId, false)
}
}) })
this.$io.on('note_attribute_modified', noteId => { this.$io.on('note_attribute_modified', noteId => {
const drawFocus = !this.openNotes.includes(parseInt(noteId))
this.updateSingleNote(noteId, drawFocus)
//Do not update note if its open //Do not update note if its open
if(this.openNotes.includes(parseInt(noteId))){ if(this.activeNoteId1 != noteId){
this.$store.dispatch('fetchAndUpdateUserTotals') this.$store.dispatch('fetchAndUpdateUserTotals')
this.updateSingleNote(noteId, false)
} }
}) })
//Update title cards when new note text is saved //Update title cards when new note text is saved
this.$io.on('new_note_text_saved', ({noteId, hash}) => { this.$io.on('new_note_text_saved', ({noteId, hash}) => {
const drawFocus = !this.openNotes.includes(parseInt(noteId)) //Do not update note if its open
this.updateSingleNote(noteId, drawFocus) if(this.activeNoteId1 != noteId){
this.updateSingleNote(noteId, true)
}
}) })
this.$bus.$on('update_single_note', (noteId) => { this.$bus.$on('update_single_note', (noteId) => {
//Do not update note if its open
const drawFocus = !this.openNotes.includes(parseInt(noteId)) if(this.activeNoteId1 != noteId){
this.updateSingleNote(noteId, drawFocus) this.updateSingleNote(noteId)
}
}) })
//Update totals for app //Update totals for app
@ -304,7 +262,19 @@
//Close note event //Close note event
this.$bus.$on('close_active_note', ({noteId, modified}) => { this.$bus.$on('close_active_note', ({noteId, modified}) => {
this.closeNote(noteId, modified)
if(modified){
console.log('Just closed Note -> ' + noteId + ', modified -> ', modified)
}
//A note has been closed
if(this.$route.fullPath != '/notes'){
this.$router.push('/notes')
}
this.$store.dispatch('fetchAndUpdateUserTotals')
//Focus and animate if modified
this.updateSingleNote(noteId, modified)
}) })
this.$bus.$on('note_deleted', (noteId) => { this.$bus.$on('note_deleted', (noteId) => {
@ -351,13 +321,11 @@
//Reload page content - don't trigger if load is in progress //Reload page content - don't trigger if load is in progress
this.$bus.$on('note_reload', () => { this.$bus.$on('note_reload', () => {
if(!this.showLoading){ if(!this.loadingInProgress){
this.reset() this.reset()
} }
}) })
// Window scroll needed when scrolling full page.
// second scroll event added on note-list for floating view scroll detection
window.addEventListener('scroll', this.onScroll) window.addEventListener('scroll', this.onScroll)
//Close notes when back button is pressed //Close notes when back button is pressed
@ -384,9 +352,9 @@
}, },
mounted() { mounted() {
//Open note on PAGE LOAD if ID is set //Open note on load if ID is set
if(this.$route.params.id > 1){ if(this.$route.params.id > 1){
this.openNote(this.$route.params.id) this.activeNoteId1 = this.$route.params.id
} }
//Loads initial batch and tags //Loads initial batch and tags
@ -395,123 +363,34 @@
}, },
watch: { watch: {
'$route.params.id': function(id){ '$route.params.id': function(id){
this.openNote(id) //Open note on ID, null id will close note
}, this.activeNoteId1 = id
'$route' (to, from) {
// Reload the notes if returning to this page
if(to.fullPath == '/notes' && !from.fullPath.includes('/notes/open/')){
this.reset()
}
// Close all notes if returning to /notes page
if(to.fullPath == '/notes' && from.fullPath.includes('/notes/open/')){
this.closeAllNotes()
}
//Lookup tags set in URL
if(to.params.tag && this.$store.getters.totals && this.$store.getters.totals['tags'][to.params.tag]){
//Lookup tag in store by string
const tagObject = this.$store.getters.totals['tags'][to.params.tag]
//Pull key out of string and load tags for that key
this.toggleTagFilter(tagObject.id)
return
}
} }
}, },
computed: { methods: {
isFloatingList(){ toggleTitleView(){
this.titleView = !this.titleView
//If note 1 or 2 is open, show floating column
return (this.openNotes.length > 0)
}, },
showOneColumn(){ showOneColumn(){
return this.$store.getters.getIsUserOnMobile return this.$store.getters.getIsUserOnMobile
} //If note 1 or 2 is open, show one column. Or if the user is on mobile
return (this.activeNoteId1 != null || this.activeNoteId2 != null) &&
!this.$store.getters.getIsUserOnMobile
}, },
methods: {
openNote(id, event = null){ openNote(id, event = null){
//
const intId = parseInt(id)
if(this.openNotes.includes(intId)){
console.log('Open already open note?')
// const openIndex = this.openNotes.indexOf(intId)
// if(openIndex != -1){
// console.log('Open note and remove it ', intId + ' on index ' + openIndex)
// this.openNotes.splice(openIndex, 1)
// }
// this.$bus.$emit('close_note_by_id', intId)
return
}
//Don't open note if a link is clicked in display card //Don't open note if a link is clicked in display card
if(event && event.target && event.target.nodeName){ if(event && event.target && event.target.nodeName){
const nodeClick = event.target.nodeName const nodeClick = event.target.nodeName
if(nodeClick == 'A'){ return } if(nodeClick == 'A'){ return }
} }
// Push note to stack if not open //Open note if a link was not clicked
if(Number.isInteger(intId) && !this.openNotes.includes(intId)){
this.openNotes.push(intId)
}
this.$nextTick(() => {
// change route if open ID is not the same as current ID
if(this.$route.params.id != id){
console.log('Open note, change route -> route id ' + this.$route.params.id + ' note id ->' + id + ', ' +(this.$route.params.id == id))
this.$router.push('/notes/open/'+id) this.$router.push('/notes/open/'+id)
}
})
return return
}, },
closeNote(noteId, modified){
console.log('close note', this.$route.fullPath)
const openIndex = this.openNotes.indexOf(noteId)
if(openIndex != -1){
console.log('Removing note id ', noteId + ' on index ' + openIndex)
this.openNotes.splice(openIndex, 1)
}
// //A note has been closed
// if(this.$route.fullPath != '/notes'){
// this.$router.push('/notes')
// }
if(this.openNotes.length == 0 && this.$route.fullPath != '/notes'){
this.$router.push('/notes')
}
if(modified){
console.log('Just closed Note -> ' + noteId + ', modified -> ', modified)
this.$store.dispatch('fetchAndUpdateUserTotals')
//Focus and animate if modified
this.updateSingleNote(noteId, modified)
}
console.log('closeNote(): Open notes length ', this.openNotes.length)
},
closeAllNotes(){
console.log('Close all notes ------------')
for (let i = this.openNotes.length - 1; i >= 0; i--) {
console.log('Close all notes -> ' + this.openNotes[i])
this.closeNote(this.openNotes[i])
}
console.log('----------------')
},
toggleTagFilter(tagId){ toggleTagFilter(tagId){
this.searchTags = [tagId] this.searchTags = [tagId]
@ -528,10 +407,6 @@
}, },
onScroll(e){ onScroll(e){
if(!this.scrollLoadEnabled){
return
}
clearTimeout(this.loadingBatchTimeout) clearTimeout(this.loadingBatchTimeout)
this.loadingBatchTimeout = setTimeout(() => { this.loadingBatchTimeout = setTimeout(() => {
@ -541,12 +416,12 @@
const height = document.getElementById('app').scrollHeight const height = document.getElementById('app').scrollHeight
//Load if less than 500px from the bottom //Load if less than 500px from the bottom
if(((height - scrolledDown) < 500) && this.scrollLoadEnabled){ if(((height - scrolledDown) < 500) && this.scrollLoadEnabled && !this.loadingInProgress){
this.search(true, this.batchSize, true) this.search(false, this.batchSize, true)
} }
}, 50) }, 30)
return return
@ -567,24 +442,21 @@
} }
this.lastVisibilityState = document.visibilityState this.lastVisibilityState = document.visibilityState
}, },
// @TODO Don't even trigger this if the note wasn't changed // @TODO Don't even trigger this if the note wasn't changed
updateSingleNote(noteId, focuseAndAnimate = true){ updateSingleNote(noteId, focuseAndAnimate = true){
console.log('updating single note', noteId)
noteId = parseInt(noteId) noteId = parseInt(noteId)
//Find local note, if it exists; continue //Find local note, if it exists; continue
let note = null let note = null
if(this.$refs['note-'+noteId]?.[0]?.note){ if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0] && this.$refs['note-'+noteId][0].note){
note = this.$refs['note-'+noteId][0].note note = this.$refs['note-'+noteId][0].note
//Show that note is working on updating //Show that note is working on updating
this.$refs['note-'+noteId][0].showWorking = true this.$refs['note-'+noteId][0].showWorking = true
} }
this.rebuildNoteCategorise()
// return
//Lookup one note using passed in ID //Lookup one note using passed in ID
const postData = { const postData = {
@ -606,7 +478,6 @@
return return
} }
// if old note data and new note data exists
if(note && newNote){ if(note && newNote){
//go through each prop and update it with new values //go through each prop and update it with new values
@ -615,7 +486,7 @@
}) })
//Push new note to front if its modified or we want it to //Push new note to front if its modified or we want it to
if( note.updated != newNote.updated ){ if( focuseAndAnimate || note.updated != newNote.updated ){
// Find note, in section, move to front // Find note, in section, move to front
Object.keys(this.noteSections).forEach( key => { Object.keys(this.noteSections).forEach( key => {
@ -629,9 +500,6 @@
}) })
}) })
}
if( focuseAndAnimate ){
this.$nextTick( () => { this.$nextTick( () => {
//Trigger close animation on note //Trigger close animation on note
this.$refs['note-'+noteId][0].justClosed() this.$refs['note-'+noteId][0].justClosed()
@ -674,14 +542,19 @@
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
//Don't double load note batches //Don't double load note batches
if(this.showLoading){ if(this.loadingInProgress){
console.log('Loading already in progress') console.log('Loading already in progress')
return resolve(false) return resolve(false)
} }
//Reset a lot of stuff if we are not merging batches
if(!mergeExisting){ if(!mergeExisting){
this.batchOffset = 0 // Reset batch offset if we are not merging note batches or new set will be offset from current and overwrite current set with second batch Object.keys(this.noteSections).forEach( key => {
this.noteSections[key] = []
})
this.batchOffset = 0 // Reset batch offset if we are not merging note batches
} }
this.searchResultsCount = 0
//Remove all filter limits from previous queries //Remove all filter limits from previous queries
delete this.fastFilters.limitSize delete this.fastFilters.limitSize
@ -709,40 +582,25 @@
} }
//Perform search - or die //Perform search - or die
this.showLoading = showLoading this.loadingInProgress = true
this.scrollLoadEnabled = false
axios.post('/api/note/search', postData) axios.post('/api/note/search', postData)
.then(response => { .then(response => {
//Reset a lot of stuff if we are not merging batches
if(!mergeExisting){
Object.keys(this.noteSections).forEach( key => {
this.noteSections[key] = []
})
}
this.searchResultsCount = 0
// console.timeEnd('Fetch TitleCard Batch '+notesInNextLoad) // console.timeEnd('Fetch TitleCard Batch '+notesInNextLoad)
//Save the number of notes just loaded //Save the number of notes just loaded
this.batchOffset += response.data.notes.length this.batchOffset += response.data.notes.length
//Enable scroll loading if endpoint retured notes //Enable or disable scroll loading
this.scrollLoadEnabled = response.data.notes.length > 0 this.scrollLoadEnabled = response.data.notes.length > 0
if(response.data.total > 0){ if(response.data.total > 0){
this.searchResultsCount = response.data.total this.searchResultsCount = response.data.total
} }
this.showLoading = false this.loadingInProgress = false
this.generateNoteCategories(response.data.notes, mergeExisting) this.generateNoteCategories(response.data.notes, mergeExisting)
//cache initial notes for faster reloads
if(!mergeExisting && this.showClear == false){
const cachedNotesJson = JSON.stringify(response.data.notes)
localStorage.setItem('snippetCache', cachedNotesJson)
}
return resolve(true) return resolve(true)
}) })
.catch(error => { this.$bus.$emit('notification', 'Failed to Search Notes') }) .catch(error => { this.$bus.$emit('notification', 'Failed to Search Notes') })
@ -857,7 +715,7 @@
//clear out tags //clear out tags
this.searchTags = [] this.searchTags = []
this.tagSuggestions = [] this.tagSuggestions = []
this.showLoading = false this.loadingInProgress = false
this.searchTerm = '' this.searchTerm = ''
this.$bus.$emit('reset_fast_filters') //Clear out search this.$bus.$emit('reset_fast_filters') //Clear out search
@ -874,32 +732,15 @@
filter[options[index]] = 1 filter[options[index]] = 1
this.fastFilters = filter this.fastFilters = filter
//If notes exist in cache, load them up
let showLoading = true
const cachedNotesJson = localStorage.getItem('snippetCache')
const cachedNotes = JSON.parse(cachedNotesJson)
if(cachedNotes && cachedNotes.length > 0 && !this.showClear){
//Load cache. do not merge existing
this.generateNoteCategories(cachedNotes, false)
showLoading = false
}
//Fetch First batch of notes with new filter //Fetch First batch of notes with new filter
this.search(showLoading, this.batchSize, false) this.search(true, this.firstLoadBatchSize, false)
// .then( r => this.search(false, this.batchSize, true)) .then( r => this.search(false, this.batchSize, true))
} }
} }
} }
</script> </script>
<style type="text/css" scoped> <style type="text/css" scoped>
.text-fix {
padding: 8px 0 0 15px;
display: inline-block;
color: var(--menu-accent);
}
.detail { .detail {
float: right; float: right;
} }
@ -917,150 +758,4 @@
.note-card-section + .note-card-section { .note-card-section + .note-card-section {
padding: 15px 0 0; padding: 15px 0 0;
} }
.loading-section {
color: var(--main-accent);
box-shadow: 0 1px 3px 0 var(--main-accent);
border-radius: 6px;
background-color: var(--small_element_bg_color);
display: inline-block;
width: 100%;
margin: 15px 0;
}
.floating-list {
z-index: 1000;
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 25%;
height: 100vh;
background-color: var(--small_element_bg_color);
padding: 15px 5px 0px 10px;
overflow-y: scroll;
overflow-x: hidden;
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox */
background-color: var(--border_color);
}
.floating-list::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
.note-panel-container {
position: fixed;
width: 75%;
height: 100vh;
background: gray;
top: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-items: stretch;
align-content: stretch;
z-index: 1000;
}
.note-panel-fullwidth {
width: 100% !important;
}
.note-panel-container > div {
flex: 1;
position: relative;
}
.hidden-floating-list {
left: -1000px !important;
}
.show-hidden-note-list-button {
position: fixed;
top: 25px;
left: 0;
min-width: 45px;
background-color: var(--main-accent);
color: var(--text_color);
display: block;
z-index: 1100;
cursor: pointer;
border-bottom-right-radius: 5px;
border-top-right-radius: 5px;
padding: 8px 0px 8px 13px;
text-align: left;
font-size: 1.4em;
}
@media (min-width:320px) { /* smartphones, iPhone, portrait 480x320 phones */
.floating-list {
left: -1000px;
}
.note-panel-container {
width: 100%;
}
}
@media (min-width:481px) { /* portrait e-readers (Nook/Kindle), smaller tablets @ 600 or @ 640 wide. */
.floating-list {
left: 0px;
}
.note-panel-container {
width: 75%;
}
}
@media (min-width:641px) { /* portrait tablets, portrait iPad, landscape e-readers, landscape 800x480 or 854x480 phones */
}
@media (min-width:961px) { /* tablet, landscape iPad, lo-res laptops ands desktops */
}
@media (min-width:1025px) { /* big landscape tablets, laptops, and desktops */
}
@media (min-width:1281px) { /* hi-res laptops and desktops */
}
@media (min-width:2000px) { /* BIG hi-res laptops and desktops */
.floating-list {
left: 180px;
width: calc(30% - 180px);
}
.note-panel-container {
width: 70%;
}
}
.master-note-edit {
position: absolute;
width: 100%;
background: var(--small_element_bg_color);
left: 0;
top: 0;
bottom: 0;
overflow: hidden;
}
.master-note-edit + .master-note-edit {
border-left: 2px solid var(--main-accent);
border-left: 5px solid var(--border_color);
}
/*html, body {
height: 100%;
}
.wrap {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}*/
</style> </style>

View File

@ -1,761 +0,0 @@
<template>
<div class="page-container">
<div class="ui grid" ref="content">
<div class="sixteen wide column">
<!-- :class="{ 'sixteen wide column':showOneColumn(), 'sixteen wide column':!showOneColumn() }" -->
<div class="ui stackable grid">
<div class="six wide column" v-if="$store.getters.totals && $store.getters.totals['totalNotes']">
<search-input />
</div>
<div class="ten wide column" :class="{ 'sixteen wide column':$store.getters.getIsUserOnMobile }">
<div class="ui basic button shrinking"
v-on:click="updateFastFilters(3)"
v-if="$store.getters.totals && ($store.getters.totals['youGotMailCount'] > 0)"
style="position: relative;">
<i class="green mail icon"></i>Inbox
<span class="tiny circular floating ui green label">+{{ $store.getters.totals['youGotMailCount'] }}</span>
</div>
<tag-display
:active-tags="searchTags"
v-on:tagClick="tagId => toggleTagFilter(tagId)"
/>
<div class="ui basic shrinking icon button" v-on:click="toggleTitleView()" v-if="$store.getters.totals && $store.getters.totals['totalNotes'] > 0">
<i v-if="titleView" class="th icon"></i>
<i v-if="!titleView" class="bars icon"></i>
</div>
</div>
<div class="eight wide column" v-if="showClear">
<!-- <fast-filters /> -->
<span class="ui fluid green button" @click="reset">
<i class="arrow circle left icon"></i>Show All Notes
</span>
</div>
</div>
</div>
<div class="sixteen wide column" v-if="searchTerm.length > 0 && !loadingInProgress">
<h2 class="ui header">
<div class="content">
{{ searchResultsCount.toLocaleString() }} notes with keyword "{{ searchTerm }}"
<div v-if="searchResultsCount == 0" class="sub header">
Search can only find key words. Try a single word search.
</div>
</div>
</h2>
</div>
<div v-if="fastFilters['onlyArchived'] == 1" class="sixteen wide column">
<h2>Archived Notes</h2>
</div>
<div class="sixteen wide column" v-if="fastFilters['onlyShowTrashed'] == 1">
<h2>Trash
<span>({{ $store.getters.totals['trashedNotes'] }})</span>
<div class="ui right floated basic button" data-tooltip="This doesn't work yet">
<i class="poo storm icon"></i>
Empty Trash
</div>
</h2>
</div>
<div class="sixteen wide column" v-if="fastFilters['onlyShowSharedNotes'] == 1">
<h2>Shared Notes</h2>
</div>
<div class="sixteen wide column" v-if="tagSuggestions.length > 0">
<h5 class="ui tiny dividing header"><i class="green tags icon"></i> Tags ({{ tagSuggestions.length }})</h5>
<div class="ui clickable green label" v-for="tag in tagSuggestions" v-on:click="tagId => toggleTagFilter(tag.id)">
<i class="tag icon"></i>
{{ tag.text }}
</div>
</div>
<!-- found attachments -->
<div class="sixteen wide column" v-if="foundAttachments.length > 0">
<h5 class="ui tiny dividing header"><i class="green folder open outline icon"></i> Files ({{ foundAttachments.length }})</h5>
<attachment-display
v-for="item in foundAttachments"
:item="item"
:key="item.id"
:search-params="{}"
/>
</div>
<!-- Note title card display -->
<div class="sixteen wide column">
<h3 v-if="$store.getters.totals && $store.getters.totals['totalNotes'] == 0 && fastFilters['notesHome'] == 1">
No Notes Yet. <br>Thats ok.<br><br> <br>
<img loading="lazy" width="25%" src="/api/static/assets/marketing/hamburger.svg" alt="Create a new note"><br>
Create one when you feel ready.
</h3>
<!-- Go to one wide column, do not do this on mobile interface -->
<div :class="{'one-column':( showOneColumn() )}">
<!-- render each section based on notes in set -->
<div v-for="section,index in noteSections" v-if="section.length > 0" class="note-card-section">
<h5 class="ui tiny dividing header"><i :class="`green ${sectionData[index][0]} icon`"></i>{{ sectionData[index][1] }}</h5>
<div class="note-card-display-area">
<note-title-display-card
v-on:tagClick="tagId => toggleTagFilter(tagId)"
v-for="note in section"
:ref="'note-'+note.id"
:onClick="openNote"
:data="note"
:title-view="titleView"
:currently-open="activeNoteId1 == note.id"
:key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated"
/>
</div>
</div>
<loading-icon v-if="loadingInProgress" message="Decrypting Notes" />
</div>
</div>
</div>
<note-input-panel
v-if="activeNoteId1 != null"
:key="activeNoteId1"
:noteid="activeNoteId1"
:url-data="$route.params"
/>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'SearchBar',
components: {
'note-input-panel': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'),
'note-title-display-card': require('@/components/NoteTitleDisplayCard.vue').default,
// 'fast-filters': require('@/components/FastFilters.vue').default,
'search-input': require('@/components/SearchInput.vue').default,
'attachment-display': require('@/components/AttachmentDisplayCard').default,
'counter':require('@/components/AnimatedCounterComponent.vue').default,
'tag-display':require('@/components/TagDisplayComponent.vue').default,
'loading-icon':require('@/components/LoadingIconComponent.vue').default,
},
data () {
return {
initComponent: true,
tagSuggestions:[],
searchTerm: '',
searchResultsCount: 0,
searchTags: [],
notes: [],
highlights: [],
searchDebounce: null,
fastFilters: {},
titleView: false,
//Load up notes in batches
firstLoadBatchSize: 10, //First set of rapidly loaded notes
batchSize: 25, //Size of batch loaded when user scrolls through current batch
batchOffset: 0, //Tracks the current batch that has been loaded
loadingBatchTimeout: null, //Limit how quickly batches can be loaded
loadingInProgress: false,
scrollLoadEnabled: true,
//Clear button is not visible
showClear: false,
initialPostData: null,
//Currently open notes in app
activeNoteId1: null,
activeNoteId2: null,
//Position determines how note is Positioned
activeNote1Position: 0,
activeNote2Position: 0,
lastVisibilityState: null,
foundAttachments: [],
sectionData: {
'pinned': ['thumbtack', 'Pinned'],
'archived': ['archive', 'Archived'],
'shared': ['envelope outline', 'Inbox'],
'sent': ['paper plane outline', 'Sent Notes'],
'notes': ['file','Notes'],
'highlights': ['paragraph', 'Found In Text'],
'trashed': ['poop', 'Trashed Notes'],
'tagged': ['tag', 'Tagged'],
},
noteSections: {
pinned: [],
archived: [],
shared:[],
sent:[],
notes: [],
highlights: [],
trashed: [],
tagged:[],
},
}
},
beforeMount(){
this.$parent.loginGateway()
this.$io.on('new_note_created', noteId => {
//Do not update note if its open
if(this.activeNoteId1 != noteId){
this.$store.dispatch('fetchAndUpdateUserTotals')
this.updateSingleNote(noteId, false)
}
})
this.$io.on('note_attribute_modified', noteId => {
//Do not update note if its open
if(this.activeNoteId1 != noteId){
this.$store.dispatch('fetchAndUpdateUserTotals')
this.updateSingleNote(noteId, false)
}
})
//Update title cards when new note text is saved
this.$io.on('new_note_text_saved', ({noteId, hash}) => {
//Do not update note if its open
if(this.activeNoteId1 != noteId){
this.updateSingleNote(noteId, true)
}
})
this.$bus.$on('update_single_note', (noteId) => {
//Do not update note if its open
if(this.activeNoteId1 != noteId){
this.updateSingleNote(noteId)
}
})
//Update totals for app
this.$store.dispatch('fetchAndUpdateUserTotals')
//Close note event
this.$bus.$on('close_active_note', ({noteId, modified}) => {
if(modified){
console.log('Just closed Note -> ' + noteId + ', modified -> ', modified)
}
//A note has been closed
if(this.$route.fullPath != '/notes'){
this.$router.push('/notes')
}
this.$store.dispatch('fetchAndUpdateUserTotals')
//Focus and animate if modified
this.updateSingleNote(noteId, modified)
})
this.$bus.$on('note_deleted', (noteId) => {
//Remove deleted note from set, its deleted
Object.keys(this.noteSections).forEach( key => {
this.noteSections[key].forEach( (note, index) => {
if(note.id == noteId){
this.noteSections[key].splice(index,1)
this.$store.dispatch('fetchAndUpdateUserTotals')
return
}
})
})
})
this.$bus.$on('update_fast_filters', filterIndex => {
this.updateFastFilters(filterIndex)
})
//Event to update search from other areas
this.$bus.$on('update_search_term', sentInSearchTerm => {
this.searchTerm = sentInSearchTerm
this.search(true, this.batchSize)
.then( () => {
this.searchAttachments()
const postData = {
'tagText':this.searchTerm.trim()
}
this.tagSuggestions = []
axios.post('/api/tag/suggest', postData)
.then( response => {
this.tagSuggestions = response.data
})
// return
})
})
//Reload page content - don't trigger if load is in progress
this.$bus.$on('note_reload', () => {
if(!this.loadingInProgress){
this.reset()
}
})
window.addEventListener('scroll', this.onScroll)
//Close notes when back button is pressed
// window.addEventListener('hashchange', this.hashChangeAction)
//update note on visibility change
// document.addEventListener('visibilitychange', this.visibiltyChangeAction);
},
beforeDestroy(){
window.removeEventListener('scroll', this.onScroll)
// document.removeEventListener('visibilitychange', this.visibiltyChangeAction)
this.$bus.$off('note_reload')
this.$bus.$off('close_active_note')
// this.$bus.$off('update_single_note')
this.$bus.$off('note_deleted')
this.$bus.$off('update_fast_filters')
this.$bus.$off('update_search_term')
//We want to remove event listeners, but something here is messing them up and preventing ALL event listeners from working
// this.$off() // Remove all event listeners
// this.$bus.$off()
},
mounted() {
//Open note on load if ID is set
if(this.$route.params.id > 1){
this.activeNoteId1 = this.$route.params.id
}
//Loads initial batch and tags
this.reset()
},
watch: {
'$route.params.id': function(id){
//Open note on ID, null id will close note
this.activeNoteId1 = id
}
},
methods: {
toggleTitleView(){
this.titleView = !this.titleView
},
showOneColumn(){
return this.$store.getters.getIsUserOnMobile
//If note 1 or 2 is open, show one column. Or if the user is on mobile
return (this.activeNoteId1 != null || this.activeNoteId2 != null) &&
!this.$store.getters.getIsUserOnMobile
},
openNote(id, event = null){
//Don't open note if a link is clicked in display card
if(event && event.target && event.target.nodeName){
const nodeClick = event.target.nodeName
if(nodeClick == 'A'){ return }
}
//Open note if a link was not clicked
this.$router.push('/notes/open/'+id)
return
},
toggleTagFilter(tagId){
this.searchTags = [tagId]
//Reset note set and load up notes and tags
if(this.searchTags.length > 0){
this.search(true, this.batchSize)
return
}
//If no tags are selected, reset entire page
this.reset()
},
onScroll(e){
clearTimeout(this.loadingBatchTimeout)
this.loadingBatchTimeout = setTimeout(() => {
//Detect distance scrolled down the page
const scrolledDown = window.pageYOffset + window.innerHeight
//Get height of div to properly detect scroll distance down
const height = document.getElementById('app').scrollHeight
//Load if less than 500px from the bottom
if(((height - scrolledDown) < 500) && this.scrollLoadEnabled && !this.loadingInProgress){
this.search(false, this.batchSize, true)
}
}, 30)
return
},
visibiltyChangeAction(event){
//Fuck this shit, just use web sockets
return
//@TODO - phase this out, update it via socket.io
//If user leaves page then returns to page, reload the first batch
if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible'){
//Load initial batch, then tags, then other batch
this.search(false, this.firstLoadBatchSize)
.then( () => {
// return
})
}
this.lastVisibilityState = document.visibilityState
},
// @TODO Don't even trigger this if the note wasn't changed
updateSingleNote(noteId, focuseAndAnimate = true){
noteId = parseInt(noteId)
//Find local note, if it exists; continue
let note = null
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0] && this.$refs['note-'+noteId][0].note){
note = this.$refs['note-'+noteId][0].note
//Show that note is working on updating
this.$refs['note-'+noteId][0].showWorking = true
}
//Lookup one note using passed in ID
const postData = {
searchQuery: this.searchTerm,
searchTags: this.searchTags,
fastFilters:{
noteIdSet:[noteId]
}
}
//Note data must be fetched, then sorted into existing note data
axios.post('/api/note/search', postData)
.then(results => {
//Pull note data out of note set
let newNote = results.data.notes[0]
if(newNote === undefined){
return
}
if(note && newNote){
//go through each prop and update it with new values
Object.keys(newNote).forEach(prop => {
note[prop] = newNote[prop]
})
//Push new note to front if its modified or we want it to
if( focuseAndAnimate || note.updated != newNote.updated ){
// Find note, in section, move to front
Object.keys(this.noteSections).forEach( key => {
this.noteSections[key].forEach( (searchNote, index) => {
if(searchNote.id == noteId){
//Remove note from location and push to front
this.noteSections[key].splice(index, 1)
this.noteSections[key].unshift(note)
return
}
})
})
this.$nextTick( () => {
//Trigger close animation on note
this.$refs['note-'+noteId][0].justClosed()
this.$refs['note-'+noteId][0].showWorking = false
})
}
}
//New notes don't exist in list, push them to the front
if(note == null){
this.noteSections.notes.unshift(newNote)
//Trigger close animation on note
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0]){
this.$refs['note-'+noteId][0].justClosed()
this.$refs['note-'+noteId][0].showWorking = false
}
}
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0]){
this.$refs['note-'+noteId][0].showWorking = false
}
//Trigger section rebuild
this.rebuildNoteCategorise()
})
.catch(error => {
console.log(error)
this.$bus.$emit('notification', 'Failed to Update Note')
})
},
searchAttachments(){
axios.post('/api/attachment/textsearch', {'searchTerm':this.searchTerm})
.then(results => {
this.foundAttachments = results.data
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Search Attachments') })
},
search(showLoading = true, notesInNextLoad = 10, mergeExisting = false){
return new Promise((resolve, reject) => {
//Don't double load note batches
if(this.loadingInProgress){
console.log('Loading already in progress')
return resolve(false)
}
//Reset a lot of stuff if we are not merging batches
if(!mergeExisting){
Object.keys(this.noteSections).forEach( key => {
this.noteSections[key] = []
})
this.batchOffset = 0 // Reset batch offset if we are not merging note batches
}
this.searchResultsCount = 0
//Remove all filter limits from previous queries
delete this.fastFilters.limitSize
delete this.fastFilters.limitOffset
let postData = {
searchQuery: this.searchTerm,
searchTags: this.searchTags,
fastFilters: this.fastFilters,
}
//Save initial post data on first load
if(this.initialPostData == null){
this.initialPostData = JSON.stringify(postData)
}
//If post data is not the same as initial, show clear button
if(JSON.stringify(postData) != this.initialPostData){
this.showClear = true
}
if(notesInNextLoad && notesInNextLoad > 0){
//Create limit based off of the number of notes already loaded
postData.fastFilters.limitSize = notesInNextLoad
postData.fastFilters.limitOffset = this.batchOffset
}
//Perform search - or die
this.loadingInProgress = true
axios.post('/api/note/search', postData)
.then(response => {
// console.timeEnd('Fetch TitleCard Batch '+notesInNextLoad)
//Save the number of notes just loaded
this.batchOffset += response.data.notes.length
//Enable or disable scroll loading
this.scrollLoadEnabled = response.data.notes.length > 0
if(response.data.total > 0){
this.searchResultsCount = response.data.total
}
this.loadingInProgress = false
this.generateNoteCategories(response.data.notes, mergeExisting)
return resolve(true)
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Search Notes') })
})
},
rebuildNoteCategorise(){
let currentNotes = []
Object.keys(this.noteSections).forEach( key => {
this.noteSections[key].forEach( note => {
currentNotes.push(note)
})
})
this.generateNoteCategories(currentNotes, false)
},
generateNoteCategories(notes, mergeExisting){
// Place each note in a category based on certain attributes and fast filters
//Reset all sections if we are not merging existing
if(!mergeExisting){
Object.keys(this.noteSections).forEach( key => {
this.noteSections[key] = []
})
}
//Sort notes into defined sections
notes.forEach(note => {
if(this.searchTerm.length > 0){
if(note.pinned == 1){
this.noteSections.pinned.push(note)
return
}
//Push to default note section
this.noteSections.notes.push(note)
return
}
//Display all tags in tag section
if(this.searchTags.length >= 1){
this.noteSections.tagged.push(note)
return
}
//Only show trashed notes when trashed
if(this.fastFilters.onlyShowTrashed == 1){
if(note.trashed == 1){
this.noteSections.trashed.push(note)
}
return
}
if(note.trashed == 1){
return
}
//Show archived notes
if(this.fastFilters.onlyArchived == 1){
if(note.pinned == 1 && note.archived == 1){
this.noteSections.pinned.push(note)
return
}
if(note.archived == 1){
this.noteSections.archived.push(note)
}
return
}
if(note.archived == 1){ return }
//Only show sent notes section if shared is selected
if(this.fastFilters.onlyShowSharedNotes == 1){
if(note.shared == 2){
this.noteSections.sent.push(note)
}
if(note.shareUsername != null){
this.noteSections.shared.push(note)
}
return
}
//Show shared notes on main list but not notes shared with you
if(note.shareUsername != null){ return }
// Pinned notes are always first, they can appear in the archive
if(note.pinned == 1){
this.noteSections.pinned.push(note)
return
}
//Push to default note section
this.noteSections.notes.push(note)
return
})
},
reset(){
this.showClear = false
this.scrollLoadEnabled = true
this.searchTerm = ''
this.searchTags = []
this.tagSuggestions = []
this.fastFilters = {}
this.foundAttachments = [] //Remove all attachments
this.updateFastFilters(5) //This loads notes
},
updateFastFilters(index){
//clear out tags
this.searchTags = []
this.tagSuggestions = []
this.loadingInProgress = false
this.searchTerm = ''
this.$bus.$emit('reset_fast_filters') //Clear out search
const options = [
'withLinks', // 'Only Show Notes with Links'
'withTags', // 'Only Show Notes with Tags'
'onlyArchived', //'Only Show Archived Notes'
'onlyShowSharedNotes', //Only show shared notes
'onlyShowTrashed',
'notesHome',
]
let filter = {}
filter[options[index]] = 1
this.fastFilters = filter
//Fetch First batch of notes with new filter
this.search(true, this.firstLoadBatchSize, false)
.then( r => this.search(false, this.batchSize, true))
}
}
}
</script>
<style type="text/css" scoped>
.detail {
float: right;
}
.note-card-display-area {
display: flex;
flex-wrap: wrap;
}
.display-area-title {
width: 100%;
display: inline-block;
}
.note-card-section {
/*padding-bottom: 15px;*/
}
.note-card-section + .note-card-section {
padding: 15px 0 0;
}
</style>

View File

@ -49,7 +49,7 @@
<div class="ui segment"> <div class="ui segment">
<div class="ui stackable grid"> <div class="ui stackable grid">
<div class="six wide column"> <div class="six wide column">
<div class="ui tiny dividing header">1. Enter Password and get QR</div> <p>1. Enter Password and get QR</p>
<div class="ui fluid action input"> <div class="ui fluid action input">
<input type="password" placeholder="Current Password" v-model="password"> <input type="password" placeholder="Current Password" v-model="password">
@ -62,12 +62,12 @@
</div> </div>
</div> </div>
<div class="four wide column"> <div class="four wide column">
<div class="ui tiny dividing header">2. Scan QR Code</div> <p>2. Scan QR Code</p>
<p v-if="qrCode == ''">(QR Code will appear here)</p> <p v-if="qrCode == ''">(QR Code will appear here)</p>
<img v-if="qrCode != ''" :src="qrCode" class="ui image" alt="QR Code"> <img v-if="qrCode != ''" :src="qrCode" alt="QR Code">
</div> </div>
<div class="six wide column"> <div class="six wide column">
<div class="ui tiny dividing header">3. Verify with code</div> <p>3. Verify with code</p>
<div class="ui fluid action input" v-if="qrCode != ''"> <div class="ui fluid action input" v-if="qrCode != ''">
<input type="text" placeholder="Verification Code" v-model="verificationToken" v-on:keyup.enter="verifyQrCode()"> <input type="text" placeholder="Verification Code" v-model="verificationToken" v-on:keyup.enter="verifyQrCode()">
<div class="ui green button"> <div class="ui green button">

View File

@ -12,7 +12,6 @@ const SharePage = () => import(/* webpackChunkName: "SharePage" */ '@/pages/Shar
const NotesPage = () => import(/* webpackChunkName: "NotesPage" */ '@/pages/NotesPage') const NotesPage = () => import(/* webpackChunkName: "NotesPage" */ '@/pages/NotesPage')
const QuickPage = () => import(/* webpackChunkName: "QuickPage" */ '@/pages/QuickPage') const QuickPage = () => import(/* webpackChunkName: "QuickPage" */ '@/pages/QuickPage')
const AttachmentsPage = () => import(/* webpackChunkName: "AttachmentsPage" */ '@/pages/AttachmentsPage') const AttachmentsPage = () => import(/* webpackChunkName: "AttachmentsPage" */ '@/pages/AttachmentsPage')
const OverviewPage = () => import(/* webpackChunkName: "OverviewPage" */ '@/pages/OverviewPage')
const NotFoundPage = () => import(/* webpackChunkName: "404Page" */ '@/pages/NotFoundPage') const NotFoundPage = () => import(/* webpackChunkName: "404Page" */ '@/pages/NotFoundPage')
Vue.use(Router) Vue.use(Router)
@ -43,12 +42,6 @@ export default new Router({
meta: {title: 'Open Note'}, meta: {title: 'Open Note'},
component: NotesPage, component: NotesPage,
}, },
{
path: '/search/tags/:tag',
name: 'Search Notes',
meta: {title: 'Search Notes'},
component: NotesPage,
},
{ {
path: '/notes/open/:id/menu/:openMenu', path: '/notes/open/:id/menu/:openMenu',
name: 'Open Note Menu', name: 'Open Note Menu',
@ -103,24 +96,11 @@ export default new Router({
meta: {title:'Attachments by Type'}, meta: {title:'Attachments by Type'},
component: AttachmentsPage component: AttachmentsPage
}, },
{
path: '/overview',
name: 'Overview of Notes',
meta: {title:'Overview of Notes'},
component: OverviewPage
},
{ {
path: '*', path: '*',
name: 'Page Not Found', name: 'Page Not Found',
meta: {title:'404 Page Not Found'}, meta: {title:'404 Page Not Found'},
component: NotFoundPage component: NotFoundPage
}, },
// Cycle Tracking
{
path: '/metrictrack',
name: 'Metric Tracking',
meta: {title:'Metric Tracking'},
component: () => import(/* webpackChunkName: "MetrictrackingPage" */ '@/pages/MetrictrackingPage')
},
] ]
}) })

View File

@ -1,15 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})

View File

@ -9,9 +9,7 @@ export default new Vuex.Store({
username: null, username: null,
nightMode: false, nightMode: false,
isUserOnMobile: false, isUserOnMobile: false,
fetchTotalsTimeout: null, userTotals: null,
userTotals: null, // {} // setting this to object breaks reactivity
activeSessions: 0,
}, },
mutations: { mutations: {
setUsername(state, username){ setUsername(state, username){
@ -26,7 +24,6 @@ export default new Vuex.Store({
localStorage.removeItem('loginToken') localStorage.removeItem('loginToken')
localStorage.removeItem('username') localStorage.removeItem('username')
localStorage.removeItem('currentVersion') localStorage.removeItem('currentVersion')
localStorage.removeItem('snippetCache')
delete axios.defaults.headers.common['authorizationtoken'] delete axios.defaults.headers.common['authorizationtoken']
state.username = null state.username = null
state.userTotals = null state.userTotals = null
@ -44,15 +41,23 @@ export default new Vuex.Store({
'menu-text': '#5e6268', 'menu-text': '#5e6268',
}, },
'black':{ 'black':{
'body_bg_color': 'linear-gradient(135deg, rgba(0,0,0,1) 0%, rgba(23,12,46,1) 100%)', 'body_bg_color': '#0f0f0f',//'#000',
//'#0f0f0f',//'#000',
'small_element_bg_color': '#000', 'small_element_bg_color': '#000',
'text_color': '#FFF', 'text_color': '#FFF',
'dark_border_color': '#555',//'#ACACAC', //Lighter color to accent elemnts user can interact with 'dark_border_color': '#555',//'#ACACAC', //Lighter color to accent elemnts user can interact with
'border_color': '#505050', 'border_color': '#555',
'menu-accent': '#626262', 'menu-accent': '#626262',
'menu-text': '#d9d9d9', 'menu-text': '#d9d9d9',
}, },
'night':{
'body_bg_color': '#000',
'small_element_bg_color': '#000',
'text_color': '#a98457',
'dark_border_color': '#555',
'border_color': '#555',
'menu-accent': '#626262',
'menu-text': '#a69682',
},
} }
//Catch values not in set //Catch values not in set
@ -101,23 +106,8 @@ export default new Vuex.Store({
state.socket = socket state.socket = socket
}, },
setUserTotals(state, totalsObject){ setUserTotals(state, totalsObject){
//Save all the totals for the user
if(!state.userTotals){ state.userTotals = totalsObject
state.userTotals = {}
}
// retain old values loaded on initial, extended options load
let oldMissingValues = {}
Object.keys(state.userTotals).forEach(key => {
if(!totalsObject[key] && totalsObject[key] !== 0){
oldMissingValues[key] = state.userTotals[key]
}
})
// combine old settings with updated settings
let oldAndNew = Object.assign(oldMissingValues, totalsObject)
state.userTotals = oldAndNew
//Set computer version from server //Set computer version from server
const currentVersion = localStorage.getItem('currentVersion') const currentVersion = localStorage.getItem('currentVersion')
@ -137,15 +127,6 @@ export default new Vuex.Store({
// Object.keys(totalsObject).forEach( key => { // Object.keys(totalsObject).forEach( key => {
// console.log(key + ' -- ' + totalsObject[key]) // console.log(key + ' -- ' + totalsObject[key])
// }) // })
},
setActiveSessions(state, countData){
//Count of the number of active socket.io sessions for this user
state.activeSessions = countData
},
hideMetricTrackingReminder(state){
if(state.userTotals){
state.userTotals['showTrackMetricsButton'] = false
}
} }
}, },
getters: { getters: {
@ -171,19 +152,10 @@ export default new Vuex.Store({
totals: state => { totals: state => {
return state.userTotals return state.userTotals
}, },
getActiveSessions: state => {
return state.activeSessions
}
}, },
actions: { actions: {
fetchAndUpdateUserTotals ({ commit, state }) { fetchAndUpdateUserTotals ({ commit }) {
clearTimeout(state.fetchTotalsTimeout) axios.post('/api/user/totals')
state.fetchTotalsTimeout = setTimeout(() => {
// load extended options on initial load
let postData = {
extendedOptions: !state.userTotals
}
axios.post('/api/user/totals', postData)
.then( ({data}) => { .then( ({data}) => {
commit('setUserTotals', data) commit('setUserTotals', data)
}) })
@ -193,7 +165,6 @@ export default new Vuex.Store({
location.reload() location.reload()
} }
}) })
}, 100)
} }
} }
}) })

View File

@ -1,97 +0,0 @@
#
# Working dev server config
#
server {
listen 80;
listen [::]:80;
server_name 192.168.1.164;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
ssl_certificate /home/mab/ss/client/certs/nginx-selfsigned.crt;
ssl_certificate_key /home/mab/ss/client/certs/nginx-selfsigned.key;
ssl_dhparam /home/mab/ss/client/certs/dhparam.pem;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_protocols TLSV1.1 TLSV1.2 TLSV1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/httpslocalhost.access.log;
error_log /var/log/nginx/httpslocalhost.error.log;
client_max_body_size 20M;
location / {
proxy_pass https://127.0.0.1:8081;
proxy_set_header Host localhost;
proxy_set_header X-Forwarded-Host localhost;
proxy_set_header X-Forwarded-Server localhost;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_redirect off;
proxy_connect_timeout 90s;
proxy_read_timeout 90s;
proxy_send_timeout 90s;
proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
}
location /sockjs-node {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass https://127.0.0.1:8081;
proxy_redirect off;
proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
}
location /api {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
}
location /socket {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
}
# Prod settings to serve static index
# location / {
# autoindex on;
# #try_files $uri $uri/ /index.html;
# }
# location / {
# #autoindex on
#
# proxy_pass http://127.0.0.1:8444;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection 'upgrade';
# proxy_set_header Host $host;
# proxy_cache_bypass $http_upgrade;
# }

View File

@ -12,4 +12,3 @@ bundle.*
client/dist* client/dist*
server/public/* server/public/*
client/dist* client/dist*
*_scrape*

2519
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,21 +9,21 @@
"author": "Max", "author": "Max",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"body-parser": "^1.19.0", "body-parser": "^1.18.3",
"cheerio": "^1.0.0-rc.3", "cheerio": "^1.0.0-rc.3",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"express": "^4.17.1", "express": "^4.16.4",
"express-rate-limit": "^5.1.3", "express-rate-limit": "^5.1.3",
"gm": "^1.23.1", "gm": "^1.23.1",
"helmet": "^4.1.1", "helmet": "^3.23.1",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"module-alias": "^2.2.2", "module-alias": "^2.2.2",
"multer": "^1.4.2", "multer": "^1.4.2",
"mysql2": "^2.2.5", "mysql2": "^1.7.0",
"node-tesseract-ocr": "^2.0.0", "node-tesseract-ocr": "^1.0.0",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"request": "^2.88.2", "request": "^2.88.2",
"request-promise": "^4.2.6", "request-promise": "^4.2.5",
"socket.io": "^2.3.0", "socket.io": "^2.3.0",
"speakeasy": "^2.0.0" "speakeasy": "^2.0.0"
}, },

View File

@ -6,7 +6,6 @@ const speakeasy = require('speakeasy')
let Auth = {} let Auth = {}
const tokenSecretKey = process.env.JSON_KEY const tokenSecretKey = process.env.JSON_KEY
const sessionTokenUses = 300 //Defines number of uses each session token has before being refreshed
//Creates session token //Creates session token
Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => { Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => {
@ -27,7 +26,7 @@ Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) =>
return db.promise().query( return db.promise().query(
'INSERT INTO user_active_session (salt, encrypted_master_password, created, uses, user_hash, session_id) VALUES (?,?,?,?,?,?)', 'INSERT INTO user_active_session (salt, encrypted_master_password, created, uses, user_hash, session_id) VALUES (?,?,?,?,?,?)',
[salt, encryptedMasterPass, created, sessionTokenUses, userHash, sessionId]) [salt, encryptedMasterPass, created, 40, userHash, sessionId])
}) })
.then((r,f) => { .then((r,f) => {

View File

@ -54,7 +54,7 @@ SiteScrape.getCleanUrls = (textBlock) => {
SiteScrape.getHostName = (url) => { SiteScrape.getHostName = (url) => {
var hostname = 'https://'+(new URL(url)).hostname; var hostname = 'https://'+(new URL(url)).hostname;
// console.log('hostname', hostname) console.log('hostname', hostname)
return hostname return hostname
} }
@ -63,95 +63,36 @@ SiteScrape.getDisplayImage = ($, url) => {
const hostname = SiteScrape.getHostName(url) const hostname = SiteScrape.getHostName(url)
let metaImg = $('[property="og:image"]') let metaImg = $('meta[property="og:image"]')
let shortcutIcon = $('[rel="shortcut icon"]') let shortcutIcon = $('link[rel="shortcut icon"]')
let favicon = $('[rel="icon"]') let favicon = $('link[rel="icon"]')
let randomImg = $('img') let randomImg = $('img')
//Set of images we may want gathered from various places in source console.log('----')
let imagesWeWant = []
let thumbnail = ''
//Scrape metadata for page image //Scrape metadata for page image
if(randomImg && randomImg.length > 0){ //Grab the first random image we find
if(randomImg && randomImg[0] && randomImg[0].attribs){
let imgSrcs = [] thumbnail = hostname + randomImg[0].attribs.src
for (let i = 0; i < randomImg.length; i++) { console.log('random img '+thumbnail)
imgSrcs.push( randomImg[i].attribs.src )
} }
//Grab the favicon of the site
const half = Math.ceil(imgSrcs.length / 2)
imagesWeWant = [...imgSrcs.slice(-half), ...imgSrcs.slice(0,half) ]
}
//Grab the shortcut icon
if(favicon && favicon[0] && favicon[0].attribs){ if(favicon && favicon[0] && favicon[0].attribs){
imagesWeWant.push(favicon[0].attribs.href) thumbnail = hostname + favicon[0].attribs.href
console.log('favicon '+thumbnail)
} }
//Grab the shortcut icon //Grab the shortcut icon
if(shortcutIcon && shortcutIcon[0] && shortcutIcon[0].attribs){ if(shortcutIcon && shortcutIcon[0] && shortcutIcon[0].attribs){
imagesWeWant.push(shortcutIcon[0].attribs.href) thumbnail = hostname + shortcutIcon[0].attribs.href
console.log('shortcut '+thumbnail)
} }
//Grab the presentation image for the site //Grab the presentation image for the site
if(metaImg && metaImg[0] && metaImg[0].attribs){ if(metaImg && metaImg[0] && metaImg[0].attribs){
imagesWeWant.unshift(metaImg[0].attribs.content) thumbnail = metaImg[0].attribs.content
} console.log('ogImg '+thumbnail)
// console.log(imagesWeWant)
//Remove everything that isn't an accepted file format
for (let i = imagesWeWant.length - 1; i >= 0; i--) {
let img = String(imagesWeWant[i])
if(
!img.includes('.jpg') &&
!img.includes('.jpeg') &&
!img.includes('.png') &&
!img.includes('.gif')
){
imagesWeWant.splice(i,1)
}
}
//Find if we have absolute thumbnails or not
let foundAbsolute = false
for (let i = imagesWeWant.length - 1; i >= 0; i--) {
let img = imagesWeWant[i]
//Add host name if its not included
if(String(img).includes('//') || String(img).includes('http')){
foundAbsolute = true
break
}
}
//Go through all found images. Grab the one closest to the top. Closer is better
for (let i = imagesWeWant.length - 1; i >= 0; i--) {
let img = imagesWeWant[i]
if(!String(img).includes('//') && foundAbsolute){
continue;
}
//Only add host to images if no absolute images were found
if(!String(img).includes('//') ){
if(img.indexOf('/') != 0){
img = '/' + img
}
img = hostname + img
}
if(img.indexOf('//') == 0){
img = 'https:' + img //Scrape breaks without protocol
}
thumbnail = img
} }
console.log('-----')
return thumbnail return thumbnail
} }

View File

@ -1,12 +1,7 @@
//Set up environmental variables, pulled from ~/.env file used as process.env.DB_HOST //Set up environmental variables, pulled from .env file used as process.env.DB_HOST
const os = require('os') //Used to get path of home directory const os = require('os') //Used to get path of home directory
const result = require('dotenv').config({ path:(os.homedir()+'/.env') }) const result = require('dotenv').config({ path:(os.homedir()+'/.env') })
const ports = {
express: 3000,
socketIo: 3001
}
//Allow user of @ in in require calls. Config in package.json //Allow user of @ in in require calls. Config in package.json
require('module-alias/register') require('module-alias/register')
@ -20,6 +15,7 @@ const helmet = require('helmet')
const express = require('express') const express = require('express')
const app = express() const app = express()
app.use( helmet() ) app.use( helmet() )
const port = 3000
// //
@ -55,31 +51,12 @@ io.on('connection', function(socket){
Auth.decodeToken(token) Auth.decodeToken(token)
.then(userData => { .then(userData => {
socket.join(userData.userId) socket.join(userData.userId)
//Track active logged in user accounts
const usersInRoom = io.sockets.adapter.rooms[userData.userId]
io.to(userData.userId).emit('update_active_user_count', usersInRoom.length)
}).catch(error => { }).catch(error => {
//Don't add user to room if they are not logged in //Don't add user to room if they are not logged in
// console.log(error) // console.log(error)
}) })
}) })
socket.on('get_active_user_count', token => {
Auth.decodeToken(token)
.then(userData => {
socket.join(userData.userId)
//Track active logged in user accounts
const usersInRoom = io.sockets.adapter.rooms[userData.userId]
io.to(userData.userId).emit('update_active_user_count', usersInRoom.length)
}).catch(error => {
// console.log(error)
})
})
//Renew Session tokens when users request a new one //Renew Session tokens when users request a new one
socket.on('renew_session_token', token => { socket.on('renew_session_token', token => {
@ -228,14 +205,14 @@ io.on('connection', function(socket){
} }
}) })
socket.on('disconnect', function(socket){ socket.on('disconnect', function(){
// console.log('user disconnected'); // console.log('user disconnected');
}); });
}); });
http.listen(ports.socketIo, function(){ http.listen(3001, function(){
console.log(`Socke.io: Listening on port ${ports.socketIo}!`) // console.log('socket.io liseting on port 3001');
}); });
//Enable json body parsing in requests. Allows me to post data in ajax calls //Enable json body parsing in requests. Allows me to post data in ajax calls
@ -280,6 +257,7 @@ const printResults = true
let UserTest = require('@models/User') let UserTest = require('@models/User')
let NoteTest = require('@models/Note') let NoteTest = require('@models/Note')
let AuthTest = require('@helpers/Auth') let AuthTest = require('@helpers/Auth')
Auth.test() Auth.test()
UserTest.keyPairTest('genMan30', '1', printResults) UserTest.keyPairTest('genMan30', '1', printResults)
.then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults)) .then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
@ -288,8 +266,9 @@ UserTest.keyPairTest('genMan30', '1', printResults)
Auth.testTwoFactor() Auth.testTwoFactor()
}) })
//Test //Test
app.get('/api', (req, res) => res.send('Solidscribe /API is up and running')) app.get('/api', (req, res) => res.send('Solidscribe API is up and running'))
//Serve up uploaded files //Serve up uploaded files
app.use('/api/static', express.static( __dirname+'/../staticFiles' )) app.use('/api/static', express.static( __dirname+'/../staticFiles' ))
@ -318,13 +297,9 @@ app.use('/api/attachment', attachment)
var quickNote = require('@routes/quicknoteController') var quickNote = require('@routes/quicknoteController')
app.use('/api/quick-note', quickNote) app.use('/api/quick-note', quickNote)
//cycle tracking endpoint
var metricTracking = require('@routes/metrictrackingController')
app.use('/api/metric-tracking', metricTracking)
//Output running status //Output running status
app.listen(ports.express, () => { app.listen(port, () => {
console.log(`Express: Listening on port ${ports.express}!`) // console.log(`Listening on port ${port}!`)
}) })
// //

View File

@ -46,26 +46,16 @@ Attachment.textSearch = (userId, searchTerm) => {
}) })
} }
Attachment.search = (userId, noteId, attachmentType, offset, setSize, includeShared) => { Attachment.search = (userId, noteId, attachmentType, offset, setSize) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let params = [userId] let params = [userId]
let query = ` let query = 'SELECT * FROM attachment WHERE user_id = ? AND visible = 1 '
SELECT attachment.*, note.share_user_id FROM attachment
JOIN note ON (attachment.note_id = note.id)
WHERE attachment.user_id = ? AND visible = 1 `
if(noteId && noteId > 0){ if(noteId && noteId > 0){
// query += 'AND note_id = ? '
// Show everything if note ID is present
//
query += 'AND attachment.note_id = ? '
params.push(noteId) params.push(noteId)
}
} else {
//
// Other filters if NO note id
//
if(attachmentType == 'links'){ if(attachmentType == 'links'){
query += 'AND attachment_type = 1 ' query += 'AND attachment_type = 1 '
@ -74,25 +64,13 @@ Attachment.search = (userId, noteId, attachmentType, offset, setSize, includeSha
query += 'AND attachment_type > 1 ' query += 'AND attachment_type > 1 '
} }
query += `AND note.archived = ${ attachmentType == 'archived' ? '1':'0' } `
query += `AND note.trashed = ${ attachmentType == 'trashed' ? '1':'0' } `
}
if(!noteId){
const sharedOrNot = includeShared ? ' NOT ':' '
query += `AND note.share_user_id IS${sharedOrNot}NULL `
}
query += 'ORDER BY last_indexed DESC ' query += 'ORDER BY last_indexed DESC '
const limitOffset = parseInt(offset, 10) || 0 //Either parse int, or use zero const limitOffset = parseInt(offset, 10) || 0 //Either parse int, or use zero
const parsedSetSize = parseInt(setSize, 10) || 20 const parsedSetSize = parseInt(setSize, 10) || 20 //Either parse int, or use zero
query += ` LIMIT ${limitOffset}, ${parsedSetSize}` query += ` LIMIT ${limitOffset}, ${parsedSetSize}`
console.log(query)
db.promise() db.promise()
.query(query, params) .query(query, params)
.then((rows, fields) => { .then((rows, fields) => {
@ -347,14 +325,14 @@ Attachment.downloadFileFromUrl = (url) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if(url == null || url == undefined || url == ''){ if(url == null){
resolve(null) resolve(null)
} }
const random = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) const random = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
let extension = '' const extension = '.'+url.split('.').pop() //This is throwing an error
let fileName = random+'_scrape' let fileName = random+'_scrape'+extension
let thumbPath = 'thumb_'+fileName const thumbPath = 'thumb_'+fileName
console.log('Scraping image url') console.log('Scraping image url')
console.log(url) console.log(url)
@ -369,8 +347,6 @@ Attachment.downloadFileFromUrl = (url) => {
.on('response', res => { .on('response', res => {
console.log(res.statusCode) console.log(res.statusCode)
console.log(res.headers['content-type']) console.log(res.headers['content-type'])
//Get mime type from header content type
// extension = '.'+String(res.headers['content-type']).split('/').pop()
}) })
.pipe(fs.createWriteStream(filePath+thumbPath)) .pipe(fs.createWriteStream(filePath+thumbPath))
.on('close', () => { .on('close', () => {
@ -378,17 +354,14 @@ Attachment.downloadFileFromUrl = (url) => {
//resize image if its real big //resize image if its real big
gm(filePath+thumbPath) gm(filePath+thumbPath)
.resize(550) //Resize to width of 550 px .resize(550) //Resize to width of 550 px
.quality(85) //compression level 0 - 100 (best) .quality(75) //compression level 0 - 100 (best)
.write(filePath+thumbPath, function (err) { .write(filePath+thumbPath, function (err) {
if(err){ if(err){ console.log(err) }
console.log(err)
return resolve(null)
}
console.log('Saved Image')
return resolve(fileName)
}) })
console.log('Saved Image')
resolve(fileName)
}) })
}) })
} }
@ -423,7 +396,7 @@ Attachment.processUrl = (userId, noteId, url) => {
.query(`INSERT INTO attachment .query(`INSERT INTO attachment
(note_id, user_id, attachment_type, text, url, last_indexed, file_location) (note_id, user_id, attachment_type, text, url, last_indexed, file_location)
VALUES (?, ?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?)`,
[noteId, userId, 1, url, url, created, null]) [noteId, userId, 1, 'Processing...', url, created, null])
.then((rows, fields) => { .then((rows, fields) => {
//Set two bigger variables then return request for processing //Set two bigger variables then return request for processing
request = rp(options) request = rp(options)
@ -448,10 +421,8 @@ Attachment.processUrl = (userId, noteId, url) => {
const keywords = SiteScrape.getKeywords($) const keywords = SiteScrape.getKeywords($)
var desiredSearchText = '' var desiredSearchText = ''
desiredSearchText += pageTitle desiredSearchText += pageTitle + "\n"
if(keywords){ desiredSearchText += keywords
desiredSearchText += "\n" + keywords
}
console.log({ console.log({
pageTitle, pageTitle,

View File

@ -1,71 +0,0 @@
let db = require('@config/database')
let Note = require('@models/Note')
let MetricTracking = module.exports = {};
MetricTracking.get = (userId, masterKey) => {
return new Promise((resolve, reject) => {
db.promise()
.query(`
SELECT note.id FROM note WHERE quick_note = 2 AND user_id = ? LIMIT 1`, [userId])
.then((rows, fields) => {
//Quick Note is set, return note object
if(rows[0][0] != undefined){
let noteId = rows[0][0].id
const note = Note.get(userId, noteId, masterKey)
.then(noteData => {
return resolve(noteData)
})
} else {
return resolve('no data')
}
})
.catch(console.log)
})
}
MetricTracking.create = (userId, masterKey) => {
return new Promise((resolve, reject) => {
let finalId = null
return Note.create(userId, 'Metric Tracking', '', masterKey)
.then(insertedId => {
finalId = insertedId
db.promise().query('UPDATE note SET quick_note = 2 WHERE id = ? AND user_id = ?',[insertedId, userId])
.then((rows, fields) => {
const note = Note.get(userId, finalId, masterKey)
.then(noteData => {
return resolve(noteData)
})
})
})
.catch(console.log)
})
}
MetricTracking.save = (userId, metricData, masterKey) => {
return new Promise((resolve, reject) => {
let finalId = null
MetricTracking.get(userId, masterKey)
.then(noteObject => {
return Note.update(userId, noteObject.id, metricData, noteObject.title, noteObject.color, noteObject.pinned, noteObject.archived, null, masterKey)
})
.then( saveResults => {
return resolve(saveResults)
})
})
}

View File

@ -442,10 +442,6 @@ Note.update = (userId, noteId, noteText, noteTitle, color, pinned, archived, has
}) })
.then((rows, fields) => { .then((rows, fields) => {
if(!rows[0] || !rows[0][0] || !rows[0][0]['note_raw_text_id']){
return reject(false)
}
const textId = rows[0][0]['note_raw_text_id'] const textId = rows[0][0]['note_raw_text_id']
let salt = rows[0][0]['salt'] let salt = rows[0][0]['salt']
let snippetSalt = rows[0][0]['snippet_salt'] let snippetSalt = rows[0][0]['snippet_salt']
@ -662,9 +658,60 @@ Note.delete = (userId, noteId, masterKey = null) => {
}) })
} }
// //text is the current text for the note that will be compared to the text in the database
// Returns noteData Note.getDiffText = (userId, noteId, usersCurrentText, lastUpdated) => {
// return new Promise((resolve, reject) => {
Note.get(userId, noteId)
.then(noteObject => {
if(!noteObject.text || !usersCurrentText || noteObject.encrypted == 1){
return resolve(null)
}
let oldText = noteObject.text.replace(/(\r\n|\n|\r)/gm,"")
let newText = usersCurrentText.replace(/(\r\n|\n|\r)/gm,"")
if(noteObject.updated == lastUpdated){
// console.log('No note diff')
return resolve(null)
}
if(noteObject.updated > lastUpdated){
newText = noteObject.text.replace(/(\r\n|\n|\r)/gm,"")
oldText = usersCurrentText.replace(/(\r\n|\n|\r)/gm,"")
}
const dmp = new DiffMatchPatch.diff_match_patch()
const diff = dmp.diff_main(oldText, newText)
dmp.diff_cleanupSemantic(diff)
const patch_list = dmp.patch_make(oldText, newText, diff);
const patch_text = dmp.patch_toText(patch_list);
//Patch text - shows a list of changes
var patches = dmp.patch_fromText(patch_text);
// console.log(patch_text)
//results[1] - contains diagnostic data for patch apply, its possible it can fail
var results = dmp.patch_apply(patches, oldText);
//Compile return data for front end
const returnData = {
updatedText: results[0],
diffs: results[1].length, //Only use length for now
updated: Math.max(noteObject.updated,lastUpdated) //Return most recently updated date
}
//Final change in notes
// console.log(returnData)
resolve(returnData)
})
})
}
Note.get = (userId, noteId, masterKey) => { Note.get = (userId, noteId, masterKey) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -688,7 +735,6 @@ Note.get = (userId, noteId, masterKey) => {
note_raw_text.text, note_raw_text.text,
note_raw_text.salt, note_raw_text.salt,
note_raw_text.updated as updated, note_raw_text.updated as updated,
GROUP_CONCAT(DISTINCT(tag.text) ORDER BY tag.text DESC) AS tags,
note.id, note.id,
note.user_id, note.user_id,
note.created, note.created,
@ -705,9 +751,7 @@ Note.get = (userId, noteId, masterKey) => {
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id) JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
LEFT JOIN attachment ON (note.id = attachment.note_id) LEFT JOIN attachment ON (note.id = attachment.note_id)
LEFT JOIN user as shareUser ON (note.share_user_id = shareUser.id) LEFT JOIN user as shareUser ON (note.share_user_id = shareUser.id)
LEFT JOIN note_tag ON (note.id = note_tag.note_id AND note_tag.user_id = ?) WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId, noteId])
LEFT JOIN tag ON (note_tag.tag_id = tag.id)
WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId, userId, noteId])
}) })
.then((rows, fields) => { .then((rows, fields) => {
@ -995,7 +1039,6 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
LEFT JOIN attachment ON (note.id = attachment.note_id AND attachment.visible = 1) LEFT JOIN attachment ON (note.id = attachment.note_id AND attachment.visible = 1)
LEFT JOIN user as shareUser ON (note.share_user_id = shareUser.id) LEFT JOIN user as shareUser ON (note.share_user_id = shareUser.id)
WHERE note.user_id = ? WHERE note.user_id = ?
AND note.quick_note <= 1
` `
//If text search returned results, limit search to those ids //If text search returned results, limit search to those ids

View File

@ -13,14 +13,11 @@ QuickNote.get = (userId, masterKey) => {
SELECT note.id FROM note WHERE quick_note = 1 AND user_id = ? LIMIT 1`, [userId]) SELECT note.id FROM note WHERE quick_note = 1 AND user_id = ? LIMIT 1`, [userId])
.then((rows, fields) => { .then((rows, fields) => {
//Quick Note is set, return note object //Quick Note is set, return note text
if(rows[0][0] != undefined){ if(rows[0][0] != undefined){
let noteId = rows[0][0].id let noteId = rows[0][0].id
const note = Note.get(userId, noteId, masterKey) return resolve({'noteId':noteId})
.then(noteData => {
return resolve(noteData)
})
} else { } else {
//Or create a new note and get the id //Or create a new note and get the id
@ -84,7 +81,7 @@ QuickNote.update = (userId, pushText, masterKey) => {
.replace(/&[#A-Za-z0-9]+;/g,'') //Rip out all HTML entities .replace(/&[#A-Za-z0-9]+;/g,'') //Rip out all HTML entities
.replace(/<[^>]+>/g, '') //Rip out all HTML tags .replace(/<[^>]+>/g, '') //Rip out all HTML tags
//Turn links into actual link //Turn links into actual linx
clean = QuickNote.makeUrlLink(clean) clean = QuickNote.makeUrlLink(clean)
if(clean == ''){ clean = '&nbsp;' } if(clean == ''){ clean = '&nbsp;' }
@ -117,7 +114,7 @@ QuickNote.update = (userId, pushText, masterKey) => {
} }
}) })
.then( saveResults => { .then( saveResults => {
return resolve(saveResults) return resolve(true)
}) })
}) })

View File

@ -138,33 +138,6 @@ Tag.get = (userId, noteId) => {
}) })
} }
//
// Get just tag string for note
//
Tag.fornote = (userId, noteId) => {
return new Promise((resolve, reject) => {
db.promise()
.query(`SELECT GROUP_CONCAT(DISTINCT(tag.text) ORDER BY tag.text DESC) AS tags
FROM note_tag
LEFT JOIN tag ON (note_tag.tag_id = tag.id)
WHERE note_tag.note_id = ?
AND user_id = ?;
`, [noteId,userId])
.then((rows, fields) => {
//pull IDs out of returned results
// let ids = rows[0].map( item => {})
resolve( rows[0][0] ) //Return all tags found by query
})
.catch(console.log)
})
}
// //
// Get all tags for a note and concatinate into a string 'all, tags, like, this' // Get all tags for a note and concatinate into a string 'all, tags, like, this'
// //

View File

@ -9,7 +9,7 @@ const speakeasy = require('speakeasy')
let User = module.exports = {} let User = module.exports = {}
const version = '3.6.3' const version = '3.1.6'
//Login a user, if that user does not exist create them //Login a user, if that user does not exist create them
//Issues login token //Issues login token
@ -193,19 +193,17 @@ User.register = (username, password) => {
} }
//Counts notes, pinned notes, archived notes, shared notes, unread notes, total files and types //Counts notes, pinned notes, archived notes, shared notes, unread notes, total files and types
User.getCounts = (userId, extendedOptions) => { User.getCounts = (userId) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let countTotals = { let countTotals = {}
tags: {} const userHash = cs.hash(String(userId)).toString('base64')
}
// const userHash = cs.hash(String(userId)).toString('base64')
db.promise().query( db.promise().query(
`SELECT `SELECT
SUM(archived = 1 && share_user_id IS NULL && trashed = 0) AS archivedNotes, SUM(archived = 1 && share_user_id IS NULL && trashed = 0) AS archivedNotes,
SUM(trashed = 1) AS trashedNotes, SUM(trashed = 1) AS trashedNotes,
SUM(share_user_id IS NULL && trashed = 0 AND quick_note < 2) AS totalNotes, SUM(share_user_id IS NULL && trashed = 0) AS totalNotes,
SUM(share_user_id IS NOT null && opened IS null && trashed = 0) AS youGotMailCount, SUM(share_user_id IS NOT null && opened IS null && trashed = 0) AS youGotMailCount,
SUM(share_user_id != ? && trashed = 0) AS sharedToNotes SUM(share_user_id != ? && trashed = 0) AS sharedToNotes
FROM note FROM note
@ -246,73 +244,17 @@ User.getCounts = (userId, extendedOptions) => {
Object.assign(countTotals, rows[0][0]) //combine results Object.assign(countTotals, rows[0][0]) //combine results
//Count usages of user tags, sort by most popular
return db.promise().query(`
SELECT
tag.text, COUNT(tag_id) AS uses, tag.id
FROM note_tag
JOIN tag ON (tag.id = note_tag.tag_id)
WHERE user_id = ?
GROUP BY tag_id
ORDER BY uses DESC
LIMIT 16
`, [userId])
}).then( (rows, fields) => {
//Convert everything to an int or 0 //Convert everything to an int or 0
Object.keys(countTotals).forEach( key => { Object.keys(countTotals).forEach( key => {
const count = parseInt(countTotals[key]) const count = parseInt(countTotals[key])
countTotals[key] = count ? count : 0 countTotals[key] = count ? count : 0
}) })
//Build out tags object
let tagsObject = {}
rows[0].forEach(tagRow => {
tagsObject[tagRow['text']] = {'id':tagRow.id, 'uses':tagRow.uses}
})
//Assign after counts are updated
countTotals['tags'] = tagsObject
countTotals['currentVersion'] = version countTotals['currentVersion'] = version
// Allow for extended options set on page load
if(extendedOptions){
db.promise().query(
`SELECT updated FROM note
JOIN note_raw_text ON note_raw_text.id = note.note_raw_text_id
WHERE note.quick_note = 2
AND user_id = ?`, [userId])
.then( (rows, fields) => {
if(rows[0][0] && rows[0][0].updated){
const lastOpened = rows[0][0].updated
const timeDiff = Math.round(((+new Date) - (lastOpened))/1000)
const hoursInSeconds = (12 * 60 * 60) //12 hours
// Show metric tracking button if its been 12 hours since last entry
if(lastOpened && timeDiff > hoursInSeconds){
countTotals['showTrackMetricsButton'] = true
}
}
resolve(countTotals) resolve(countTotals)
}) })
} else {
resolve(countTotals)
}
})
}) })
} }

View File

@ -26,7 +26,7 @@ router.use(function setUserId (req, res, next) {
}) })
router.post('/search', function (req, res) { router.post('/search', function (req, res) {
Attachment.search(userId, req.body.noteId, req.body.attachmentType, req.body.offset, req.body.setSize, req.body.includeShared) Attachment.search(userId, req.body.noteId, req.body.attachmentType, req.body.offset, req.body.setSize)
.then( data => res.send(data) ) .then( data => res.send(data) )
}) })

View File

@ -1,45 +0,0 @@
//
// /api/metric-tracking
//
var express = require('express')
var router = express.Router()
let MetricTracking = require('@models/MetricTracking');
let userId = null
let masterKey = null
// middleware that is specific to this router
router.use(function setUserId (req, res, next) {
//Session key is required to continue
if(!req.headers.sessionId){
next('Unauthorized')
}
if(req.headers.userId){
userId = req.headers.userId
masterKey = req.headers.masterKey
next()
}
})
router.post('/get', function (req, res) {
MetricTracking.get(userId, masterKey)
.then( data => res.send(data) )
})
router.post('/create', function (req, res) {
MetricTracking.create(userId, masterKey)
.then( data => res.send(data) )
})
//Push text to quick note
router.post('/save', function (req, res) {
MetricTracking.save(userId, req.body.cycleData, masterKey)
.then( data => res.send(data) )
})
module.exports = router

View File

@ -60,6 +60,14 @@ router.post('/search', function (req, res) {
}) })
}) })
router.post('/difftext', function (req, res) {
Note.getDiffText(userId, req.body.noteId, req.body.text, req.body.updated)
.then( fullDiffText => {
//Response should be full diff text
res.send(fullDiffText)
})
})
router.post('/reindex', function (req, res) { router.post('/reindex', function (req, res) {
Note.reindex(userId, masterKey) Note.reindex(userId, masterKey)
.then( data => { .then( data => {

View File

@ -50,12 +50,6 @@ router.post('/get', function (req, res) {
.then( data => res.send(data) ) .then( data => res.send(data) )
}) })
//Get the latest notes the user has created
router.post('/fornote', function (req, res) {
Tags.fornote(userId, req.body.noteId)
.then( data => res.send(data) )
})
//Get all the tags for this user in order of usage //Get all the tags for this user in order of usage
router.post('/usertags', function (req, res) { router.post('/usertags', function (req, res) {
Tags.userTags(userId, req.body.searchQuery, req.body.searchTags, req.body.fastFilters) Tags.userTags(userId, req.body.searchQuery, req.body.searchTags, req.body.fastFilters)

View File

@ -53,7 +53,7 @@ router.post('/revokesessions', function(req, res) {
// fetch counts of users notes // fetch counts of users notes
router.post('/totals', function (req, res) { router.post('/totals', function (req, res) {
User.getCounts(req.headers.userId, req.body.extendedOptions) User.getCounts(req.headers.userId)
.then( countsObject => res.send( countsObject )) .then( countsObject => res.send( countsObject ))
}) })

View File

@ -1,11 +1,10 @@
#!/bin/bash #!/bin/bash
cd /home/mab/ss echo 'Make sure this is being run from root folder of project'
echo '::--:: Starting dev server. cd client; npm run serve -> 192.168.1.164:8081' echo 'Starting Client webpack dev server (/app), in a screen, watching for file changes...'
screen -dmS "NoteClientScreen" bash -c "cd /home/mab/ss/client; npm run serve -- --port 8081 --https true" screen -dm bash -c "cd client/; npm run watch"
echo '::--:: Starting API server (/api), watching for file changes...' echo 'Starting API server (/api), watching for file changes...'
cd /home/mab/ss/server cd server
pm2 flush
pm2 start ecosystem.config.js pm2 start ecosystem.config.js

4
staticFiles/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*
*/
!.gitignore
!assets

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" id="svg8" version="1.1" viewBox="0 0 132.29166 132.29167" height="500" width="500">
<defs id="defs2"/>
<metadata id="metadata5">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g style="display:inline" transform="translate(0,-164.70832)" id="layer1">
<path id="path3813-4" d="m 56.22733,165.36641 -55.56249926,15.875 8e-7,63.5 47.62499846,11.90625 v 27.78125 l -47.76066333,-13.9757 0.13566407,10.00695 55.56249926,15.875 v -47.625 l -47.6249985,-11.90625 -8e-7,-47.625 47.7606633,-13.94121 c 0.135664,-2.30629 -0.135664,-9.87129 -0.135664,-9.87129 z" style="fill:#0f7425;fill-opacity:1;stroke:none;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;shape-rendering:crispedges"/>
<path id="path4563" d="m 20.508581,220.92891 c 15.265814,-14.23899 27.809717,-7.68002 39.687499,3.96875 v -7.9375 C 51.75093,200.8366 37.512584,206.01499 20.508581,205.05391 Z" style="fill:#04cb03;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;shape-rendering:crispedges"/>
<path id="path4563-6" d="m 111.78985,220.92891 c -15.265834,-14.23899 -27.809737,-7.68002 -39.68752,3.96875 v -7.9375 c 8.445151,-16.12356 22.683497,-10.94517 39.68752,-11.90625 z" style="display:inline;fill:#04cb03;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;shape-rendering:crispedges"/>
<path id="path3813-4-2" d="m 76.07108,165.36641 55.5625,15.875 v 63.5 l -47.625,11.90625 v 27.78125 l 47.76067,-13.9757 -0.13567,10.00695 -55.5625,15.875 v -47.625 l 47.625,-11.90626 V 189.17891 L 75.93542,175.2377 c -0.13567,-2.30629 0.13566,-9.87129 0.13566,-9.87129 z" style="display:inline;fill:#04cb03;fill-opacity:1;stroke:none;stroke-width:0.52916676;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;shape-rendering:crispedges"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,24 +0,0 @@
{
"theme_color":"#000",
"background_color": "#000",
"description": "Take Notes",
"display": "standalone",
"icons": [
{
"src": "/api/static/assets/logo.png",
"sizes": "496x496",
"type": "image/png",
"purpose": "any"
},
{
"src": "/api/static/assets/maskable_icon.png",
"sizes": "826x826",
"type": "image/png",
"purpose": "maskable"
}
],
"name": "Solid Scribe",
"short_name": "Solid Scribe",
"start_url": "/#/notes",
"author":"Max"
}

View File

@ -1,15 +0,0 @@
{
"background_color": "purple",
"description": "Take Notes",
"display": "fullscreen",
"icons": [
{
"src": "/api/static/assets/favicon.ico",
"sizes": "192x192",
"type": "image/png"
}
],
"name": "Notes",
"short_name": "Notes",
"start_url": "/#/notes"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" id="svg8" version="1.1" viewBox="0 0 132.29166 132.29167" height="500" width="500">
<defs id="defs2"/>
<metadata id="metadata5">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g style="display:inline" transform="translate(0,-164.70832)" id="layer1">
<path id="path3813-4" d="m 56.22733,165.36641 -55.56249926,15.875 8e-7,63.5 47.62499846,11.90625 v 27.78125 l -47.76066333,-13.9757 0.13566407,10.00695 55.56249926,15.875 v -47.625 l -47.6249985,-11.90625 -8e-7,-47.625 47.7606633,-13.94121 c 0.135664,-2.30629 -0.135664,-9.87129 -0.135664,-9.87129 z" style="fill:#0f7425;fill-opacity:1;stroke:none;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
<path id="path4563" d="m 20.508581,220.92891 c 15.265814,-14.23899 27.809717,-7.68002 39.687499,3.96875 v -7.9375 C 51.75093,200.8366 37.512584,206.01499 20.508581,205.05391 Z" style="fill:#04cb03;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
<path id="path4563-6" d="m 111.78985,220.92891 c -15.265834,-14.23899 -27.809737,-7.68002 -39.68752,3.96875 v -7.9375 c 8.445151,-16.12356 22.683497,-10.94517 39.68752,-11.90625 z" style="display:inline;fill:#04cb03;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
<path id="path3813-4-2" d="m 76.07108,165.36641 55.5625,15.875 v 63.5 l -47.625,11.90625 v 27.78125 l 47.76067,-13.9757 -0.13567,10.00695 -55.5625,15.875 v -47.625 l 47.625,-11.90626 V 189.17891 L 75.93542,175.2377 c -0.13567,-2.30629 0.13566,-9.87129 0.13566,-9.87129 z" style="display:inline;fill:#04cb03;fill-opacity:1;stroke:none;stroke-width:0.52916676;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,24 +0,0 @@
{
"theme_color":"#000",
"background_color": "#000",
"description": "Take Notes",
"display": "standalone",
"icons": [
{
"src": "/api/static/assets/logo.png",
"sizes": "496x496",
"type": "image/png",
"purpose": "any"
},
{
"src": "/api/static/assets/maskable_icon.png",
"sizes": "826x826",
"type": "image/png",
"purpose": "maskable"
}
],
"name": "Solid Scribe",
"short_name": "Solid Scribe",
"start_url": "/#/notes",
"author":"Max"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@ -12,4 +12,4 @@
# z - Compress for speed # z - Compress for speed
# h - Human Readable file sizes # h - Human Readable file sizes
rsync -e 'ssh' --exclude-from=dontSync.txt -havzC --update mab@marvin.local:/home/mab/ss/ . rsync -e 'ssh' --exclude-from=dontSync.txt -havzC --update mab@marvin.local:/home/mab/pi/ .

View File

@ -1,22 +0,0 @@
#!/bin/bash
# Setup env variables
source ~/.env
# Send updated dynamic IP address to Namecheap, in order to update subdomains.
# This uses curl (separate pkg) to send the change; Namecheap automatically detects source IP if the ip field (like domain, password) ..
# is not specified.
# info helper
info() { printf "\n%s %s\n\n" "$( date )" "$*" >&2; }
info "Starting IP update for subdomains"
echo "https://dynamicdns.park-your-domain.com/update?host=$DYDNS_HOST&domain=$DYDNS_DOMAIN&password=$DYDNS_PASS"
# first subdomain
curl "https://dynamicdns.park-your-domain.com/update?host=$DYDNS_HOST&domain=$DYDNS_DOMAIN&password=$DYDNS_PASS"
# second subdomain
curl "https://dynamicdns.park-your-domain.com/update?host=$DYDNS_HOST2&domain=$DYDNS_DOMAIN&password=$DYDNS_PASS"
info "IP update done"