Compare commits

...

12 commits
1.2 ... main

40 changed files with 29494 additions and 372 deletions

View file

@ -1,35 +1,21 @@
CC = gcc
DESTDIR = /usr/bin
SHAREDIR = /usr/share
CFLAGS = -Wall -Wextra -g
PKG_CONFIG = pkg-config
GTK_LIBS = $(shell $(PKG_CONFIG) --libs gtk+-3.0)
WEBKIT_LIBS = $(shell $(PKG_CONFIG) --libs webkit2gtk-4.1)
LIBS = $(GTK_LIBS) $(WEBKIT_LIBS)
INCLUDES = $(shell $(PKG_CONFIG) --cflags gtk+-3.0 webkit2gtk-4.1)
SRCS = burgernotes.c
OBJS = $(SRCS:.c=.o)
TARGET = burgernotes
GO = /usr/bin/go
.PHONY: all clean
all: burgernotes
burgernotes: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $(TARGET) $(LIBS)
%.o: %.c
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
burgernotes:
$(GO) build
clean:
rm -f $(OBJS) $(TARGET)
rm -f burgernotes-app
install: burgernotes
mkdir -p $(DESTDIR)
mkdir -p $(SHAREDIR)/burgernotes
cp burgernotes $(SHAREDIR)/burgernotes/
cp burgernotes-app $(SHAREDIR)/burgernotes/burgernotes
ln -sf $(SHAREDIR)/burgernotes/burgernotes $(DESTDIR)/burgernotes
mkdir -p $(SHAREDIR)/icons/hicolor/scalable/apps/
mkdir -p $(SHAREDIR)/applications/
@ -38,3 +24,4 @@ install: burgernotes
cp org.hectabit.Burgernotes.desktop $(SHAREDIR)/applications/
cp org.hectabit.Burgernotes.metainfo.xml $(SHAREDIR)/metainfo/
cp -r website $(SHAREDIR)/burgernotes/website
cp createwebsite.sh $(SHAREDIR)/burgernotes/

View file

@ -1,56 +0,0 @@
#include <webkit2/webkit2.h>
#include <gtk/gtk.h>
#include <unistd.h>
#include <libgen.h>
static void cookie_changed_cb(WebKitCookieManager *cookie_manager, GParamSpec *pspec, gpointer user_data) {
// Handle cookie changes here
g_print("Cookie changed\n");
}
int main(int argc, char *argv[]) {
gtk_init(&argc, &argv);
// Create a new window
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "Burgernotes");
gtk_window_set_default_size(GTK_WINDOW(window), 1000, 600);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
// Create a web view widget
WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new());
// Get the path of the executable
char exe_path[1024];
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path)-1);
if (len == -1) {
perror("readlink");
return EXIT_FAILURE;
}
exe_path[len] = '\0';
// Get the directory containing the executable
char *exe_dir = dirname(exe_path);
// Construct the path to the website directory
char website_path[1024];
snprintf(website_path, sizeof(website_path), "file:/\/%s/website/app/index.html", exe_dir);
// Load the URI
webkit_web_view_load_uri(webview, website_path);
// Add the web view to the window
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(webview));
// Show all widgets
gtk_widget_show_all(window);
// Connect signals for handling cookies
WebKitCookieManager *cookie_manager = webkit_web_context_get_cookie_manager(webkit_web_view_get_context(webview));
g_signal_connect(cookie_manager, "notify::cookie-accept-policy", G_CALLBACK(cookie_changed_cb), NULL);
// Run the main GTK event loop
gtk_main();
return 0;
}

View file

