Compare commits

...

2 Commits

Author SHA1 Message Date
Max G
21f606b480 * Fixed a bunch of little bugs
* Added more options to attachment page and filters
* Much better rendering and updating on attachment page
* Math bug is fixed with better string parsing fixes #14
* Icons are limited to 4 per note
* If an image is visible on note preview it will not appear in images preview
* Touched up text algorithm to better display note titles
2020-02-23 06:27:49 +00:00
Max G
b961a69a91 Added a function to calculate math on notes 2020-02-19 00:31:18 +00:00
13 changed files with 263 additions and 36 deletions

View File

@ -8,7 +8,7 @@
<link rel="shortcut icon" href="/api/static/assets/favicon.ico" type="image/x-icon"/>
<meta name="theme-color" content="#000" />
<link rel="manifest" href="/api/static/assets/manifest.webmanifest">
<link rel="manifest" href="/api/static/assets/manifest.json">
<title>Notes</title>
</head>

View File

@ -55,6 +55,9 @@ body {
background-color: var(--background_color);
border-color: var(--border_color);
}
.ui.icon.input > i.icon {
color: var(--text_color);
}
div.ui.basic.green.label {
background-color: var(--background_color) !important;
}

View File

@ -98,7 +98,12 @@
</div>
<div class="ten wide column">
<textarea ref="edit" class="text" v-on:blur="saveIt()" v-on:keyup="checkKeyup" v-model="text"></textarea>
<textarea ref="edit" class="text" v-on:blur="saveIt()" v-on:keyup="checkKeyup"
v-model="text"
v-on:focus="showSave = true"
></textarea>
<div v-if="showSave" class="ui green button">Save</div>
<!-- link -->
<a class="link" :href="linkUrl" target="_blank">{{linkText}}</a>
@ -139,6 +144,7 @@
unfolded:true,
visible: true,
showSave: false,
working: false,
}
@ -199,6 +205,8 @@
},
saveIt(){
this.showSave = false
//Don't save text if it didn'th change
if(this.item.text == this.text){
return

View File

@ -166,7 +166,7 @@
<div class="menu-section" v-if="loggedIn && $store.getters.totals && $store.getters.totals['totalFiles']">
<router-link class="menu-item menu-button" exact-active-class="active" to="/attachments">
<i class="folder open outline icon"></i>Files
<i class="open folder outline icon"></i>Files
<counter class="float-right" number-id="totalFiles" />
</router-link>
</div>
@ -233,11 +233,13 @@
this.mobile = this.$store.getters.getIsUserOnMobile
this.collapsed = this.$store.getters.getIsUserOnMobile
// {{ totals['totalNotes'] }}
if(this.mobile){
this.menuOpen = false
}
if(this.loggedIn){
this.$store.dispatch('fetchAndUpdateUserTotals')
}
},
computed: {

View File

@ -158,12 +158,18 @@
Uncheck all Checked items
</div>
</div>
<div class="sixteen wide column">
<div class="eight wide column">
<div class="ui labeled icon fluid basic button" v-on:click="undoCustom">
<i class="undo icon"></i>
Undo last change
</div>
</div>
<div class="eight wide column">
<div class="ui labeled icon fluid basic button" v-on:click="calculateMath" data-tooltip="Calculates algebra before '='">
<i class="calculator icon"></i>
Simple Math
</div>
</div>
<div class="sixteen wide column" v-if="rawTextId > 0">
<share-note-component
:note-id="noteid"
@ -561,6 +567,59 @@
}
})
},
calculateMath(){
//
// Find math in note and calculate the outcome
//
//Close menu if user is on mobile, then sort list
if(this.$store.getters.getIsUserOnMobile){
this.showNoteOptions = false
}
//Fetch the container
let container = document.getElementById('squire-id')
// simple function that trys to evaluate javascript
const shittyMath = (string) => {
//Remove all chars but math chars
const cleanString = String(string).replace(/[a-zA-Z\s]*/g,'')
try {
return Function('"use strict"; return (' + cleanString + ')')();
} catch (error) {
console.log('Math Error: ', string)
return null
}
}
//Go through each item, on first level, look for Unordered Lists
container.childNodes.forEach( (node) => {
const line = node.innerText.trim()
// = sign exists and its the last character in the string
if(line.indexOf('=') != -1 && (line.length-1) == line.indexOf('=')){
//Pull out everything before the formula and try to evaluate it
const formula = line.split('=').shift()
const output = shittyMath(formula)
//If its a number and didn't throw an error, update the line
if(!isNaN(output) && output != null){
//Since there is HTML in the line, splice in the number after the = sign
let equalLocation = node.innerHTML.indexOf('=')
let newLine = node.innerHTML.slice(0, equalLocation+1).trim()
newLine += ` ${output}`
newLine += node.innerHTML.slice(equalLocation+1).trim()
//Slam in that new HTML with the output
node.innerHTML = newLine
}
}
})
},
setText(inText){
this.editor.setHTML(inText)

View File

@ -66,9 +66,9 @@
<delete-button :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }" :note-id="note.id" />
</div>
<div class="row" v-if="note.thumbs">
<div class="row" v-if="getThumbs.length > 0">
<div class="tiny-thumb-box" v-on:click="openEditAttachment">
<img v-for="thumb in note.thumbs.split(',').reverse()" class="tiny-thumb" :src="`/api/static/thumb_${thumb}`">
<img v-for="thumb in getThumbs" class="tiny-thumb" :src="`/api/static/thumb_${thumb}`">
</div>
</div>
@ -118,6 +118,30 @@
iconColor: null,
}
},
computed: {
getThumbs(){
if(!this.note.thumbs){
return []
}
let notDisplaying = []
//Remove images displaying in text from the thumbnails
this.note.thumbs.forEach( path => {
const titleLocation = String(this.note.title).indexOf(path)
const subtextLocation = String(this.note.subtext).indexOf(path)
if(titleLocation != -1 || subtextLocation != -1){
return
}
notDisplaying.push(path)
})
return notDisplaying
}
},
beforeMount(){
this.note = this.data

View File

@ -87,7 +87,8 @@
},
mounted(){
axios.post('/api/attachment/search', {'attachmentType':2})
axios.post('/api/attachment/search', {'attachmentType':'files', 'setSize':1000})
.then( ({data}) => {
//Sort files into two categories

View File

@ -1,8 +1,8 @@
<template>
<div class="ui basic segment no-fluf-segment">
<div class="ui basic segment no-fluf-segment" ref="content">
<div class="ui grid">
<div class="ui sixteen wide column">
<div class="ui twelve wide column">
<h2 class="ui header">
<i class="folder open outline icon"></i>
<div class="content">
@ -10,13 +10,42 @@
<div class="sub header">Uploaded Files and Websites from notes.</div>
</div>
</h2>
<!-- subnav -->
<router-link
exact-active-class="green"
class="ui basic button"
to="/attachments">
<i class="open folder outline icon"></i>
All
</router-link>
<router-link
v-if="$store.getters.totals && $store.getters.totals['linkFiles']"
exact-active-class="green"
class="ui basic button"
to="/attachments/type/links">
<i class="linkify icon"></i>
Links
</router-link>
<router-link
v-if="$store.getters.totals && $store.getters.totals['otherFiles']"
exact-active-class="green"
class="ui basic button"
to="/attachments/type/files">
<i class="copy icon"></i>
Other Files
</router-link>
</div>
<div class="four wide bottom aligned column">
<i v-if="loading" class="green sync alternate loading icon"></i>
</div>
<div class="sixteen wide column" v-if="searchParams.noteId">
<div class="ui green button" v-on:click="clearNote">
<router-link class="ui green button" to="/attachments">
<i class="chevron circle left icon"></i>
Show All Attachments
</div>
Back to All
</router-link>
<div class="ui green button" v-on:click="openNote">
<i class="file outline icon"></i>
Open Note
@ -47,11 +76,16 @@
export default {
components: {
'attachment-display': require('@/components/AttachmentDisplayCard').default,
'counter':require('@/components/AnimatedCounterComponent.vue').default,
},
data: function(){
return {
loading: false,
attachments: [],
searchParams: {}
searchParams: {},
loadedAttachmentsOffset: 0,
loadingBatchTimeout: null,
allLoaded: false,
}
},
beforeCreate: function(){
@ -59,40 +93,107 @@
// Perform Login check
//
this.$parent.loginGateway()
},
mounted: function(){
//Load more attachments on scroll
window.addEventListener('scroll', this.onScroll)
//Mount notes on load if note ID is set
this.openNoteAttachments()
this.searchAttachments()
},
beforeDestroy(){
//Remove scroll event on destroy
window.removeEventListener('scroll', this.onScroll)
},
watch:{
$route (to, from){
//Open or close notes on route change
this.openNoteAttachments()
//Reset everything on route change
this.reset()
//Params are handled by search function
this.searchAttachments()
}
},
methods: {
openNoteAttachments(){
if(this.$route.params && this.$route.params.id){
const inputNoteId = this.$route.params.id
this.searchParams['noteId'] = inputNoteId
}
},
openNote(){
const noteId = this.searchParams['noteId']
this.$router.push('/notes/open/'+noteId)
},
clearNote(){
this.$router.push('/attachments/')
delete this.searchParams.noteId
onScroll(){
clearTimeout(this.loadingBatchTimeout)
this.loadingBatchTimeout = setTimeout(() => {
//Distance to bottom of page
const bottomOfWindow =
Math.max(window.pageYOffset, document.documentElement.scrollTop, document.body.scrollTop)
+ window.innerHeight
//height of page
const offsetHeight = this.$refs.content.clientHeight
//Determine percentage down the page
const percentageDown = Math.round( (bottomOfWindow/offsetHeight)*100 )
//If greater than 80 of the way down the page, load the next batch
if(percentageDown >= 80){
this.searchAttachments()
}
}, 50)
},
reset(){
this.attachments = []
this.loading = false
this.allLoaded = false
this.loadedAttachmentsOffset = 0
},
searchAttachments (){
if(this.loading || this.allLoaded){
return
}
delete this.searchParams.attachmentType
delete this.searchParams.noteId
//Set attchment type if in URL
if(this.$route.params.type){
this.searchParams.attachmentType = this.$route.params.type
}
//Set noteId in if in URL
if(this.$route.params.id){
this.searchParams.noteId = this.$route.params.id
}
//Offset of attchments to load
this.searchParams.offset = this.loadedAttachmentsOffset
if(this.allLoaded){
return
}
this.loading = true
axios.post('/api/attachment/search', this.searchParams)
.then( results => {
this.attachments = results.data
this.loading = false
if(results.data.length == 0){
this.allLoaded = true
return
}
//Load up the results
this.attachments.push(...results.data)
//Grab the next batch
this.loadedAttachmentsOffset += results.data.length
})
},
}

View File

@ -73,8 +73,14 @@ export default new Router({
},
{
path: '/attachments/note/:id',
name: 'Attachments',
meta: {title:'Attachments'},
name: 'Attachments for Note',
meta: {title:'Attachments for Note'},
component: AttachmentsPage
},
{
path: '/attachments/type/:type',
name: 'Attachments by Type',
meta: {title:'Attachments by Type'},
component: AttachmentsPage
},
]

View File

@ -40,13 +40,19 @@ ProcessText.deduceNoteTitle = (inString) => {
//Remove inline styles that may be added by editor
inString = inString.replace(/style=".*?"/g,'')
// inString = inString.replace('</a>','')
//Emergency ending tag if truncated. This will help regex find all the lines
inString += '</end>'
//Match full line and closing tag or just closing tag
let lines = inString.match(/[<[a-zA-Z0-9]+>(.*?)<\/[a-zA-Z0-9]+>|<\/[a-zA-Z0-9>]+?>/g)
let lines = inString.match(/[<[a-zA-Z0-9]+>(.*?)<\/[a-zA-Z0-9]+>|<\/[a-zA-Z0-9>]+?>/gms)
if(lines == null){ lines = [inString] }
//.match(/[^\r\n]+/g) //Match return or newline
// console.log('----------------')
// console.log(lines)
// console.log('----------------')
let finalLines = []
@ -129,6 +135,7 @@ ProcessText.deduceNoteTitle = (inString) => {
//Cut the string down to character limit
const cutString = lines[i].substring(0, lines[i].length+charLimit)
//Find last space and cut off everything after it
let cleanCutString = cutString.substring(0, cutString.lastIndexOf(' '))

View File

@ -43,7 +43,7 @@ Attachment.textSearch = (userId, searchTerm) => {
})
}
Attachment.search = (userId, noteId, attachmentType) => {
Attachment.search = (userId, noteId, attachmentType, offset, setSize) => {
return new Promise((resolve, reject) => {
let params = [userId]
@ -54,13 +54,20 @@ Attachment.search = (userId, noteId, attachmentType) => {
params.push(noteId)
}
if(Number.isInteger(attachmentType)){
query += 'AND attachment_type = ? '
params.push(attachmentType)
if(attachmentType == 'links'){
query += 'AND attachment_type = 1 '
}
if(attachmentType == 'files'){
query += 'AND attachment_type > 1 '
}
query += 'ORDER BY last_indexed DESC '
const limitOffset = parseInt(offset, 10) || 0 //Either parse int, or use zero
const parsedSetSize = parseInt(setSize, 10) || 20 //Either parse int, or use zero
query += ` LIMIT ${limitOffset}, ${parsedSetSize}`
db.promise()
.query(query, params)
.then((rows, fields) => {

View File

@ -593,6 +593,15 @@ Note.search = (userId, searchQuery, searchTags, fastFilters) => {
note.attachment_highlights = []
note.tag_highlights = []
//Limit number of attachment thumbs to 4
if(note.thumbs){
//Convert comma delimited string to array
let thumbArray = note.thumbs.split(',').reverse()
//Limit array to 4 or size of array
thumbArray.length = Math.min(thumbArray.length, 4)
note.thumbs = thumbArray
}
//Push in search highlights
if(highlights && highlights[note.id]){
note['note_highlights'] = [highlights[note.id]]

View File

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