@ -1,6 +1,8 @@
#!/bin/sh
rm -rf website/*
mkdir -p website
rm -rf burgernotes-client-web
git clone https://centrifuge.hectabit.org/hectabit/burgernotes-client-web.git --depth=1
mv burgernotes-client-web/* website/
rm -rf burgernotes-client-web website/index.html website/README.md website/LICENSE
cp rdir.html website/index.html
rm -rf burgernotes-client-web website/README.md website/LICENSE.md

10
go.mod Normal file
View file

@ -0,0 +1,10 @@
module hectabit.org/burgernotes-app
go 1.22.2
require (
github.com/arzumify/webview_go-4.1 v0.0.0-20240425153857-cdb51de8ba32
github.com/gotk3/gotk3 v0.6.3
)
replace github.com/arzumify/webview_go-4.1 v0.0.0-20240425153857-cdb51de8ba32 => ./webkit-4.1

2
go.sum Normal file
View file

@ -0,0 +1,2 @@
github.com/gotk3/gotk3 v0.6.3 h1:+Ke4WkM1TQUNOlM2TZH6szqknqo+zNbX3BZWVXjSHYw=
github.com/gotk3/gotk3 v0.6.3/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=

View file

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Burgernotes</title>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<head>
Logging out..
<script>
localStorage.removeItem("DONOTSHARE-secretkey")
localStorage.removeItem("DONOTSHARE-password")
localStorage.removeItem("CACHE-username")
window.location.replace("../login/index.html")
</script>

160
main.go Normal file
View file

@ -0,0 +1,160 @@
package main
import (
"fmt"
"path/filepath"
"archive/zip"
"github.com/arzumify/webview_go-4.1"
"net/http"
"os"
"io"
"strconv"
)
func upgrade(path string) {
err := os.RemoveAll(filepath.Join(path, "website"))
if err != nil {
fmt.Println("[ERROR] Failed to delete current version:", err)
return
}
err = os.MkdirAll(filepath.Join(path, "website"), os.ModePerm)
if err != nil {
fmt.Println("[ERROR] Failed to create website directory:", err)
return
}
resp, err := http.Get("https://centrifuge.hectabit.org/HectaBit/Burgernotes-client-web/archive/main.zip")
if err != nil {
fmt.Println("[ERROR] Cannot fetch latest version:", err)
return
}
defer resp.Body.Close()
tempFile, err := os.CreateTemp("", "upgrade_*.zip")
if err != nil {
fmt.Println("[ERROR] Failed to create temporary file:", err)
return
}
defer os.Remove(tempFile.Name())
_, err = io.Copy(tempFile, resp.Body)
if err != nil {
fmt.Println("[ERROR] Failed to copy zip content to temporary file:", err)
return
}
zipReader, err := zip.OpenReader(tempFile.Name())
if err != nil {
fmt.Println("[ERROR] Failed to open zip file:", err)
return
}
defer zipReader.Close()
for _, file := range zipReader.File {
dstPath := filepath.Join(filepath.Join(path, "website"), file.Name)
if file.FileInfo().IsDir() {
os.MkdirAll(dstPath, os.ModePerm)
continue
}
fileReader, err := file.Open()
if err != nil {
fmt.Println("[ERROR] Failed to open file in zip:", err)
return
}
defer fileReader.Close()
dstFile, err := os.Create(dstPath)
if err != nil {
fmt.Println("[ERROR] Failed to create destination file:", err)
return
}
defer dstFile.Close()
_, err = io.Copy(dstFile, fileReader)
if err != nil {
fmt.Println("[ERROR] Failed to copy file contents:", err)
return
}
}
src, err := os.Open(filepath.Join(filepath.Join(path, "website"), "burgernotes-client-web"))
if err != nil {
fmt.Println("[ERROR] Cannot find created folder:", err)
return
}
files, err := src.Readdir(-1)
if err != nil {
fmt.Println("[ERROR] Failed to read files:", err)
return
}
for _, file := range files {
srcPath := filepath.Join(filepath.Join(filepath.Join(path, "website"), "burgernotes-client-web"), file.Name())
dstPath := filepath.Join(filepath.Join(path, "website"), file.Name())
err := os.Rename(srcPath, dstPath)
if err != nil {
fmt.Println("[ERROR] Failed to move files:", err)
return
}
}
err = os.Remove(filepath.Join(filepath.Join(path, "website"), "burgernotes-client-web"))
if err != nil {
fmt.Println("[ERROR] Failed to delete source directory:", err)
return
}
file, err := os.OpenFile(filepath.Join(filepath.Join(path, "website"), "index.html"), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
if err != nil {
fmt.Println("[ERROR] Failed to open index.html:", err)
return
}
defer file.Close()
filecontent := "<!DOCTYPE html><html><head><title>Burgernotes</title><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" /><meta http-equiv=\"refresh\" content=\"0; url=/app\"><head>Redirecting...<script>window.location.replace(\"/app\")</script>"
_, err = file.WriteString(filecontent)
if err != nil {
fmt.Println("[ERROR] Failed to replace index.html:", err)
return
}
}
func vcheck(path string) {
localVersion, err := os.ReadFile(path + "/website/static/version.txt")
if err != nil {
fmt.Println("[ERROR] Cannot get local version:", err)
os.Exit(1)
}
localVersionNum, err := strconv.Atoi(string(localVersion))
if err != nil {
fmt.Println("[ERROR] Failed to convert local version to integer:", err)
return
}
resp, err := http.Get("https://notes.hectabit.org/static/version.txt")
if err != nil {
fmt.Println("[ERROR] Cannot fetch remote version:", err)
return
}
remoteVersion, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("[ERROR] Failed to read remote version:", err)
return
}
defer resp.Body.Close()
remoteVersionNum, err := strconv.Atoi(string(remoteVersion))
if err != nil {
fmt.Println("[ERROR] Failed to convert remote version to integer:", err)
return
}
if localVersionNum < remoteVersionNum {
fmt.Println("[INFO] Local version is old. Attempting upgrade...")
upgrade(path)
} else {
fmt.Println("[INFO] Up to date")
}
}
func main() {
exepath, _ := os.Executable()
path, _ := filepath.EvalSymlinks(exepath)
go func() {
http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(filepath.Dir(path) + "/website"))))
http.ListenAndServe("localhost:52064", nil)
}()
vcheck(filepath.Dir(path))
w := webview.New(false)
defer w.Destroy()
w.SetTitle("Burgernotes")
w.SetSize(800, 800, webview.HintNone)
w.Navigate("http://localhost:52064")
w.Run()
}

View file

@ -50,6 +50,16 @@
</branding>
<releases>
<release version="1.3" date="2024-04-22">
<url type="details">https://centrifuge.hectabit.org/HectaBit/Burgernotes-App/releases/tag/1.3</url>
<description>
<p>Release 1.3</p>
<ul>
<li>Update to latest version of client</li>
<li>Updated to a newer golang client</li>
</ul>
</description>
</release>
<release version="1.2" date="2024-03-15">
<url type="details">https://centrifuge.hectabit.org/HectaBit/Burgernotes-App/releases/tag/1.2</url>
<description>

15
webkit-4.1/.gitignore vendored Normal file
View file

@ -0,0 +1,15 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/

15
webkit-4.1/CHANGELOG.md Normal file
View file

@ -0,0 +1,15 @@
# Changelog
### Migrating from v0.1.1 to v0.10.0
1. `Webview.Open()` has been removed. Use other webview APIs to create a window, open a link and run main UI loop.
2. `Webview.Debug()` and `webview.Debugf()` have been removed. Use your favorite logging library to debug webview apps.
3. `Webview.Settings` struct has been removed. Title, URL and size are controlled via other API setters and can be updated at any time, not only when webview is created.
4. `Webview.Loop()` has been removed. Use `Run()` instead.
5. `WebView.Run()`, `WebView.Terminate()`, `WebView.SetTitle()`, `WebView.Dispatch()` stayed the same.
6. `WebView.Exit()` has been renamed to `WebView.Destroy()`
7. `WebView.SetColor()` and `WebView.SetFullScreen()` have been removed. Use `Window()` to get native window handle and probably write some Cgo code to adjust native window to your taste.
8. `Webview.Dialog` has been removed. But it is likely to be brought back as a standalone module.
9. `WebView.Eval()` remained the same.
10. `WebView.InjectCSS()` has been removed. Use eval to inject style tag with CSS inside.
11. `WebView.Bind()` kept the name, but changed the semantics. Only functions can be bound. Not the structs, like in Lorca.

22
webkit-4.1/LICENSE Normal file
View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2017 Serge Zaitsev
Copyright (c) 2020 webview
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

51
webkit-4.1/README.md Normal file
View file

@ -0,0 +1,51 @@
# webview_go-4.1
[![GoDoc](https://godoc.org/github.com/arzumify/webview_go-4.1?status.svg)](https://godoc.org/github.com/arzumify/webview_go-4.1)
[![Go Report Card](https://goreportcard.com/badge/github.com/arzumify/webview_go-4.1)](https://goreportcard.com/report/github.com/webview/webview_go)
Go language binding for the [webview library][webview], for webkitgtk-4.1.
This is entirely backwards compatible with webview_go 4.0, and needs only a dependency replacement.
> [!NOTE]
> Versions <= 0.1.1 are available in the [old repository][webview].
### Getting Started
See [Go package documentation][go-docs] for the Go API documentation, or simply read the source code.
Start with creating a new directory structure for your project.
```sh
mkdir my-project && cd my-project
```
Create a new Go module.
```sh
go mod init example.com/app
```
Save one of the example programs into your project directory.
```sh
curl -sSLo main.go "https://raw.githubusercontent.com/arzumify/webview_go-4.1/master/examples/basic/main.go"
```
Install dependencies.
```sh
go get github.com/webview/webview_go
```
Build the example. On Windows, add `-ldflags="-H windowsgui"` to the command line.
```sh
go build
```
### Notes
Calling `Eval()` or `Dispatch()` before `Run()` does not work because the webview instance has only been configured and not yet started.
[go-docs]: https://pkg.go.dev/github.com/arzumify/webview_go-4.1
[webview]: https://github.com/webview/webview

View file

@ -0,0 +1,12 @@
package main
import webview "github.com/webview/webview_go"
func main() {
w := webview.New(false)
defer w.Destroy()
w.SetTitle("Basic Example")
w.SetSize(480, 320, webview.HintNone)
w.SetHtml("Thanks for using webview!")
w.Run()
}

View file

@ -0,0 +1,38 @@
package main
import webview "github.com/webview/webview_go"
const html = `<button id="increment">Tap me</button>
<div>You tapped <span id="count">0</span> time(s).</div>
<script>
const [incrementElement, countElement] =
document.querySelectorAll("#increment, #count");
document.addEventListener("DOMContentLoaded", () => {
incrementElement.addEventListener("click", () => {
window.increment().then(result => {
countElement.textContent = result.count;
});
});
});
</script>`
type IncrementResult struct {
Count uint `json:"count"`
}
func main() {
var count uint = 0
w := webview.New(false)
defer w.Destroy()
w.SetTitle("Bind Example")
w.SetSize(480, 320, webview.HintNone)
// A binding that increments a value and immediately returns the new value.
w.Bind("increment", func() IncrementResult {
count++
return IncrementResult{Count: count}
})
w.SetHtml(html)
w.Run()
}

36
webkit-4.1/glue.c Normal file
View file

@ -0,0 +1,36 @@
#include "webview.h"
#include <stdlib.h>
#include <stdint.h>
struct binding_context {
webview_t w;
uintptr_t index;
};
void _webviewDispatchGoCallback(void *);
void _webviewBindingGoCallback(webview_t, char *, char *, uintptr_t);
static void _webview_dispatch_cb(webview_t w, void *arg) {
_webviewDispatchGoCallback(arg);
}
static void _webview_binding_cb(const char *id, const char *req, void *arg) {
struct binding_context *ctx = (struct binding_context *) arg;
_webviewBindingGoCallback(ctx->w, (char *)id, (char *)req, ctx->index);
}
void CgoWebViewDispatch(webview_t w, uintptr_t arg) {
webview_dispatch(w, _webview_dispatch_cb, (void *)arg);
}
void CgoWebViewBind(webview_t w, const char *name, uintptr_t index) {
struct binding_context *ctx = calloc(1, sizeof(struct binding_context));
ctx->w = w;
ctx->index = index;
webview_bind(w, name, _webview_binding_cb, (void *)ctx);
}
void CgoWebViewUnbind(webview_t w, const char *name) {
webview_unbind(w, name);
}

3
webkit-4.1/go.mod Normal file
View file

@ -0,0 +1,3 @@
module github.com/arzumify/webview_go-4.1
go 1.13

0
webkit-4.1/go.sum Normal file
View file

View file

@ -0,0 +1,27 @@
Copyright (C) Microsoft Corporation. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* The name of Microsoft Corporation, or the names of its contributors
may not be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
1.0.1150.38

View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2017 Serge Zaitsev
Copyright (c) 2022 Steffen André Langnes
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
fb6b17d826041411e6346cd9a785a5ceba7987c4

1
webkit-4.1/webview.cc Normal file
View file

@ -0,0 +1 @@
#include "webview.h"

327
webkit-4.1/webview.go Normal file
View file

@ -0,0 +1,327 @@
package webview
/*
#cgo CFLAGS: -I${SRCDIR}/libs/webview/include
#cgo CXXFLAGS: -I${SRCDIR}/libs/webview/include -DWEBVIEW_STATIC
#cgo linux openbsd freebsd netbsd CXXFLAGS: -DWEBVIEW_GTK -std=c++11
#cgo linux openbsd freebsd netbsd LDFLAGS: -ldl
#cgo linux openbsd freebsd netbsd pkg-config: gtk+-3.0 webkit2gtk-4.1
#cgo darwin CXXFLAGS: -DWEBVIEW_COCOA -std=c++11
#cgo darwin LDFLAGS: -framework WebKit -ldl
#cgo windows CXXFLAGS: -DWEBVIEW_EDGE -std=c++14 -I${SRCDIR}/libs/mswebview2/include
#cgo windows LDFLAGS: -static -ladvapi32 -lole32 -lshell32 -lshlwapi -luser32 -lversion
#include "webview.h"
#include <stdlib.h>
#include <stdint.h>
void CgoWebViewDispatch(webview_t w, uintptr_t arg);
void CgoWebViewBind(webview_t w, const char *name, uintptr_t index);
void CgoWebViewUnbind(webview_t w, const char *name);
*/
import "C"
import (
"encoding/json"
"errors"
"reflect"
"runtime"
"sync"
"unsafe"
)
func init() {
// Ensure that main.main is called from the main thread
runtime.LockOSThread()
}
// Hints are used to configure window sizing and resizing
type Hint int
const (
// Width and height are default size
HintNone = C.WEBVIEW_HINT_NONE
// Window size can not be changed by a user
HintFixed = C.WEBVIEW_HINT_FIXED
// Width and height are minimum bounds
HintMin = C.WEBVIEW_HINT_MIN
// Width and height are maximum bounds
HintMax = C.WEBVIEW_HINT_MAX
)
type WebView interface {
// Run runs the main loop until it's terminated. After this function exits -
// you must destroy the webview.
Run()
// Terminate stops the main loop. It is safe to call this function from
// a background thread.
Terminate()
// Dispatch posts a function to be executed on the main thread. You normally
// do not need to call this function, unless you want to tweak the native
// window.
Dispatch(f func())
// Destroy destroys a webview and closes the native window.
Destroy()
// Window returns a native window handle pointer. When using GTK backend the
// pointer is GtkWindow pointer, when using Cocoa backend the pointer is
// NSWindow pointer, when using Win32 backend the pointer is HWND pointer.
Window() unsafe.Pointer
// SetTitle updates the title of the native window. Must be called from the UI
// thread.
SetTitle(title string)
// SetSize updates native window size. See Hint constants.
SetSize(w int, h int, hint Hint)
// Navigate navigates webview to the given URL. URL may be a properly encoded data.
// URI. Examples:
// w.Navigate("https://github.com/webview/webview")
// w.Navigate("data:text/html,%3Ch1%3EHello%3C%2Fh1%3E")
// w.Navigate("data:text/html;base64,PGgxPkhlbGxvPC9oMT4=")
Navigate(url string)
// SetHtml sets the webview HTML directly.
// Example: w.SetHtml(w, "<h1>Hello</h1>");
SetHtml(html string)
// Init injects JavaScript code at the initialization of the new page. Every
// time the webview will open a the new page - this initialization code will
// be executed. It is guaranteed that code is executed before window.onload.
Init(js string)
// Eval evaluates arbitrary JavaScript code. Evaluation happens asynchronously,
// also the result of the expression is ignored. Use RPC bindings if you want
// to receive notifications about the results of the evaluation.
Eval(js string)
// Bind binds a callback function so that it will appear under the given name
// as a global JavaScript function. Internally it uses webview_init().
// Callback receives a request string and a user-provided argument pointer.
// Request string is a JSON array of all the arguments passed to the
// JavaScript function.
//
// f must be a function
// f must return either value and error or just error
Bind(name string, f interface{}) error
// Removes a callback that was previously set by Bind.
Unbind(name string) error
}
type webview struct {
w C.webview_t
}
var (
m sync.Mutex
index uintptr
dispatch = map[uintptr]func(){}
bindings = map[uintptr]func(id, req string) (interface{}, error){}
)
func boolToInt(b bool) C.int {
if b {
return 1
}
return 0
}
// New calls NewWindow to create a new window and a new webview instance. If debug
// is non-zero - developer tools will be enabled (if the platform supports them).
func New(debug bool) WebView { return NewWindow(debug, nil) }
// NewWindow creates a new webview instance. If debug is non-zero - developer
// tools will be enabled (if the platform supports them). Window parameter can be
// a pointer to the native window handle. If it's non-null - then child WebView is
// embedded into the given parent window. Otherwise a new window is created.
// Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be passed
// here.
func NewWindow(debug bool, window unsafe.Pointer) WebView {
w := &webview{}
w.w = C.webview_create(boolToInt(debug), window)
return w
}
func (w *webview) Destroy() {
C.webview_destroy(w.w)
}
func (w *webview) Run() {
C.webview_run(w.w)
}
func (w *webview) Terminate() {
C.webview_terminate(w.w)
}
func (w *webview) Window() unsafe.Pointer {
return C.webview_get_window(w.w)
}
func (w *webview) Navigate(url string) {
s := C.CString(url)
defer C.free(unsafe.Pointer(s))
C.webview_navigate(w.w, s)
}
func (w *webview) SetHtml(html string) {
s := C.CString(html)
defer C.free(unsafe.Pointer(s))
C.webview_set_html(w.w, s)
}
func (w *webview) SetTitle(title string) {
s := C.CString(title)
defer C.free(unsafe.Pointer(s))
C.webview_set_title(w.w, s)
}
func (w *webview) SetSize(width int, height int, hint Hint) {
C.webview_set_size(w.w, C.int(width), C.int(height), C.webview_hint_t(hint))
}
func (w *webview) Init(js string) {
s := C.CString(js)
defer C.free(unsafe.Pointer(s))
C.webview_init(w.w, s)
}
func (w *webview) Eval(js string) {
s := C.CString(js)
defer C.free(unsafe.Pointer(s))
C.webview_eval(w.w, s)
}
func (w *webview) Dispatch(f func()) {
m.Lock()
for ; dispatch[index] != nil; index++ {
}
dispatch[index] = f
m.Unlock()
C.CgoWebViewDispatch(w.w, C.uintptr_t(index))
}
//export _webviewDispatchGoCallback
func _webviewDispatchGoCallback(index unsafe.Pointer) {
m.Lock()
f := dispatch[uintptr(index)]
delete(dispatch, uintptr(index))
m.Unlock()
f()
}
//export _webviewBindingGoCallback
func _webviewBindingGoCallback(w C.webview_t, id *C.char, req *C.char, index uintptr) {
m.Lock()
f := bindings[uintptr(index)]
m.Unlock()
jsString := func(v interface{}) string { b, _ := json.Marshal(v); return string(b) }
status, result := 0, ""
if res, err := f(C.GoString(id), C.GoString(req)); err != nil {
status = -1
result = jsString(err.Error())
} else if b, err := json.Marshal(res); err != nil {
status = -1
result = jsString(err.Error())
} else {
status = 0
result = string(b)
}
s := C.CString(result)
defer C.free(unsafe.Pointer(s))
C.webview_return(w, id, C.int(status), s)
}
func (w *webview) Bind(name string, f interface{}) error {
v := reflect.ValueOf(f)
// f must be a function
if v.Kind() != reflect.Func {
return errors.New("only functions can be bound")
}
// f must return either value and error or just error
if n := v.Type().NumOut(); n > 2 {
return errors.New("function may only return a value or a value+error")
}
binding := func(id, req string) (interface{}, error) {
raw := []json.RawMessage{}
if err := json.Unmarshal([]byte(req), &raw); err != nil {
return nil, err
}
isVariadic := v.Type().IsVariadic()
numIn := v.Type().NumIn()
if (isVariadic && len(raw) < numIn-1) || (!isVariadic && len(raw) != numIn) {
return nil, errors.New("function arguments mismatch")
}
args := []reflect.Value{}
for i := range raw {
var arg reflect.Value
if isVariadic && i >= numIn-1 {
arg = reflect.New(v.Type().In(numIn - 1).Elem())
} else {
arg = reflect.New(v.Type().In(i))
}
if err := json.Unmarshal(raw[i], arg.Interface()); err != nil {
return nil, err
}
args = append(args, arg.Elem())
}
errorType := reflect.TypeOf((*error)(nil)).Elem()
res := v.Call(args)
switch len(res) {
case 0:
// No results from the function, just return nil
return nil, nil
case 1:
// One result may be a value, or an error
if res[0].Type().Implements(errorType) {
if res[0].Interface() != nil {
return nil, res[0].Interface().(error)
}
return nil, nil
}
return res[0].Interface(), nil
case 2:
// Two results: first one is value, second is error
if !res[1].Type().Implements(errorType) {
return nil, errors.New("second return value must be an error")
}
if res[1].Interface() == nil {
return res[0].Interface(), nil
}
return res[0].Interface(), res[1].Interface().(error)
default:
return nil, errors.New("unexpected number of return values")
}
}
m.Lock()
for ; bindings[index] != nil; index++ {
}
bindings[index] = binding
m.Unlock()
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
C.CgoWebViewBind(w.w, cname, C.uintptr_t(index))
return nil
}
func (w *webview) Unbind(name string) error {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
C.CgoWebViewUnbind(w.w, cname)
return nil
}

View file

@ -0,0 +1,50 @@
package webview
import (
"flag"
"log"
"os"
"testing"
)
func Example() {
w := New(true)
defer w.Destroy()
w.SetTitle("Hello")
w.Bind("noop", func() string {
log.Println("hello")
return "hello"
})
w.Bind("add", func(a, b int) int {
return a + b
})
w.Bind("quit", func() {
w.Terminate()
})
w.SetHtml(`<!doctype html>
<html>
<body>hello</body>
<script>
window.onload = function() {
document.body.innerText = ` + "`hello, ${navigator.userAgent}`" + `;
noop().then(function(res) {
console.log('noop res', res);
add(1, 2).then(function(res) {
console.log('add res', res);
quit();
});
});
};
</script>
</html>
)`)
w.Run()
}
func TestMain(m *testing.M) {
flag.Parse()
if testing.Verbose() {
Example()
}
os.Exit(m.Run())
}

View file

@ -1,23 +1,15 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>Burgernotes</title>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" type="text/css" href="../static/css/style.css" />
<script type="text/javascript" src="../static/js/crypto-js.js"></script>
<link rel="icon" href="../static/svg/favicon.svg">
<script>
if (window.location.href.endsWith('/index.html')) {
if (window.location.origin !== null) {
var currentUrl = window.location.href;
var newUrl = currentUrl.replace('/index.html', '');
window.location.href = newUrl;
}
}
</script>
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
<script type="text/javascript" src="/static/js/crypto-js.js"></script>
<script type="text/javascript" src="/static/js/marked.js"></script>
<link rel="icon" href="/static/svg/favicon.svg">
</head>
<body>
@ -29,6 +21,7 @@
<div class="bottomBar">
<button id="removeBox" class="removeButton"></button>
<button id="wordCountBox">0 words</button>
<button onclick="toggleMarkdown()">Toggle Markdown</button>
<div class="textManipulator">
<button id="textMinusBox">-</button>
<button id="textSizeBox">16px</button>
@ -38,7 +31,7 @@
<div id="notesBar" class="notesBar">
<button id="newNote" class="newNote"><img id="newNoteImage" draggable="false" alt=""
src="../static/svg/add.svg">New note</button>
src="/static/svg/add.svg">New note</button>
<div id="notesDiv" class="notesDiv">
<button class="loadingStuff" id="loadingStuff"></button>
</div>
@ -56,10 +49,10 @@
<p id="storageThing"></p>
<div class="section"></div>
<p>Account managment</p>
<button id="deleteMyAccountButton"><img src="../static/svg/delete_forever.svg">Delete my account</button>
<button id="exportNotesButton"><img src="../static/svg/download.svg">Export notes</button>
<button id="sessionManagerButton"><img src="../static/svg/list.svg">Session manager</button>
<button class="lastButton" id="logOutButton"><img src="../static/svg/logout.svg">Log out</button>
<button id="deleteMyAccountButton"><img src="/static/svg/delete_forever.svg" alt="">Delete my account</button>
<button id="exportNotesButton"><img src="/static/svg/download.svg" alt="">Export notes</button>
<button id="sessionManagerButton"><img src="/static/svg/list.svg" alt="">Session manager</button>
<button class="lastButton" id="logOutButton"><img src="/static/svg/logout.svg" alt="">Log out</button>
</div>
<div id="sessionManagerDiv" class="optionsDiv hidden">
<button class="exit" id="exitSessionsThing">X</button>
@ -69,18 +62,20 @@
<div class="sessionDiv" id="sessionDiv">
</div>
</div>
</div>
<div id="errorDiv" class="optionsDiv hidden">
<p id="errorMessageThing"></p>
<input class="hidden" id="errorInput" type="text" placeholder=""><br></input>
<input class="hidden" id="errorInput" type="text" placeholder=""><br>
<button class="normalButton" id="closeErrorButton">Ok</button>
<button class="normalButton hidden" id="cancelErrorButton">Cancel</button>
</div>
</div>
<textarea id="noteBox" class="noteBox"></textarea>
<div class="noteBox">
<textarea id="noteBox" class="noteBoxText"></textarea>
<iframe id="markdown" style="display: none;" sandbox="allow-scripts"></iframe>
</div>
<script type="text/javascript" src="../static/js/main.js"></script>
<script type="text/javascript" src="/static/js/main.js"></script>
<script>
for (let i = 0; i < 40; i++) {
notesDiv.appendChild(loadingStuff.cloneNode())

View file

@ -1,33 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Burgernotes</title>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" type="text/css" href="../static/css/style.css" />
<link rel="icon" href="../static/svg/favicon.svg">
<script>
if (window.location.href.endsWith('/index.html')) {
if (window.location.origin !== null) {
var currentUrl = window.location.href;
var newUrl = currentUrl.replace('/index.html', '');
window.location.href = newUrl;
}
}
</script>
</head>
<body>
<h2 class="w300">{{ errorMessage }}</h2>
{{ errorCode }} | {{ errorMessage }}
</body>
<style>
body {
margin-left: 15px;
}
</style>
</html>

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>Signup - Burgernotes</title>
@ -7,22 +7,13 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" type="text/css" href="../static/css/style.css">
<link rel="icon" href="./static/svg/favicon.svg">
<script src="../static/js/hash-wasm.js"></script>
<style>
body {
background-color: #d9d9d9;
background-image: url("/static/svg/grid.svg");
background-repeat: repeat;
background-size: 312px
}
.inoutdiv {
border-radius: 8px;
}
</style>
<link rel="icon" href="/static/svg/favicon.svg">
<script src="/static/js/hash-wasm.js"></script>
</head>
<body>
<p class="credit">Image by perga (@pergagreen on discord)</p>
<img src="/static/img/background.jpg" class="background" alt="">
<div class="inoutdiv">
<h2 class="w300">Homeserver</h2>
<p>Change your Burgernotes homeserver</p>
@ -33,7 +24,7 @@
<p>Please put in the URL in standard format; https://, http://, etc.</p>
</div>
<script type="text/javascript" src="../static/js/homeserver.js"></script>
<script type="text/javascript" src="/static/js/homeserver.js"></script>
</body>
</html>

1
website/index.html Normal file
View file

@ -0,0 +1 @@
<!DOCTYPE html><html><head><title>Burgernotes</title><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta http-equiv="refresh" content="0; url=/app"><head>Redirecting...<script>window.location.replace("/app")</script>

View file

@ -1,28 +1,19 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>Login - Burgernotes</title>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" type="text/css" href="../static/css/style.css" />
<script src="../static/js/hash-wasm.js"></script>
<link rel="icon" href="../static/svg/favicon.svg">
<script>
if (window.location.href.endsWith('/index.html')) {
if (window.location.origin !== null) {
var currentUrl = window.location.href;
var newUrl = currentUrl.replace('/index.html', '');
window.location.href = newUrl;
}
}
</script>
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
<script src="/static/js/hash-wasm.js"></script>
<link rel="icon" href="/static/svg/favicon.svg">
</head>
<body>
<p class="credit">Image by perga (@pergagreen on discord)</p>
<img src="/static/img/background.jpg" class="background">
<img src="/static/img/background.jpg" class="background" alt="">
<div class="inoutdiv">
<h2 class="w300">Login</h2>
<p id="statusBox"></p>
@ -33,9 +24,9 @@
<button id="backButton" class="hidden">Back</button>
<br>
<br>
<p>Don't have an account? If so, <a href="../signup/index.html">Create one here!</a></p>
<p>Don't have an account? If so, <a href="/signup/">Create one here!</a></p>
<div style="display: flex;"><p id="homeserver">Your homeserver is loading... </p><div style="display: flex;flex-direction: column;justify-content: center;"><a href="/homeserver">Change</a></div></div>
<a href="../privacy/index.html">Privacy &amp; Terms</a>
<a href="/privacy/">Privacy &amp; Terms</a>
</div>
<script type="text/javascript" src="../static/js/login.js"></script>

View file

@ -1,25 +1,16 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>Burgernotes</title>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="icon" href="../static/svg/favicon.svg">
<script>
if (window.location.href.endsWith('/index.html')) {
if (window.location.origin !== null) {
var currentUrl = window.location.href;
var newUrl = currentUrl.replace('/index.html', '');
window.location.href = newUrl;
}
}
</script>
<head>
Logging out..
<link rel="icon" href="/static/svg/favicon.svg">
</head>
<p>Logging out...</p>
<script>
localStorage.removeItem("DONOTSHARE-secretkey")
localStorage.removeItem("DONOTSHARE-password")
localStorage.removeItem("CACHE-username")
window.location.replace("../login/index.html")
window.location.replace("/login")
</script>

View file

@ -1,22 +1,13 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>Burgernotes Privacy &amp; Terms</title>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" type="text/css" href="../static/css/style.css" />
<link rel="icon" href="../static/svg/favicon.svg">
<script>
if (window.location.href.endsWith('/index.html')) {
if (window.location.origin !== null) {
var currentUrl = window.location.href;
var newUrl = currentUrl.replace('/index.html', '');
window.location.href = newUrl;
}
}
</script>
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
<link rel="icon" href="/static/svg/favicon.svg">
</head>
<body>
@ -39,21 +30,21 @@
<li>Web browser "User agent"</li>
</ul>
<h2 class="w300">Information we collect while using our services</h2>
<p>When you create an note, we collect and use this information:</p>
<p>When you create a note, we collect and use this information:</p>
<ul>
<li>Encrypted note content and title</li>
<li>Note creator</li>
<li>Note creation date</li>
<li>Note last edited date</li>
</ul>
<p>When you edit an note, we collect and use this information:</p>
<p>When you edit a note, we collect and use this information:</p>
<ul>
<li>Encrypted note content and title</li>
<li>Note last edited date</li>
</ul>
<h2 class="w300">How we use your data</h2>
<p>We use your data to make our services work. We don't share your information with third-parties.</p>
<h2 class="w300">We can't see notes you create's content and title</h2>
<h2 class="w300">We can't see the content and title of the notes you create</h2>
<p>Your notes are <a href="https://en.wikipedia.org/wiki/End-to-end_encryption">encrypted end-to-end</a> using AES
(Advanced Encryption Standard) 256-bit encryption.</p>
<p>We can only see:</p>
@ -79,7 +70,7 @@
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.</p>
<br>
<button onclick="if (document.referrer !== "") {window.location.href = document.referrer;}else {window.location.href = "https://notes.hectabit.org/login";}" style="cursor: pointer; padding: 15px 20px;margin-right: auto;color: white;text-decoration: none;background-color: var(--theme-color);border-radius: 8px;border: medium;font-size: 15px;">Take me back where I was!</button>
<button onclick="if(document.referrer!==' '){if(document.referrer!==''){window.location.href=document.referrer;}else{window.location.href='/';}window.location.href=document.referrer; }else{window.location.href='../index.html';}" style="cursor: pointer; padding: 15px 20px;margin-right: auto;color: white;text-decoration: none;background-color: var(--theme-color);border-radius: 8px;border: medium;font-size: 15px;">Take me back where I was!</button>
<br><br>
</body>

View file

@ -1,28 +1,19 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>Signup - Burgernotes</title>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" type="text/css" href="../static/css/style.css" />
<script src="../static/js/hash-wasm.js"></script>
<link rel="icon" href="../static/svg/favicon.svg">
<script>
if (window.location.href.endsWith('/index.html')) {
if (window.location.origin !== null) {
var currentUrl = window.location.href;
var newUrl = currentUrl.replace('/index.html', '');
window.location.href = newUrl;
}
}
</script>
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
<script src="/static/js/hash-wasm.js"></script>
<link rel="icon" href="/static/svg/favicon.svg">
</head>
<body>
<p class="credit">Image by perga (@pergagreen on discord)</p>
<img src="/static/img/background.jpg" class="background">
<img src="/static/img/background.jpg" class="background" alt="">
<div class="inoutdiv">
<h2 class="w300">Signup</h2>
<p>Signup for a Burgernotes account</p>
@ -30,10 +21,10 @@
<input id="usernameBox" type="text" placeholder="Username">
<input id="passwordBox" type="password" placeholder="Password"><br>
<button id="signupButton">Signup</button><br><br>
<p>Already have an account? If so, <a href="../login/index.html">Login</a> instead!</p>
<p>Already have an account? If so, <a href="/login/">Login</a> instead!</p>
<p>Please note that it's impossible to reset your password, do not forget it!</p>
<div style="display: flex;"><p id="homeserver">Your homeserver is loading... </p><div style="display: flex;flex-direction: column;justify-content: center;"><a href="/homeserver">Change</a></div></div>
<a href="../privacy/index.html">Privacy &amp; Terms</a>
<a href="/privacy/">Privacy &amp; Terms</a>
</div>
<script type="text/javascript" src="../static/js/signup.js"></script>
<script type="text/javascript" src="/static/js/signup.js"></script>
</body>

View file

@ -345,6 +345,21 @@ body {
width: calc(100% - 180px - 7px - 6px);
height: calc(100% - 50px - 6px - 8px - 30px);
font-family: "Inter", sans-serif;
display: flex;
}
.noteBoxText {
background-color: var(--editor);
color: var(--text-color);
border: none;
width: 100%;
font-family: "Inter", sans-serif;
}
iframe#markdown {
width: 100%;
border: none;
border-left: solid var(--bar) 1px;
}
.noteBox:focus {

View file

@ -1,10 +1,10 @@
if (localStorage.getItem("DONOTSHARE-secretkey") !== null) {
window.location.replace("../app/index.html")
window.location.replace("/app/")
document.body.innerHTML = "Redirecting..."
throw new Error();
}
if (localStorage.getItem("DONOTSHARE-password") !== null) {
window.location.replace("../app/index.html")
window.location.replace("/app/")
document.body.innerHTML = "Redirecting..."
throw new Error();
}
@ -28,20 +28,20 @@ inputNameBox.innerText = "Username:"
let currentInputType = 0
function showInput(inputType) {
if (inputType == 0) {
if (inputType === 0) {
usernameBox.classList.remove("hidden")
passwordBox.classList.add("hidden")
backButton.classList.add("hidden")
inputNameBox.innerText = "Username:"
statusBox.innerText = "Login to your Burgernotes account!"
currentInputType = 0
} else if (inputType == 1) {
} else if (inputType === 1) {
usernameBox.classList.add("hidden")
passwordBox.classList.remove("hidden")
backButton.classList.remove("hidden")
inputNameBox.innerText = "Password:"
currentInputType = 1
} else if (inputType == 2) {
} else if (inputType === 2) {
usernameBox.classList.add("hidden")
passwordBox.classList.add("hidden")
signupButton.classList.add("hidden")
@ -75,9 +75,9 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById("homeserver").innerText = "Your homeserver is: " + remote + ". "
});
signupButton.addEventListener("click", (event) => {
signupButton.addEventListener("click", () => {
if (passwordBox.classList.contains("hidden")) {
if (usernameBox.value == "") {
if (usernameBox.value === "") {
statusBox.innerText = "A username is required!"
return
} else {
@ -89,7 +89,7 @@ signupButton.addEventListener("click", (event) => {
let username = usernameBox.value
let password = passwordBox.value
if (password == "") {
if (password === "") {
statusBox.innerText = "A password is required!"
return
}
@ -99,7 +99,7 @@ signupButton.addEventListener("click", (event) => {
statusBox.innerText = "Signing in..."
async function hashpassold(pass) {
const key = await hashwasm.argon2id({
return await hashwasm.argon2id({
password: pass,
salt: await hashwasm.sha512(pass),
parallelism: 1,
@ -107,9 +107,8 @@ signupButton.addEventListener("click", (event) => {
memorySize: 512,
hashLength: 32,
outputType: "encoded"
});
return key
};
})
}
async function hashpass(pass) {
let key = pass
@ -117,7 +116,7 @@ signupButton.addEventListener("click", (event) => {
key = await hashwasm.sha3(key)
}
return key
};
}
fetch(remote + "/api/login", {
method: "POST",
@ -135,13 +134,13 @@ signupButton.addEventListener("click", (event) => {
.then((response) => {
async function doStuff() {
let responseData = await response.json()
if (response.status == 200) {
if (response.status === 200) {
localStorage.setItem("DONOTSHARE-secretkey", responseData["key"])
localStorage.setItem("DONOTSHARE-password", await hashwasm.sha512(password))
window.location.href = "../app/index.html"
window.location.href = "/app/"
}
else if (response.status == 401) {
else if (response.status === 401) {
console.log("Trying oldhash")
fetch(remote + "/api/login", {
method: "POST",
@ -159,13 +158,13 @@ signupButton.addEventListener("click", (event) => {
.then((response) => {
async function doStuff2() {
let responseData = await response.json()
if (response.status == 200) {
if (response.status === 200) {
localStorage.setItem("DONOTSHARE-secretkey", responseData["key"])
localStorage.setItem("DONOTSHARE-password", await hashwasm.sha512(password))
window.location.href = "../app/index.html"
window.location.href = "/app/"
}
else if (response.status == 401) {
else if (response.status === 401) {
statusBox.innerText = "Wrong username or password..."
showInput(1)
showElements(true)
@ -192,7 +191,7 @@ signupButton.addEventListener("click", (event) => {
}
});
backButton.addEventListener("click", (event) => {
backButton.addEventListener("click", () => {
showInput(0)
});

View file

@ -1,10 +1,10 @@
if (localStorage.getItem("DONOTSHARE-secretkey") === null) {
window.location.replace("../login/index.html")
window.location.replace("/login")
document.body.innerHTML = "Redirecting..."
throw new Error();
}
if (localStorage.getItem("DONOTSHARE-password") === null) {
window.location.replace("../login/index.html")
window.location.replace("/login")
document.body.innerHTML = "Redirecting..."
throw new Error();
}
@ -21,16 +21,10 @@ if (remote == null) {
function formatBytes(a, b = 2) { if (!+a) return "0 Bytes"; const c = 0 > b ? 0 : b, d = Math.floor(Math.log(a) / Math.log(1000)); return `${parseFloat((a / Math.pow(1000, d)).toFixed(c))} ${["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"][d]}` }
function truncateString(str, num) {
if (str.length > num) {
return str.slice(0, num) + "...";
} else {
return str;
}
}
let secretkey = localStorage.getItem("DONOTSHARE-secretkey")
let password = localStorage.getItem("DONOTSHARE-password")
let currentFontSize = 16
let markdowntoggle = false
let usernameBox = document.getElementById("usernameBox")
let optionsCoverDiv = document.getElementById("optionsCoverDiv")
@ -45,7 +39,6 @@ let exitSessionsThing = document.getElementById("exitSessionsThing")
let sessionManagerButton = document.getElementById("sessionManagerButton")
let sessionManagerDiv = document.getElementById("sessionManagerDiv")
let sessionDiv = document.getElementById("sessionDiv")
let mfaDiv = document.getElementById("mfaDiv")
let deleteMyAccountButton = document.getElementById("deleteMyAccountButton")
let storageThing = document.getElementById("storageThing")
let storageProgressThing = document.getElementById("storageProgressThing")
@ -56,8 +49,13 @@ let notesDiv = document.getElementById("notesDiv")
let newNote = document.getElementById("newNote")
let noteBox = document.getElementById("noteBox")
let loadingStuff = document.getElementById("loadingStuff")
let burgerButton = document.getElementById("burgerButton")
let exportNotesButton = document.getElementById("exportNotesButton")
let markdown = document.getElementById('markdown');
let textSizeBox = document.getElementById('textSizeBox');
let textPlusBox = document.getElementById('textPlusBox');
let textMinusBox = document.getElementById('textMinusBox');
let wordCountBox = document.getElementById('wordCountBox');
let removeBox = document.getElementById("removeBox")
let selectedNote = 0
let timer
@ -70,6 +68,8 @@ if (/Android|iPhone|iPod/i.test(navigator.userAgent)) {
noteBox.style.fontSize = "18px"
noteBox.classList.add("hidden")
let touchstartX, touchstartY, touchendX, touchendY
notesBar.addEventListener("touchstart", function (event) {
touchstartX = event.changedTouches[0].screenX;
touchstartY = event.changedTouches[0].screenY;
@ -96,7 +96,7 @@ if (/Android|iPhone|iPod/i.test(navigator.userAgent)) {
if (touchendX > touchstartX + 75) {
notesBar.style.width = "calc(100% - 10px)";
noteBox.style.width = "10px"
if (selectedNote != 0) {
if (selectedNote !== 0) {
noteBox.readOnly = true
}
notesDiv.classList.remove("hidden")
@ -107,7 +107,7 @@ if (/Android|iPhone|iPod/i.test(navigator.userAgent)) {
if (touchendX < touchstartX - 75) {
noteBox.style.width = "calc(100% - 30px)";
notesBar.style.width = "10px"
if (selectedNote != 0) {
if (selectedNote !== 0) {
noteBox.readOnly = false
}
notesDiv.classList.add("hidden")
@ -129,50 +129,11 @@ function displayError(message) {
errorMessageThing.innerHTML = message
}
closeErrorButton.addEventListener("click", (event) => {
closeErrorButton.addEventListener("click", () => {
errorDiv.classList.add("hidden")
optionsCoverDiv.classList.add("hidden")
});
function displayPrompt(message, placeholdertext, callback) {
errorMessageThing.innerText = message
errorInput.value = ""
errorInput.placeholder = placeholdertext
closeErrorButton.addEventListener("click", (event) => {
if (callback) {
callback(errorInput.value)
callback = undefined
}
});
errorInput.addEventListener("keyup", (event) => {
if (event.key == "Enter") {
callback(errorInput.value)
callback = undefined
errorDiv.classList.add("hidden")
optionsCoverDiv.classList.add("hidden")
errorInput.classList.add("hidden")
cancelErrorButton.classList.add("hidden")
}
});
cancelErrorButton.addEventListener("click", (event) => {
callback = undefined
errorDiv.classList.add("hidden")
optionsCoverDiv.classList.add("hidden")
errorInput.classList.add("hidden")
cancelErrorButton.classList.add("hidden")
});
errorDiv.classList.remove("hidden")
optionsCoverDiv.classList.remove("hidden")
errorInput.classList.remove("hidden")
cancelErrorButton.classList.remove("hidden")
errorInput.focus()
}
closeErrorButton.addEventListener("click", (event) => {
closeErrorButton.addEventListener("click", () => {
errorDiv.classList.add("hidden")
optionsCoverDiv.classList.add("hidden")
errorInput.classList.add("hidden")
@ -180,9 +141,37 @@ closeErrorButton.addEventListener("click", (event) => {
});
function updateFont() {
let currentFontSize = localStorage.getItem("SETTING-fontsize")
currentFontSize = localStorage.getItem("SETTING-fontsize")
noteBox.style.fontSize = currentFontSize + "px"
textSizeBox.innerText = currentFontSize + "px"
if (markdowntoggle) {
markdown.srcdoc = "<!DOCTYPE html><html lang='en'><style>html { height: 100% } body { font-family: 'Inter', sans-serif; height: 100%; color: " + getComputedStyle(document.documentElement).getPropertyValue('--text-color') + "; font-size: " + currentFontSize + "px; }</style>" + marked.parse(noteBox.value) + "</html>";
}
}
async function waitforedit() {
while(true) {
await fetch(remote + "/api/waitforedit", {
method: "POST",
body: JSON.stringify({
"secretKey": localStorage.getItem("DONOTSHARE-secretkey")
}),
headers: {
"Content-Type": "application/json; charset=UTF-8"
}
})
.then(async (response) => {
async function doStuff() {
const data = await response.json();
// Access the "note" field from the response
const note = data["note"];
if (note === selectedNote) {
selectNote(selectedNote)
}
}
doStuff()
});
}
}
if (localStorage.getItem("SETTING-fontsize") === null) {
@ -192,11 +181,11 @@ if (localStorage.getItem("SETTING-fontsize") === null) {
updateFont()
}
textPlusBox.addEventListener("click", (event) => {
textPlusBox.addEventListener("click", () => {
localStorage.setItem("SETTING-fontsize", String(Number(localStorage.getItem("SETTING-fontsize")) + Number(1)))
updateFont()
});
textMinusBox.addEventListener("click", (event) => {
textMinusBox.addEventListener("click", () => {
localStorage.setItem("SETTING-fontsize", String(Number(localStorage.getItem("SETTING-fontsize")) - Number(1)))
updateFont()
});
@ -221,19 +210,19 @@ function updateUserInfo() {
"Content-Type": "application/json; charset=UTF-8"
}
})
.catch((error) => {
.catch(() => {
noteBox.readOnly = true
noteBox.value = ""
noteBox.placeholder = "Failed to connect to the server.\nPlease check your internet connection."
})
.then((response) => {
async function doStuff() {
if (response.status == 500) {
if (response.status === 500) {
displayError("Something went wrong! Signing you out..")
closeErrorButton.classList.add("hidden")
usernameBox.innerText = ""
setTimeout(function () {
window.location.replace("../logout/index.html")
window.location.replace("/logout")
}, 2500);
} else {
let responseData = await response.json()
@ -249,20 +238,20 @@ function updateUserInfo() {
doStuff()
});
}
usernameBox.addEventListener("click", (event) => {
usernameBox.addEventListener("click", () => {
optionsCoverDiv.classList.remove("hidden")
optionsDiv.classList.remove("hidden")
updateUserInfo()
});
logOutButton.addEventListener("click", (event) => {
window.location.replace("../logout/index.html")
logOutButton.addEventListener("click", () => {
window.location.replace("/logout")
});
exitThing.addEventListener("click", (event) => {
exitThing.addEventListener("click", () => {
optionsDiv.classList.add("hidden")
optionsCoverDiv.classList.add("hidden")
});
deleteMyAccountButton.addEventListener("click", (event) => {
if (confirm("Are you REALLY sure that you want to delete your account? There's no going back!") == true) {
deleteMyAccountButton.addEventListener("click", () => {
if (confirm("Are you REALLY sure that you want to delete your account? There's no going back!") === true) {
fetch(remote + "/api/deleteaccount", {
method: "POST",
body: JSON.stringify({
@ -273,15 +262,15 @@ deleteMyAccountButton.addEventListener("click", (event) => {
}
})
.then((response) => {
if (response.status == 200) {
window.location.href = "../logout/index.html"
if (response.status === 200) {
window.location.href = "/logout"
} else {
displayError("Failed to delete account (HTTP error code " + response.status + ")")
}
})
}
});
sessionManagerButton.addEventListener("click", (event) => {
sessionManagerButton.addEventListener("click", () => {
optionsDiv.classList.add("hidden")
sessionManagerDiv.classList.remove("hidden")
@ -298,13 +287,14 @@ sessionManagerButton.addEventListener("click", (event) => {
async function doStuff() {
let responseData = await response.json()
document.querySelectorAll(".burgerSession").forEach((el) => el.remove());
let ua;
for (let i in responseData) {
let sessionElement = document.createElement("div")
let sessionText = document.createElement("p")
let sessionImage = document.createElement("img")
let sessionRemoveButton = document.createElement("button")
sessionText.classList.add("w300")
if (responseData[i]["thisSession"] == true) {
if (responseData[i]["thisSession"] === true) {
sessionText.innerText = "(current) " + responseData[i]["device"]
} else {
sessionText.innerText = responseData[i]["device"]
@ -319,11 +309,11 @@ sessionManagerButton.addEventListener("click", (event) => {
if (ua.includes("NT") || ua.includes("Linux")) {
sessionImage.src = "/static/svg/device_computer.svg"
}
if (ua.includes("iPhone" || ua.includes("Android") || ua.include("iPod"))) {
if (ua.includes("iPhone" || ua.includes("Android") || ua.includes("iPod"))) {
sessionImage.src = "/static/svg/device_smartphone.svg"
}
sessionRemoveButton.addEventListener("click", (event) => {
sessionRemoveButton.addEventListener("click", () => {
fetch(remote + "/api/sessions/remove", {
method: "POST",
body: JSON.stringify({
@ -334,9 +324,9 @@ sessionManagerButton.addEventListener("click", (event) => {
"Content-Type": "application/json; charset=UTF-8"
}
})
.then((response) => {
if (responseData[i]["thisSession"] == true) {
window.location.replace("../logout/index.html")
.then(() => {
if (responseData[i]["thisSession"] === true) {
window.location.replace("/logout")
}
});
sessionElement.remove()
@ -354,7 +344,7 @@ sessionManagerButton.addEventListener("click", (event) => {
doStuff()
});
});
exitSessionsThing.addEventListener("click", (event) => {
exitSessionsThing.addEventListener("click", () => {
optionsDiv.classList.remove("hidden")
sessionManagerDiv.classList.add("hidden")
});
@ -363,15 +353,21 @@ updateUserInfo()
function updateWordCount() {
let wordCount = noteBox.value.split(" ").length
if (wordCount == 1) {
if (wordCount === 1) {
wordCount = 0
}
wordCountBox.innerText = wordCount + " words"
}
function renderMarkDown() {
if (markdowntoggle) {
markdown.srcdoc = "<!DOCTYPE html><html lang='en'><style>html { height: 100% } body { font-family: 'Inter', sans-serif; height: 100%; color: " + getComputedStyle(document.documentElement).getPropertyValue('--text-color') + "; font-size: " + currentFontSize + "px; }</style>" + marked.parse(noteBox.value) + "</html>"
}
}
function selectNote(nameithink) {
document.querySelectorAll(".noteButton").forEach((el) => el.classList.remove("selected"));
let thingArray = Array.from(document.querySelectorAll(".noteButton")).find(el => el.id == nameithink);
let thingArray = Array.from(document.querySelectorAll(".noteButton")).find(el => String(nameithink) === String(el.id));
thingArray.classList.add("selected")
fetch(remote + "/api/readnote", {
@ -384,7 +380,7 @@ function selectNote(nameithink) {
"Content-Type": "application/json; charset=UTF-8"
}
})
.catch((error) => {
.catch(() => {
noteBox.readOnly = true
noteBox.value = ""
noteBox.placeholder = ""
@ -399,17 +395,17 @@ function selectNote(nameithink) {
let responseData = await response.json()
let bytes = CryptoJS.AES.decrypt(responseData["content"], password);
let originalText = bytes.toString(CryptoJS.enc.Utf8);
noteBox.value = originalText
noteBox.value = bytes.toString(CryptoJS.enc.Utf8)
updateWordCount()
renderMarkDown()
noteBox.addEventListener("input", (event) => {
noteBox.addEventListener("input", () => {
updateWordCount()
renderMarkDown()
clearTimeout(timer);
timer = setTimeout(() => {
let encryptedTitle = "New note"
if (noteBox.value.substring(0, noteBox.value.indexOf("\n")) != "") {
if (noteBox.value.substring(0, noteBox.value.indexOf("\n")) !== "") {
let firstTitle = noteBox.value.substring(0, noteBox.value.indexOf("\n"));
document.getElementById(nameithink).innerText = firstTitle
@ -417,7 +413,7 @@ function selectNote(nameithink) {
}
let encryptedText = CryptoJS.AES.encrypt(noteBox.value, password).toString();
if (selectedNote == nameithink) {
if (selectedNote === nameithink) {
fetch(remote + "/api/editnote", {
method: "POST",
body: JSON.stringify({
@ -431,11 +427,11 @@ function selectNote(nameithink) {
}
})
.then((response) => {
if (response.status == 418) {
if (response.status === 418) {
displayError("You've ran out of storage... Changes will not be saved until you free up storage!")
}
})
.catch((error) => {
.catch(() => {
displayError("Failed to save changes, please try again later...")
})
}
@ -465,6 +461,7 @@ function updateNotes() {
noteBox.value = ""
clearTimeout(timer)
updateWordCount()
renderMarkDown()
let responseData = await response.json()
for (let i in responseData) {
@ -490,10 +487,10 @@ function updateNotes() {
"Content-Type": "application/json; charset=UTF-8"
}
})
.then((response) => {
.then(() => {
updateNotes()
})
.catch((error) => {
.catch(() => {
displayError("Something went wrong! Please try again later...")
})
} else {
@ -509,9 +506,9 @@ function updateNotes() {
updateNotes()
newNote.addEventListener("click", (event) => {
newNote.addEventListener("click", () => {
let noteName = "New note"
let encryptedName = CryptoJS.AES.encrypt(noteName, password).toString();
let encryptedName = CryptoJS.AES.encrypt(noteName, password).toString(CryptoJS.enc.Utf8);
fetch(remote + "/api/newnote", {
method: "POST",
body: JSON.stringify({
@ -522,7 +519,7 @@ newNote.addEventListener("click", (event) => {
"Content-Type": "application/json; charset=UTF-8"
}
})
.catch((error) => {
.catch(() => {
displayError("Failed to create new note, please try again later...")
})
.then((response) => {
@ -535,8 +532,8 @@ newNote.addEventListener("click", (event) => {
});
});
function downloadObjectAsJson(exportObj, exportName) {
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj));
var downloadAnchorNode = document.createElement("a");
let dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj));
let downloadAnchorNode = document.createElement("a");
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", exportName + ".json");
document.body.appendChild(downloadAnchorNode);
@ -545,7 +542,6 @@ function downloadObjectAsJson(exportObj, exportName) {
}
function exportNotes() {
let noteExport = []
fetch(remote + "/api/exportnotes", {
method: "POST",
body: JSON.stringify({
@ -562,14 +558,10 @@ function exportNotes() {
exportNotes.innerText = "Decrypting " + i + "/" + noteCount
let bytes = CryptoJS.AES.decrypt(responseData[i]["title"], password);
let originalTitle = bytes.toString(CryptoJS.enc.Utf8);
responseData[i]["title"] = originalTitle
responseData[i]["title"] = bytes.toString(CryptoJS.enc.Utf8)
let bytesd = CryptoJS.AES.decrypt(responseData[i]["content"], password);
let originalContent = bytesd.toString(CryptoJS.enc.Utf8);
responseData[i]["content"] = originalContent
responseData[i]["content"] = bytesd.toString(CryptoJS.enc.Utf8)
}
let jsonString = JSON.parse(JSON.stringify(responseData))
@ -584,34 +576,42 @@ function exportNotes() {
}
function isFirstTimeVisitor() {
if (document.cookie.indexOf("visited=true") !== -1) {
return false;
} else {
var expirationDate = new Date();
expirationDate.setFullYear(expirationDate.getFullYear() + 1);
document.cookie = "visited=true; expires=" + expirationDate.toUTCString() + "; path=/; SameSite=strict";
if (localStorage.getItem("FIRSTVISIT") === null) {
localStorage.setItem("FIRSTVISIT", "1")
return true;
} else {
return false;
}
}
function firstNewVersion() {
if (document.cookie.indexOf("version=1.2") !== -1) {
if (localStorage.getItem("NEWVERSION") === "1.2") {
return false;
} else {
var expirationDate = new Date();
expirationDate.setFullYear(expirationDate.getFullYear() + 1);
document.cookie = "version=1.2; expires=" + expirationDate.toUTCString() + "; path=/; SameSite=strict";
localStorage.setItem("NEWVERSION", "1.2")
return true;
}
}
exportNotesButton.addEventListener("click", (event) => {
function toggleMarkdown() {
if (markdown.style.display === 'none') {
markdown.style.display = 'inherit';
markdowntoggle = true
renderMarkDown()
} else {
markdown.style.display = 'none';
markdowntoggle = false
markdown.srcdoc = ""
}
}
exportNotesButton.addEventListener("click", () => {
exportNotesButton.innerText = "Downloading..."
exportNotes()
});
removeBox.addEventListener("click", (event) => {
if (selectedNote == 0) {
removeBox.addEventListener("click", () => {
if (selectedNote === 0) {
displayError("You need to select a note first!")
} else {
fetch(remote + "/api/removenote", {
@ -624,19 +624,25 @@ removeBox.addEventListener("click", (event) => {
"Content-Type": "application/json; charset=UTF-8"
}
})
.then((response) => {
.then(() => {
updateNotes()
})
.catch((error) => {
.catch(() => {
displayError("Something went wrong! Please try again later...")
})
}
});
document.addEventListener("DOMContentLoaded", function() {
markdown.srcdoc = "<!DOCTYPE html><html lang='en'><style>html { height: 100% } body { font-family: 'Inter', sans-serif; height: 100%; color: " + getComputedStyle(document.documentElement).getPropertyValue('--text-color') + "; font-size: " + currentFontSize + "px; }</style>" + marked.parse(noteBox.value) + "</html>"
});
if (isFirstTimeVisitor() && /Android|iPhone|iPod/i.test(navigator.userAgent)) {
displayError("To use Burgernotes:\n Swipe Right on a note to open it\n Swipe left in the text boxes to return to notes\n Click on a note to highlight it")
}
if (firstNewVersion()) {
displayError("What's new in Burgernotes 1.2?\n\nNote titles are now the first line of a note \(will not break compatibility with older notes\)\nIntroduced improved login screen\nNote titles now scroll correctly")
displayError("What's new in Burgernotes 1.2-1?\nNotes now support live editing\nFixed various bugs and issues in the client")
}
waitforedit()

1297
website/static/js/marked.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,10 @@
if (localStorage.getItem("DONOTSHARE-secretkey") !== null) {
window.location.replace("../app/index.html")
window.location.replace("/app/")
document.body.innerHTML = "Redirecting..."
throw new Error();
}
if (localStorage.getItem("DONOTSHARE-password") !== null) {
window.location.replace("../app/index.html")
window.location.replace("/app/")
document.body.innerHTML = "Redirecting..."
throw new Error();
}
@ -37,12 +37,12 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById("homeserver").innerText = "Your homeserver is: " + remote + ". "
});
signupButton.addEventListener("click", (event) => {
signupButton.addEventListener("click", () => {
async function doStuff() {
let username = usernameBox.value
let password = passwordBox.value
if (username == "") {
if (username === "") {
statusBox.innerText = "A username is required!"
return
}
@ -50,7 +50,7 @@ signupButton.addEventListener("click", (event) => {
statusBox.innerText = "Username cannot be more than 20 characters!"
return
}
if (password == "") {
if (password === "") {
statusBox.innerText = "A password is required!"
return
}
@ -68,7 +68,7 @@ signupButton.addEventListener("click", (event) => {
key = await hashwasm.sha3(key)
}
return key
};
}
fetch(remote + "/api/signup", {
@ -86,14 +86,14 @@ signupButton.addEventListener("click", (event) => {
async function doStuff() {
let responseData = await response.json()
if (response.status == 200) {
statusBox.innerText == "redirecting.."
if (response.status === 200) {
statusBox.innerText = "Redirecting...."
localStorage.setItem("DONOTSHARE-secretkey", responseData["key"])
localStorage.setItem("DONOTSHARE-password", await hashwasm.sha512(password))
window.location.href = "../app/index.html"
window.location.href = "/app/"
}
else if (response.status == 409) {
else if (response.status === 409) {
statusBox.innerText = "Username already taken!"
showElements(true)
}

View file

@ -0,0 +1 @@
121