Compare commits
5 Commits
main
...
feature/ca
Author | SHA1 | Date |
---|---|---|
kolaente | e6750871ed | |
kolaente | 106340934a | |
kolaente | b472dbd833 | |
kolaente | 5b626e8752 | |
kolaente | fb5cf704f2 |
107
.drone.yml
|
@ -1,4 +1,3 @@
|
|||
---
|
||||
kind: pipeline
|
||||
name: build
|
||||
|
||||
|
@ -39,7 +38,7 @@ steps:
|
|||
# - '.cache'
|
||||
|
||||
- name: dependencies
|
||||
image: node:18
|
||||
image: node:16
|
||||
pull: true
|
||||
environment:
|
||||
YARN_CACHE_FOLDER: .cache/yarn/
|
||||
|
@ -70,7 +69,7 @@ steps:
|
|||
# - dependencies
|
||||
|
||||
- name: lint
|
||||
image: node:18
|
||||
image: node:16
|
||||
pull: true
|
||||
environment:
|
||||
YARN_CACHE_FOLDER: .cache/yarn/
|
||||
|
@ -80,35 +79,38 @@ steps:
|
|||
depends_on:
|
||||
- dependencies
|
||||
|
||||
# Building in dev mode to avoid the service worker for testing
|
||||
- name: build-dev
|
||||
image: node:16
|
||||
pull: true
|
||||
environment:
|
||||
YARN_CACHE_FOLDER: .cache/yarn/
|
||||
CYPRESS_CACHE_FOLDER: .cache/cypress/
|
||||
commands:
|
||||
- yarn build:dev
|
||||
depends_on:
|
||||
- dependencies
|
||||
|
||||
- name: build-prod
|
||||
image: node:18
|
||||
image: node:16
|
||||
pull: true
|
||||
environment:
|
||||
YARN_CACHE_FOLDER: .cache/yarn/
|
||||
commands:
|
||||
- yarn build
|
||||
- yarn build --dest dist-prod
|
||||
depends_on:
|
||||
- dependencies
|
||||
|
||||
- name: test-unit
|
||||
image: node:18
|
||||
image: node:16
|
||||
pull: true
|
||||
commands:
|
||||
- yarn test:unit
|
||||
depends_on:
|
||||
- dependencies
|
||||
|
||||
- name: typecheck
|
||||
failure: ignore
|
||||
image: node:18
|
||||
pull: true
|
||||
commands:
|
||||
- yarn typecheck
|
||||
depends_on:
|
||||
- dependencies
|
||||
|
||||
- name: test-frontend
|
||||
image: cypress/browsers:node16.5.0-chrome94-ff93
|
||||
image: cypress/browsers:node14.17.0-chrome91-ff89
|
||||
pull: true
|
||||
environment:
|
||||
CYPRESS_API_URL: http://api:3456/api/v1
|
||||
|
@ -116,37 +118,35 @@ steps:
|
|||
YARN_CACHE_FOLDER: .cache/yarn/
|
||||
CYPRESS_CACHE_FOLDER: .cache/cypress/
|
||||
CYPRESS_DEFAULT_COMMAND_TIMEOUT: 60000
|
||||
CYPRESS_RECORD_KEY:
|
||||
from_secret: cypress_project_key
|
||||
commands:
|
||||
- sed -i 's/localhost/api/g' dist/index.html
|
||||
- yarn serve:dist & npx wait-on http://localhost:4173
|
||||
- yarn test:frontend --browser chrome --record
|
||||
- sed -i 's/localhost/api/g' dist-dev/index.html
|
||||
- yarn serve:dist-dev & npx wait-on http://localhost:5000
|
||||
- yarn test:frontend --browser chrome
|
||||
depends_on:
|
||||
- build-prod
|
||||
- dependencies
|
||||
- build-dev
|
||||
|
||||
- name: deploy-preview
|
||||
image: node:18
|
||||
- name: upload-test-results
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
environment:
|
||||
NETLIFY_AUTH_TOKEN:
|
||||
from_secret: netlify_auth_token
|
||||
NETLIFY_SITE_ID:
|
||||
from_secret: netlify_site_id
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
commands:
|
||||
- cp -r dist dist-preview
|
||||
# Override the default api url used for preview
|
||||
- sed -i 's|localhost:3456|try.vikunja.io|g' dist-preview/index.html
|
||||
- shasum -a 384 -c ./scripts/deploy-preview-netlify.js.sha384
|
||||
- node ./scripts/deploy-preview-netlify.js
|
||||
settings:
|
||||
bucket: drone-test-results
|
||||
access_key:
|
||||
from_secret: test_results_aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: test_results_aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
source: cypress/screenshots/**/**/*
|
||||
strip_prefix: cypress/screenshots/
|
||||
target: /${DRONE_REPO}/${DRONE_PULL_REQUEST}_${DRONE_BRANCH}/${DRONE_BUILD_NUMBER}/
|
||||
depends_on:
|
||||
- build-prod
|
||||
- test-frontend
|
||||
when:
|
||||
event:
|
||||
include:
|
||||
- pull_request
|
||||
status:
|
||||
- failure
|
||||
- success
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
|
@ -186,7 +186,7 @@ steps:
|
|||
# - '.cache'
|
||||
|
||||
- name: build
|
||||
image: node:18
|
||||
image: node:16
|
||||
pull: true
|
||||
group: build-static
|
||||
environment:
|
||||
|
@ -261,7 +261,7 @@ steps:
|
|||
# - '.cache'
|
||||
|
||||
- name: build
|
||||
image: node:18
|
||||
image: node:16
|
||||
pull: true
|
||||
group: build-static
|
||||
environment:
|
||||
|
@ -340,9 +340,6 @@ trigger:
|
|||
ref:
|
||||
- refs/heads/main
|
||||
- "refs/tags/**"
|
||||
event:
|
||||
exclude:
|
||||
- cron
|
||||
|
||||
steps:
|
||||
- name: docker-unstable
|
||||
|
@ -440,9 +437,6 @@ trigger:
|
|||
ref:
|
||||
- refs/heads/main
|
||||
- "refs/tags/**"
|
||||
event:
|
||||
exclude:
|
||||
- cron
|
||||
|
||||
steps:
|
||||
- name: docker-unstable
|
||||
|
@ -489,9 +483,6 @@ trigger:
|
|||
ref:
|
||||
- refs/heads/main
|
||||
- "refs/tags/**"
|
||||
event:
|
||||
exclude:
|
||||
- cron
|
||||
|
||||
depends_on:
|
||||
- docker-amd64-release
|
||||
|
@ -553,9 +544,6 @@ trigger:
|
|||
ref:
|
||||
- refs/heads/main
|
||||
- "refs/tags/**"
|
||||
event:
|
||||
exclude:
|
||||
- cron
|
||||
|
||||
depends_on:
|
||||
- build
|
||||
|
@ -592,9 +580,7 @@ trigger:
|
|||
branch:
|
||||
- main
|
||||
event:
|
||||
- cron
|
||||
cron:
|
||||
- update_translations
|
||||
- push
|
||||
|
||||
steps:
|
||||
- name: download
|
||||
|
@ -645,8 +631,3 @@ steps:
|
|||
environment:
|
||||
CROWDIN_KEY:
|
||||
from_secret: crowdin_key
|
||||
---
|
||||
kind: signature
|
||||
hmac: 997e1badebe484ac29557c4af356e63db4d3d57f3d32e92d482f117f8cec64da
|
||||
|
||||
...
|
||||
|
|
|
@ -21,9 +21,5 @@ indent_size = 2
|
|||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{scss,css}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[.nvmrc]
|
||||
insert_final_newline = false
|
|
@ -26,6 +26,3 @@ stats.html
|
|||
# Test files
|
||||
cypress/screenshots
|
||||
cypress/videos
|
||||
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
|
|
|
@ -9,13 +9,6 @@ All releases can be found on https://code.vikunja.io/frontend/releases.
|
|||
|
||||
The releases aim at the api versions which is why there are missing versions.
|
||||
|
||||
## [0.18.2] - 2021-11-23
|
||||
|
||||
### Fixed
|
||||
|
||||
* fix(docker): properly replace api url
|
||||
* fix: edit saved filter title
|
||||
|
||||
## [0.18.1] - 2021-09-08
|
||||
|
||||
### Added
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Stage 1: Build application
|
||||
FROM node:18 AS compile-image
|
||||
FROM node:16 AS compile-image
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
|
@ -24,6 +24,12 @@ RUN \
|
|||
# Stage 2: copy
|
||||
FROM nginx
|
||||
|
||||
RUN apt-get update && apt-get install -y apt-utils openssl && \
|
||||
mkdir -p /etc/nginx/ssl && \
|
||||
openssl genrsa -out /etc/nginx/ssl/dummy.key 2048 && \
|
||||
openssl req -new -key /etc/nginx/ssl/dummy.key -out /etc/nginx/ssl/dummy.csr -subj "/C=DE/L=Berlin/O=Vikunja/CN=Vikunja Snakeoil" && \
|
||||
openssl x509 -req -days 3650 -in /etc/nginx/ssl/dummy.csr -signkey /etc/nginx/ssl/dummy.key -out /etc/nginx/ssl/dummy.crt
|
||||
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
COPY run.sh /run.sh
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/frontend/status.svg)](https://drone.kolaente.de/vikunja/frontend)
|
||||
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
|
||||
[![Download](https://img.shields.io/badge/download-v0.18.2-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Download](https://img.shields.io/badge/download-v0.18.1-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Translation](https://badges.crowdin.net/vikunja/localized.svg)](https://crowdin.com/project/vikunja)
|
||||
|
||||
This is the web frontend for Vikunja, written in Vue.js.
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
# NPM renames .gitignore to .npmignore
|
||||
# In order to prevent that, we remove the initial "."
|
||||
# And the CLI then renames it
|
||||
|
||||
# Using Android gitignore template: https://github.com/github/gitignore/blob/master/Android.gitignore
|
||||
|
||||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
release/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
# Android Studio 3 in .gitignore file.
|
||||
.idea/caches
|
||||
.idea/modules.xml
|
||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||
.idea/navEditor.xml
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
#*.keystore
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
fastlane/readme.md
|
||||
|
||||
# Version control
|
||||
vcs.xml
|
||||
|
||||
# lint
|
||||
lint/intermediates/
|
||||
lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
|
||||
# Cordova plugins for Capacitor
|
||||
capacitor-cordova-android-plugins
|
||||
|
||||
# Copied web assets
|
||||
app/src/main/assets/public
|
|
@ -0,0 +1,2 @@
|
|||
/build/*
|
||||
!/build/.npmkeep
|
|
@ -0,0 +1,46 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
defaultConfig {
|
||||
applicationId "io.vikunja.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
flatDir{
|
||||
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||
implementation project(':capacitor-android')
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||
implementation project(':capacitor-cordova-android-plugins')
|
||||
}
|
||||
|
||||
apply from: 'capacitor.build.gradle'
|
||||
|
||||
try {
|
||||
def servicesJSON = file('google-services.json')
|
||||
if (servicesJSON.text) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
} catch(Exception e) {
|
||||
logger.warn("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
|
||||
android {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||
dependencies {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (hasProperty('postBuildExtras')) {
|
||||
postBuildExtras()
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.vikunja.app">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||
android:name="io.vikunja.app.MainActivity"
|
||||
android:label="@string/title_activity_main"
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||
android:launchMode="singleTask">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="@string/custom_url_scheme" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths"></meta-data>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<!-- Permissions -->
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!-- Camera, Photos, input file -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<!-- Geolocation API -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-feature android:name="android.hardware.location.gps" />
|
||||
<!-- Network API -->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<!-- Navigator.getUserMedia -->
|
||||
<!-- Video -->
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<!-- Audio -->
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||
</manifest>
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"appId": "io.vikunja.app",
|
||||
"appName": "Vikunja",
|
||||
"bundledWebRuntime": false,
|
||||
"npmClient": "yarn",
|
||||
"webDir": "dist",
|
||||
"plugins": {
|
||||
"SplashScreen": {
|
||||
"launchShowDuration": 0
|
||||
}
|
||||
},
|
||||
"cordova": {}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package io.vikunja.app;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.getcapacitor.BridgeActivity;
|
||||
import com.getcapacitor.Plugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MainActivity extends BridgeActivity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Initializes the Bridge
|
||||
this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{
|
||||
// Additional plugins you've installed go here
|
||||
// Ex: add(TotallyAwesomePlugin.class);
|
||||
}});
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 17 KiB |
|
@ -0,0 +1,34 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
After Width: | Height: | Size: 3.9 KiB |
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<WebView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="app_name">Vikunja</string>
|
||||
<string name="title_activity_main">Vikunja</string>
|
||||
<string name="package_name">io.vikunja.app</string>
|
||||
<string name="custom_url_scheme">io.vikunja.app</string>
|
||||
</resources>
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:background">@null</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="AppTheme.NoActionBarLaunch" parent="AppTheme.NoActionBar">
|
||||
<item name="android:background">@drawable/splash</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||
<access origin="*" />
|
||||
|
||||
|
||||
</widget>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="my_images" path="." />
|
||||
<cache-path name="my_cache_images" path="." />
|
||||
</paths>
|
|
@ -0,0 +1,29 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.6.1'
|
||||
classpath 'com.google.gms:google-services:4.3.3'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "variables.gradle"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
include ':capacitor-android'
|
||||
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
|
@ -0,0 +1,24 @@
|
|||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
|
@ -0,0 +1,188 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -0,0 +1,100 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -0,0 +1,5 @@
|
|||
include ':app'
|
||||
include ':capacitor-cordova-android-plugins'
|
||||
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
|
||||
|
||||
apply from: 'capacitor.settings.gradle'
|
|
@ -0,0 +1,17 @@
|
|||
ext {
|
||||
minSdkVersion = 21
|
||||
compileSdkVersion = 29
|
||||
targetSdkVersion = 29
|
||||
androidxAppCompatVersion = '1.1.0'
|
||||
androidxCoreVersion = '1.2.0'
|
||||
androidxMaterialVersion = '1.1.0-rc02'
|
||||
androidxBrowserVersion = '1.2.0'
|
||||
androidxLocalbroadcastmanagerVersion = '1.0.0'
|
||||
androidxExifInterfaceVersion = '1.2.0'
|
||||
firebaseMessagingVersion = '20.1.2'
|
||||
playServicesLocationVersion = '17.0.0'
|
||||
junitVersion = '4.12'
|
||||
androidxJunitVersion = '1.1.1'
|
||||
androidxEspressoCoreVersion = '3.2.0'
|
||||
cordovaAndroidVersion = '7.0.0'
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app',
|
||||
],
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"appId": "io.vikunja.app",
|
||||
"appName": "Vikunja",
|
||||
"bundledWebRuntime": false,
|
||||
"npmClient": "yarn",
|
||||
"webDir": "dist",
|
||||
"plugins": {
|
||||
"SplashScreen": {
|
||||
"launchShowDuration": 0
|
||||
}
|
||||
},
|
||||
"cordova": {},
|
||||
"linuxAndroidStudioPath": "/etc/profiles/per-user/konrad/bin/android-studio"
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"baseUrl": "http://localhost:4173",
|
||||
"baseUrl": "http://localhost:5000",
|
||||
"env": {
|
||||
"API_URL": "http://localhost:3456/api/v1",
|
||||
"TEST_SECRET": "averyLongSecretToSe33dtheDB"
|
||||
|
@ -7,6 +7,5 @@
|
|||
"video": false,
|
||||
"retries": {
|
||||
"runMode": 2
|
||||
},
|
||||
"projectId": "181c7x"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import faker from '@faker-js/faker'
|
||||
import faker from 'faker'
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import faker from '@faker-js/faker'
|
||||
import faker from 'faker'
|
||||
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from "date-fns"
|
||||
import faker from '@faker-js/faker'
|
||||
import faker from 'faker'
|
||||
|
||||
export class LinkShareFactory extends Factory {
|
||||
static table = 'link_shares'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from "date-fns"
|
||||
import faker from '@faker-js/faker'
|
||||
import faker from 'faker'
|
||||
|
||||
export class ListFactory extends Factory {
|
||||
static table = 'lists'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import faker from '@faker-js/faker'
|
||||
import faker from 'faker'
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import faker from '@faker-js/faker'
|
||||
import faker from 'faker'
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import faker from '@faker-js/faker'
|
||||
import faker from 'faker'
|
||||
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from "date-fns"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import faker from '@faker-js/faker'
|
||||
import faker from 'faker'
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import faker from '@faker-js/faker'
|
||||
import faker from 'faker'
|
||||
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from "date-fns"
|
||||
|
@ -11,7 +11,7 @@ export class UserFactory extends Factory {
|
|||
|
||||
return {
|
||||
id: '{increment}',
|
||||
username: faker.lorem.word(10) + faker.datatype.uuid(),
|
||||
username: faker.lorem.word(10) + faker.random.uuid(),
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.', // 1234
|
||||
status: 0,
|
||||
created: formatISO(now),
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
import {ListFactory} from '../../factories/list'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
import {prepareLists} from './prepareLists'
|
||||
|
||||
describe('List History', () => {
|
||||
prepareLists()
|
||||
|
||||
it('should show a list history on the home page', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/namespaces*').as('loadNamespaces')
|
||||
cy.intercept(Cypress.env('API_URL') + '/lists/*').as('loadList')
|
||||
|
||||
const lists = ListFactory.create(6)
|
||||
|
||||
cy.visit('/')
|
||||
cy.wait('@loadNamespaces')
|
||||
cy.get('body')
|
||||
.should('not.contain', 'Last viewed')
|
||||
|
||||
cy.visit(`/lists/${lists[0].id}`)
|
||||
cy.wait('@loadNamespaces')
|
||||
cy.wait('@loadList')
|
||||
cy.visit(`/lists/${lists[1].id}`)
|
||||
cy.wait('@loadNamespaces')
|
||||
cy.wait('@loadList')
|
||||
cy.visit(`/lists/${lists[2].id}`)
|
||||
cy.wait('@loadNamespaces')
|
||||
cy.wait('@loadList')
|
||||
cy.visit(`/lists/${lists[3].id}`)
|
||||
cy.wait('@loadNamespaces')
|
||||
cy.wait('@loadList')
|
||||
cy.visit(`/lists/${lists[4].id}`)
|
||||
cy.wait('@loadNamespaces')
|
||||
cy.wait('@loadList')
|
||||
cy.visit(`/lists/${lists[5].id}`)
|
||||
cy.wait('@loadNamespaces')
|
||||
cy.wait('@loadList')
|
||||
|
||||
// cy.visit('/')
|
||||
// cy.wait('@loadNamespaces')
|
||||
// Not using cy.visit here to work around the redirect issue fixed in #1337
|
||||
cy.get('nav.menu.top-menu a')
|
||||
.contains('Overview')
|
||||
.click()
|
||||
|
||||
cy.get('body')
|
||||
.should('contain', 'Last viewed')
|
||||
cy.get('.list-cards-wrapper-2-rows')
|
||||
.should('not.contain', lists[0].title)
|
||||
.should('contain', lists[1].title)
|
||||
.should('contain', lists[2].title)
|
||||
.should('contain', lists[3].title)
|
||||
.should('contain', lists[4].title)
|
||||
.should('contain', lists[5].title)
|
||||
})
|
||||
})
|
|
@ -1,76 +0,0 @@
|
|||
import {formatISO, format} from 'date-fns'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {prepareLists} from './prepareLists'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
|
||||
describe('List View Gantt', () => {
|
||||
prepareLists()
|
||||
|
||||
it('Hides tasks with no dates', () => {
|
||||
const tasks = TaskFactory.create(1)
|
||||
cy.visit('/lists/1/gantt')
|
||||
|
||||
cy.get('.gantt-chart .tasks')
|
||||
.should('not.contain', tasks[0].title)
|
||||
})
|
||||
|
||||
it('Shows tasks from the current and next month', () => {
|
||||
const now = new Date()
|
||||
const nextMonth = now
|
||||
nextMonth.setDate(1)
|
||||
nextMonth.setMonth(now.getMonth() + 1)
|
||||
|
||||
cy.visit('/lists/1/gantt')
|
||||
|
||||
cy.get('.gantt-chart .months')
|
||||
.should('contain', format(now, 'MMMM'))
|
||||
.should('contain', format(nextMonth, 'MMMM'))
|
||||
})
|
||||
|
||||
it('Shows tasks with dates', () => {
|
||||
const now = new Date()
|
||||
const tasks = TaskFactory.create(1, {
|
||||
start_date: formatISO(now),
|
||||
end_date: formatISO(now.setDate(now.getDate() + 4))
|
||||
})
|
||||
cy.visit('/lists/1/gantt')
|
||||
|
||||
cy.get('.gantt-chart .tasks')
|
||||
.should('not.be.empty')
|
||||
cy.get('.gantt-chart .tasks')
|
||||
.should('contain', tasks[0].title)
|
||||
})
|
||||
|
||||
it('Shows tasks with no dates after enabling them', () => {
|
||||
TaskFactory.create(1, {
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
})
|
||||
cy.visit('/lists/1/gantt')
|
||||
|
||||
cy.get('.gantt-options .fancycheckbox')
|
||||
.contains('Show tasks which don\'t have dates set')
|
||||
.click()
|
||||
|
||||
cy.get('.gantt-chart .tasks')
|
||||
.should('not.be.empty')
|
||||
cy.get('.gantt-chart .tasks .task.nodate')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Drags a task around', () => {
|
||||
const now = new Date()
|
||||
TaskFactory.create(1, {
|
||||
start_date: formatISO(now),
|
||||
end_date: formatISO(now.setDate(now.getDate() + 4))
|
||||
})
|
||||
cy.visit('/lists/1/gantt')
|
||||
|
||||
cy.get('.gantt-chart .tasks .task')
|
||||
.first()
|
||||
.trigger('mousedown', {which: 1})
|
||||
.trigger('mousemove', {clientX: 500, clientY: 0})
|
||||
.trigger('mouseup', {force: true})
|
||||
})
|
||||
})
|
|
@ -1,196 +0,0 @@
|
|||
import {BucketFactory} from '../../factories/bucket'
|
||||
import {ListFactory} from '../../factories/list'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {prepareLists} from './prepareLists'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
|
||||
describe('List View Kanban', () => {
|
||||
let buckets
|
||||
prepareLists()
|
||||
|
||||
beforeEach(() => {
|
||||
buckets = BucketFactory.create(2)
|
||||
})
|
||||
|
||||
it('Shows all buckets with their tasks', () => {
|
||||
const data = TaskFactory.create(10, {
|
||||
list_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.get('.kanban .bucket .title')
|
||||
.contains(buckets[0].title)
|
||||
.should('exist')
|
||||
cy.get('.kanban .bucket .title')
|
||||
.contains(buckets[1].title)
|
||||
.should('exist')
|
||||
cy.get('.kanban .bucket')
|
||||
.first()
|
||||
.should('contain', data[0].title)
|
||||
})
|
||||
|
||||
it('Can add a new task to a bucket', () => {
|
||||
TaskFactory.create(2, {
|
||||
list_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket')
|
||||
.contains(buckets[0].title)
|
||||
.get('.bucket-footer .button')
|
||||
.contains('Add another task')
|
||||
.click()
|
||||
cy.get('.kanban .bucket')
|
||||
.contains(buckets[0].title)
|
||||
.get('.bucket-footer .field .control input.input')
|
||||
.type('New Task{enter}')
|
||||
|
||||
cy.get('.kanban .bucket')
|
||||
.first()
|
||||
.should('contain', 'New Task')
|
||||
})
|
||||
|
||||
it('Can create a new bucket', () => {
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.get('.kanban .bucket.new-bucket .button')
|
||||
.click()
|
||||
cy.get('.kanban .bucket.new-bucket input.input')
|
||||
.type('New Bucket{enter}')
|
||||
|
||||
cy.wait(1000) // Wait for the request to finish
|
||||
cy.get('.kanban .bucket .title')
|
||||
.contains('New Bucket')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Can set a bucket limit', () => {
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
||||
.first()
|
||||
.click()
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
||||
.contains('Limit: Not Set')
|
||||
.click()
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input')
|
||||
.first()
|
||||
.type(3)
|
||||
cy.get('[data-cy="setBucketLimit"]')
|
||||
.first()
|
||||
.click()
|
||||
|
||||
cy.get('.kanban .bucket .bucket-header span.limit')
|
||||
.contains('0/3')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Can rename a bucket', () => {
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .bucket-header .title')
|
||||
.first()
|
||||
.type('{selectall}New Bucket Title{enter}')
|
||||
cy.get('.kanban .bucket .bucket-header .title')
|
||||
.first()
|
||||
.should('contain', 'New Bucket Title')
|
||||
})
|
||||
|
||||
it('Can delete a bucket', () => {
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
||||
.first()
|
||||
.click()
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
||||
.contains('Delete')
|
||||
.click()
|
||||
cy.get('.modal-mask .modal-container .modal-content .header')
|
||||
.should('contain', 'Delete the bucket')
|
||||
cy.get('.modal-mask .modal-container .modal-content .actions .button')
|
||||
.contains('Do it!')
|
||||
.click()
|
||||
|
||||
cy.get('.kanban .bucket .title')
|
||||
.contains(buckets[0].title)
|
||||
.should('not.exist')
|
||||
cy.get('.kanban .bucket .title')
|
||||
.contains(buckets[1].title)
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Can drag tasks around', () => {
|
||||
const tasks = TaskFactory.create(2, {
|
||||
list_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .tasks .task')
|
||||
.contains(tasks[0].title)
|
||||
.first()
|
||||
.drag('.kanban .bucket:nth-child(2) .tasks')
|
||||
|
||||
cy.get('.kanban .bucket:nth-child(2) .tasks')
|
||||
.should('contain', tasks[0].title)
|
||||
cy.get('.kanban .bucket:nth-child(1) .tasks')
|
||||
.should('not.contain', tasks[0].title)
|
||||
})
|
||||
|
||||
it('Should navigate to the task when the task card is clicked', () => {
|
||||
const tasks = TaskFactory.create(5, {
|
||||
id: '{increment}',
|
||||
list_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .tasks .task')
|
||||
.contains(tasks[0].title)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.url()
|
||||
.should('contain', `/tasks/${tasks[0].id}`, { timeout: 1000 })
|
||||
})
|
||||
|
||||
it('Should remove a task from the kanban board when moving it to another list', () => {
|
||||
const lists = ListFactory.create(2)
|
||||
BucketFactory.create(2, {
|
||||
list_id: '{increment}',
|
||||
})
|
||||
const tasks = TaskFactory.create(5, {
|
||||
id: '{increment}',
|
||||
list_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
const task = tasks[0]
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .tasks .task')
|
||||
.contains(task.title)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.get('.task-view .action-buttons .button', { timeout: 3000 })
|
||||
.contains('Move')
|
||||
.click()
|
||||
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
|
||||
.type(`${lists[1].title}{enter}`)
|
||||
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
|
||||
// presses enter and we can't simulate pressing on enter to select the item.
|
||||
cy.get('.task-view .content.details .field .multiselect.control .search-results')
|
||||
.children()
|
||||
.first()
|
||||
.click()
|
||||
|
||||
cy.get('.global-notification', { timeout: 1000 })
|
||||
.should('contain', 'Success')
|
||||
cy.go('back')
|
||||
cy.get('.kanban .bucket')
|
||||
.should('not.contain', task.title)
|
||||
})
|
||||
})
|
|
@ -1,97 +0,0 @@
|
|||
import {UserListFactory} from '../../factories/users_list'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {UserFactory} from '../../factories/user'
|
||||
import {ListFactory} from '../../factories/list'
|
||||
import {prepareLists} from './prepareLists'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
|
||||
describe('List View List', () => {
|
||||
prepareLists()
|
||||
|
||||
it('Should be an empty list', () => {
|
||||
cy.visit('/lists/1')
|
||||
cy.url()
|
||||
.should('contain', '/lists/1/list')
|
||||
cy.get('.list-title h1')
|
||||
.should('contain', 'First List')
|
||||
cy.get('.list-title .dropdown')
|
||||
.should('exist')
|
||||
cy.get('p')
|
||||
.contains('This list is currently empty.')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Should navigate to the task when the title is clicked', () => {
|
||||
const tasks = TaskFactory.create(5, {
|
||||
id: '{increment}',
|
||||
list_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/list')
|
||||
|
||||
cy.get('.tasks .task .tasktext')
|
||||
.contains(tasks[0].title)
|
||||
.first()
|
||||
.click()
|
||||
|
||||
cy.url()
|
||||
.should('contain', `/tasks/${tasks[0].id}`)
|
||||
})
|
||||
|
||||
it('Should not see any elements for a list which is shared read only', () => {
|
||||
UserFactory.create(2)
|
||||
UserListFactory.create(1, {
|
||||
list_id: 2,
|
||||
user_id: 1,
|
||||
right: 0,
|
||||
})
|
||||
const lists = ListFactory.create(2, {
|
||||
owner_id: '{increment}',
|
||||
namespace_id: '{increment}',
|
||||
})
|
||||
cy.visit(`/lists/${lists[1].id}/`)
|
||||
|
||||
cy.get('.list-title a.icon')
|
||||
.should('not.exist')
|
||||
cy.get('input.input[placeholder="Add a new task..."')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('Should only show the color of a list in the navigation and not in the list view', () => {
|
||||
const lists = ListFactory.create(1, {
|
||||
hex_color: '00db60',
|
||||
})
|
||||
TaskFactory.create(10, {
|
||||
list_id: lists[0].id,
|
||||
})
|
||||
cy.visit(`/lists/${lists[0].id}/`)
|
||||
|
||||
cy.get('.menu-list li .list-menu-link .color-bubble')
|
||||
.should('have.css', 'background-color', 'rgb(0, 219, 96)')
|
||||
cy.get('.tasks-container .tasks .color-bubble')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('Should paginate for > 50 tasks', () => {
|
||||
const tasks = TaskFactory.create(100, {
|
||||
id: '{increment}',
|
||||
title: i => `task${i}`,
|
||||
list_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/list')
|
||||
|
||||
cy.get('.tasks-container .tasks')
|
||||
.should('contain', tasks[99].title)
|
||||
|
||||
cy.get('.card-content .pagination .pagination-link')
|
||||
.contains('2')
|
||||
.click()
|
||||
|
||||
cy.url()
|
||||
.should('contain', '?page=2')
|
||||
cy.get('.tasks-container .tasks')
|
||||
.should('contain', tasks[1].title)
|
||||
cy.get('.tasks-container .tasks')
|
||||
.should('not.contain', tasks[99].title)
|
||||
})
|
||||
})
|
|
@ -1,52 +0,0 @@
|
|||
import {TaskFactory} from '../../factories/task'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
|
||||
describe('List View Table', () => {
|
||||
it('Should show a table with tasks', () => {
|
||||
const tasks = TaskFactory.create(1)
|
||||
cy.visit('/lists/1/table')
|
||||
|
||||
cy.get('.list-table table.table')
|
||||
.should('exist')
|
||||
cy.get('.list-table table.table')
|
||||
.should('contain', tasks[0].title)
|
||||
})
|
||||
|
||||
it('Should have working column switches', () => {
|
||||
TaskFactory.create(1)
|
||||
cy.visit('/lists/1/table')
|
||||
|
||||
cy.get('.list-table .filter-container .items .button')
|
||||
.contains('Columns')
|
||||
.click()
|
||||
cy.get('.list-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
|
||||
.contains('Priority')
|
||||
.click()
|
||||
cy.get('.list-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
|
||||
.contains('Done')
|
||||
.click()
|
||||
|
||||
cy.get('.list-table table.table th')
|
||||
.contains('Priority')
|
||||
.should('exist')
|
||||
cy.get('.list-table table.table th')
|
||||
.contains('Done')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('Should navigate to the task when the title is clicked', () => {
|
||||
const tasks = TaskFactory.create(5, {
|
||||
id: '{increment}',
|
||||
list_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/table')
|
||||
|
||||
cy.get('.list-table table.table')
|
||||
.contains(tasks[0].title)
|
||||
.click()
|
||||
|
||||
cy.url()
|
||||
.should('contain', `/tasks/${tasks[0].id}`)
|
||||
})
|
||||
})
|
|
@ -1,11 +1,25 @@
|
|||
import {formatISO, format} from 'date-fns'
|
||||
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {prepareLists} from './prepareLists'
|
||||
import {ListFactory} from '../../factories/list'
|
||||
import {UserListFactory} from '../../factories/users_list'
|
||||
import {UserFactory} from '../../factories/user'
|
||||
import {NamespaceFactory} from '../../factories/namespace'
|
||||
import {BucketFactory} from '../../factories/bucket'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
|
||||
describe('Lists', () => {
|
||||
let lists
|
||||
prepareLists((newLists) => (lists = newLists))
|
||||
|
||||
beforeEach(() => {
|
||||
UserFactory.create(1)
|
||||
NamespaceFactory.create(1)
|
||||
lists = ListFactory.create(1, {
|
||||
title: 'First List'
|
||||
})
|
||||
TaskFactory.truncate()
|
||||
})
|
||||
|
||||
it('Should create a new list', () => {
|
||||
cy.visit('/')
|
||||
|
@ -15,16 +29,17 @@ describe('Lists', () => {
|
|||
.contains('New list')
|
||||
.click()
|
||||
cy.url()
|
||||
.should('contain', '/lists/new/1')
|
||||
.should('contain', '/namespaces/1/list')
|
||||
cy.get('.card-header-title')
|
||||
.contains('New list')
|
||||
.contains('Create a new list')
|
||||
cy.get('input.input')
|
||||
.type('New List')
|
||||
cy.get('.button')
|
||||
.contains('Create')
|
||||
.click()
|
||||
|
||||
cy.get('.global-notification', { timeout: 1000 }) // Waiting until the request to create the new list is done
|
||||
cy.wait(1000) // Waiting until the request to create the new list is done
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
cy.url()
|
||||
.should('contain', '/lists/')
|
||||
|
@ -42,7 +57,7 @@ describe('Lists', () => {
|
|||
})
|
||||
|
||||
it('Should rename the list in all places', () => {
|
||||
TaskFactory.create(5, {
|
||||
const tasks = TaskFactory.create(5, {
|
||||
id: '{increment}',
|
||||
list_id: 1,
|
||||
})
|
||||
|
@ -72,7 +87,7 @@ describe('Lists', () => {
|
|||
.should('contain', newListName)
|
||||
.should('not.contain', lists[0].title)
|
||||
cy.visit('/')
|
||||
cy.get('.card-content')
|
||||
cy.get('.card-content .tasks')
|
||||
.should('contain', newListName)
|
||||
.should('not.contain', lists[0].title)
|
||||
})
|
||||
|
@ -87,7 +102,7 @@ describe('Lists', () => {
|
|||
.click()
|
||||
cy.url()
|
||||
.should('contain', '/settings/delete')
|
||||
cy.get('[data-cy="modalPrimary"]')
|
||||
cy.get('.modal-mask .modal-container .modal-content .actions a.button')
|
||||
.contains('Do it')
|
||||
.click()
|
||||
|
||||
|
@ -98,4 +113,429 @@ describe('Lists', () => {
|
|||
cy.location('pathname')
|
||||
.should('equal', '/')
|
||||
})
|
||||
|
||||
describe('List View', () => {
|
||||
it('Should be an empty list', () => {
|
||||
cy.visit('/lists/1')
|
||||
cy.url()
|
||||
.should('contain', '/lists/1/list')
|
||||
cy.get('.list-title h1')
|
||||
.should('contain', 'First List')
|
||||
cy.get('.list-title .dropdown')
|
||||
.should('exist')
|
||||
cy.get('p')
|
||||
.contains('This list is currently empty.')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Should navigate to the task when the title is clicked', () => {
|
||||
const tasks = TaskFactory.create(5, {
|
||||
id: '{increment}',
|
||||
list_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/list')
|
||||
|
||||
cy.get('.tasks .task .tasktext')
|
||||
.contains(tasks[0].title)
|
||||
.first()
|
||||
.click()
|
||||
|
||||
cy.url()
|
||||
.should('contain', `/tasks/${tasks[0].id}`)
|
||||
})
|
||||
|
||||
it('Should not see any elements for a list which is shared read only', () => {
|
||||
UserFactory.create(2)
|
||||
UserListFactory.create(1, {
|
||||
list_id: 2,
|
||||
user_id: 1,
|
||||
right: 0,
|
||||
})
|
||||
const lists = ListFactory.create(2, {
|
||||
owner_id: '{increment}',
|
||||
namespace_id: '{increment}',
|
||||
})
|
||||
cy.visit(`/lists/${lists[1].id}/`)
|
||||
|
||||
cy.get('.list-title a.icon')
|
||||
.should('not.exist')
|
||||
cy.get('input.input[placeholder="Add a new task..."')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('Should only show the color of a list in the navigation and not in the list view', () => {
|
||||
const lists = ListFactory.create(1, {
|
||||
hex_color: '00db60',
|
||||
})
|
||||
TaskFactory.create(10, {
|
||||
list_id: lists[0].id,
|
||||
})
|
||||
cy.visit(`/lists/${lists[0].id}/`)
|
||||
|
||||
cy.get('.menu-list li .list-menu-link .color-bubble')
|
||||
.should('have.css', 'background-color', 'rgb(0, 219, 96)')
|
||||
cy.get('.tasks-container .tasks .color-bubble')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('Should paginate for > 50 tasks', () => {
|
||||
const tasks = TaskFactory.create(100, {
|
||||
id: '{increment}',
|
||||
title: i => `task${i}`,
|
||||
list_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/list')
|
||||
|
||||
cy.get('.tasks-container .tasks')
|
||||
.should('contain', tasks[99].title)
|
||||
|
||||
cy.get('.card-content .pagination .pagination-link')
|
||||
.contains('2')
|
||||
.click()
|
||||
|
||||
cy.url()
|
||||
.should('contain', '?page=2')
|
||||
cy.get('.tasks-container .tasks')
|
||||
.should('contain', tasks[1].title)
|
||||
cy.get('.tasks-container .tasks')
|
||||
.should('not.contain', tasks[99].title)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Table View', () => {
|
||||
it('Should show a table with tasks', () => {
|
||||
const tasks = TaskFactory.create(1)
|
||||
cy.visit('/lists/1/table')
|
||||
|
||||
cy.get('.table-view table.table')
|
||||
.should('exist')
|
||||
cy.get('.table-view table.table')
|
||||
.should('contain', tasks[0].title)
|
||||
})
|
||||
|
||||
it('Should have working column switches', () => {
|
||||
TaskFactory.create(1)
|
||||
cy.visit('/lists/1/table')
|
||||
|
||||
cy.get('.table-view .filter-container .items .button')
|
||||
.contains('Columns')
|
||||
.click()
|
||||
cy.get('.table-view .filter-container .card .card-content .fancycheckbox .check')
|
||||
.contains('Priority')
|
||||
.click()
|
||||
cy.get('.table-view .filter-container .card .card-content .fancycheckbox .check')
|
||||
.contains('Done')
|
||||
.click()
|
||||
|
||||
cy.get('.table-view table.table th')
|
||||
.contains('Priority')
|
||||
.should('exist')
|
||||
cy.get('.table-view table.table th')
|
||||
.contains('Done')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('Should navigate to the task when the title is clicked', () => {
|
||||
const tasks = TaskFactory.create(5, {
|
||||
id: '{increment}',
|
||||
list_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/table')
|
||||
|
||||
cy.get('.table-view table.table')
|
||||
.contains(tasks[0].title)
|
||||
.click()
|
||||
|
||||
cy.url()
|
||||
.should('contain', `/tasks/${tasks[0].id}`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Gantt View', () => {
|
||||
it('Hides tasks with no dates', () => {
|
||||
const tasks = TaskFactory.create(1)
|
||||
cy.visit('/lists/1/gantt')
|
||||
|
||||
cy.get('.gantt-chart-container .gantt-chart .tasks')
|
||||
.should('not.contain', tasks[0].title)
|
||||
})
|
||||
|
||||
it('Shows tasks from the current and next month', () => {
|
||||
const now = new Date()
|
||||
const nextMonth = now
|
||||
nextMonth.setDate(1)
|
||||
nextMonth.setMonth(now.getMonth() + 1)
|
||||
|
||||
cy.visit('/lists/1/gantt')
|
||||
|
||||
cy.get('.gantt-chart-container .gantt-chart .months')
|
||||
.should('contain', format(now, 'MMMM'))
|
||||
.should('contain', format(nextMonth, 'MMMM'))
|
||||
})
|
||||
|
||||
it('Shows tasks with dates', () => {
|
||||
const now = new Date()
|
||||
const tasks = TaskFactory.create(1, {
|
||||
start_date: formatISO(now),
|
||||
end_date: formatISO(now.setDate(now.getDate() + 4))
|
||||
})
|
||||
cy.visit('/lists/1/gantt')
|
||||
|
||||
cy.get('.gantt-chart-container .gantt-chart .tasks')
|
||||
.should('not.be.empty')
|
||||
cy.get('.gantt-chart-container .gantt-chart .tasks')
|
||||
.should('contain', tasks[0].title)
|
||||
})
|
||||
|
||||
it('Shows tasks with no dates after enabling them', () => {
|
||||
TaskFactory.create(1, {
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
})
|
||||
cy.visit('/lists/1/gantt')
|
||||
|
||||
cy.get('.gantt-chart-container .gantt-options .fancycheckbox')
|
||||
.contains('Show tasks which don\'t have dates set')
|
||||
.click()
|
||||
|
||||
cy.get('.gantt-chart-container .gantt-chart .tasks')
|
||||
.should('not.be.empty')
|
||||
cy.get('.gantt-chart-container .gantt-chart .tasks .task.nodate')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Drags a task around', () => {
|
||||
const now = new Date()
|
||||
TaskFactory.create(1, {
|
||||
start_date: formatISO(now),
|
||||
end_date: formatISO(now.setDate(now.getDate() + 4))
|
||||
})
|
||||
cy.visit('/lists/1/gantt')
|
||||
|
||||
cy.get('.gantt-chart-container .gantt-chart .tasks .task')
|
||||
.first()
|
||||
.trigger('mousedown', {which: 1})
|
||||
.trigger('mousemove', {clientX: 500, clientY: 0})
|
||||
.trigger('mouseup', {force: true})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Kanban', () => {
|
||||
let buckets
|
||||
|
||||
beforeEach(() => {
|
||||
buckets = BucketFactory.create(2)
|
||||
})
|
||||
|
||||
it('Shows all buckets with their tasks', () => {
|
||||
const data = TaskFactory.create(10, {
|
||||
list_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.get('.kanban .bucket .title')
|
||||
.contains(buckets[0].title)
|
||||
.should('exist')
|
||||
cy.get('.kanban .bucket .title')
|
||||
.contains(buckets[1].title)
|
||||
.should('exist')
|
||||
cy.get('.kanban .bucket')
|
||||
.first()
|
||||
.should('contain', data[0].title)
|
||||
})
|
||||
|
||||
it('Can add a new task to a bucket', () => {
|
||||
const data = TaskFactory.create(2, {
|
||||
list_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.get('.kanban .bucket')
|
||||
.contains(buckets[0].title)
|
||||
.get('.bucket-footer .button')
|
||||
.contains('Add another task')
|
||||
.click()
|
||||
cy.get('.kanban .bucket')
|
||||
.contains(buckets[0].title)
|
||||
.get('.bucket-footer .field .control input.input')
|
||||
.type('New Task{enter}')
|
||||
|
||||
cy.get('.kanban .bucket')
|
||||
.first()
|
||||
.should('contain', 'New Task')
|
||||
})
|
||||
|
||||
it('Can create a new bucket', () => {
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.get('.kanban .bucket.new-bucket .button')
|
||||
.click()
|
||||
cy.get('.kanban .bucket.new-bucket input.input')
|
||||
.type('New Bucket{enter}')
|
||||
|
||||
cy.wait(1000) // Wait for the request to finish
|
||||
cy.get('.kanban .bucket .title')
|
||||
.contains('New Bucket')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Can set a bucket limit', () => {
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
||||
.first()
|
||||
.click()
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
||||
.contains('Limit: Not Set')
|
||||
.click()
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input')
|
||||
.first()
|
||||
.type(3)
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field a.button.is-primary')
|
||||
.first()
|
||||
.click()
|
||||
|
||||
cy.get('.kanban .bucket .bucket-header span.limit')
|
||||
.contains('0/3')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Can rename a bucket', () => {
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.get('.kanban .bucket .bucket-header .title')
|
||||
.first()
|
||||
.type('{selectall}New Bucket Title{enter}')
|
||||
cy.get('.kanban .bucket .bucket-header .title')
|
||||
.first()
|
||||
.should('contain', 'New Bucket Title')
|
||||
})
|
||||
|
||||
it('Can delete a bucket', () => {
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
||||
.first()
|
||||
.click()
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
||||
.contains('Delete')
|
||||
.click()
|
||||
cy.get('.modal-mask .modal-container .modal-content .header')
|
||||
.should('contain', 'Delete the bucket')
|
||||
cy.get('.modal-mask .modal-container .modal-content .actions .button')
|
||||
.contains('Do it!')
|
||||
.click()
|
||||
|
||||
cy.get('.kanban .bucket .title')
|
||||
.contains(buckets[0].title)
|
||||
.should('not.exist')
|
||||
cy.get('.kanban .bucket .title')
|
||||
.contains(buckets[1].title)
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Can drag tasks around', () => {
|
||||
const tasks = TaskFactory.create(2, {
|
||||
list_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.get('.kanban .bucket .tasks .task')
|
||||
.contains(tasks[0].title)
|
||||
.first()
|
||||
.drag('.kanban .bucket:nth-child(2) .tasks .dropper div')
|
||||
|
||||
cy.get('.kanban .bucket:nth-child(2) .tasks')
|
||||
.should('contain', tasks[0].title)
|
||||
cy.get('.kanban .bucket:nth-child(1) .tasks')
|
||||
.should('not.contain', tasks[0].title)
|
||||
})
|
||||
|
||||
it('Should navigate to the task when the task card is clicked', () => {
|
||||
const tasks = TaskFactory.create(5, {
|
||||
id: '{increment}',
|
||||
list_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .tasks .task')
|
||||
.contains(tasks[0].title)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.url()
|
||||
.should('contain', `/tasks/${tasks[0].id}`)
|
||||
})
|
||||
|
||||
it('Should remove a task from the kanban board when moving it to another list', () => {
|
||||
const lists = ListFactory.create(2)
|
||||
BucketFactory.create(2, {
|
||||
list_id: '{increment}',
|
||||
})
|
||||
const tasks = TaskFactory.create(5, {
|
||||
id: '{increment}',
|
||||
list_id: 1,
|
||||
bucket_id: 1,
|
||||
})
|
||||
const task = tasks[0]
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .tasks .task')
|
||||
.contains(task.title)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Move task')
|
||||
.click()
|
||||
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
|
||||
.type(`${lists[1].title}{enter}`)
|
||||
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
|
||||
// presses enter and we can't simulate pressing on enter to select the item.
|
||||
cy.get('.task-view .content.details .field .multiselect.control .search-results')
|
||||
.children()
|
||||
.first()
|
||||
.click()
|
||||
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
cy.go('back')
|
||||
cy.get('.kanban .bucket')
|
||||
.should('not.contain', task.title)
|
||||
})
|
||||
})
|
||||
|
||||
describe('List history', () => {
|
||||
it('should show a list history on the home page', () => {
|
||||
const lists = ListFactory.create(6)
|
||||
|
||||
cy.visit('/')
|
||||
cy.get('h3')
|
||||
.contains('Last viewed')
|
||||
.should('not.exist')
|
||||
|
||||
cy.visit(`/lists/${lists[0].id}`)
|
||||
cy.visit(`/lists/${lists[1].id}`)
|
||||
cy.visit(`/lists/${lists[2].id}`)
|
||||
cy.visit(`/lists/${lists[3].id}`)
|
||||
cy.visit(`/lists/${lists[4].id}`)
|
||||
cy.visit(`/lists/${lists[5].id}`)
|
||||
|
||||
cy.visit('/')
|
||||
cy.get('h3')
|
||||
.contains('Last viewed')
|
||||
.should('exist')
|
||||
cy.get('.list-cards-wrapper-2-rows')
|
||||
.should('not.contain', lists[0].title)
|
||||
.should('contain', lists[1].title)
|
||||
.should('contain', lists[2].title)
|
||||
.should('contain', lists[3].title)
|
||||
.should('contain', lists[4].title)
|
||||
.should('contain', lists[5].title)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -15,7 +15,7 @@ describe('Namepaces', () => {
|
|||
|
||||
it('Should be all there', () => {
|
||||
cy.visit('/namespaces')
|
||||
cy.get('[data-cy="namespace-title"]')
|
||||
cy.get('.namespace h1 span')
|
||||
.should('contain', namespaces[0].title)
|
||||
})
|
||||
|
||||
|
@ -23,14 +23,14 @@ describe('Namepaces', () => {
|
|||
const newNamespaceTitle = 'New Namespace'
|
||||
|
||||
cy.visit('/namespaces')
|
||||
cy.get('[data-cy="new-namespace"]')
|
||||
.should('contain', 'New namespace')
|
||||
cy.get('a.button')
|
||||
.contains('Create a new namespace')
|
||||
.click()
|
||||
|
||||
cy.url()
|
||||
.should('contain', '/namespaces/new')
|
||||
cy.get('.card-header-title')
|
||||
.should('contain', 'New namespace')
|
||||
.should('contain', 'Create a new namespace')
|
||||
cy.get('input.input')
|
||||
.type(newNamespaceTitle)
|
||||
cy.get('.button')
|
||||
|
@ -67,12 +67,12 @@ describe('Namepaces', () => {
|
|||
.contains('Save')
|
||||
.click()
|
||||
|
||||
cy.get('.global-notification', { timeout: 1000 })
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
cy.get('.namespace-container .menu.namespaces-lists')
|
||||
.should('contain', newNamespaceName)
|
||||
.should('not.contain', newNamespaces[0].title)
|
||||
cy.get('[data-cy="namespaces-list"]')
|
||||
cy.get('.content.namespaces-list')
|
||||
.should('contain', newNamespaceName)
|
||||
.should('not.contain', newNamespaces[0].title)
|
||||
})
|
||||
|
@ -89,7 +89,7 @@ describe('Namepaces', () => {
|
|||
.click()
|
||||
cy.url()
|
||||
.should('contain', '/settings/delete')
|
||||
cy.get('[data-cy="modalPrimary"]')
|
||||
cy.get('.modal-mask .modal-container .modal-content .actions a.button')
|
||||
.contains('Do it')
|
||||
.click()
|
||||
|
||||
|
@ -116,30 +116,30 @@ describe('Namepaces', () => {
|
|||
|
||||
// Initial
|
||||
cy.visit('/namespaces')
|
||||
cy.get('.namespace')
|
||||
cy.get('.namespaces-list .namespace')
|
||||
.should('not.contain', 'Archived')
|
||||
|
||||
// Show archived
|
||||
cy.get('[data-cy="show-archived-check"] label.check span')
|
||||
cy.get('.namespaces-list .fancycheckbox.show-archived-check label.check span')
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.get('[data-cy="show-archived-check"] input')
|
||||
cy.get('.namespaces-list .fancycheckbox.show-archived-check input')
|
||||
.should('be.checked')
|
||||
cy.get('.namespace')
|
||||
cy.get('.namespaces-list .namespace')
|
||||
.should('contain', 'Archived')
|
||||
|
||||
// Don't show archived
|
||||
cy.get('[data-cy="show-archived-check"] label.check span')
|
||||
cy.get('.namespaces-list .fancycheckbox.show-archived-check label.check span')
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.get('[data-cy="show-archived-check"] input')
|
||||
cy.get('.namespaces-list .fancycheckbox.show-archived-check input')
|
||||
.should('not.be.checked')
|
||||
|
||||
// Second time visiting after unchecking
|
||||
cy.visit('/namespaces')
|
||||
cy.get('[data-cy="show-archived-check"] input')
|
||||
cy.get('.namespaces-list .fancycheckbox.show-archived-check input')
|
||||
.should('not.be.checked')
|
||||
cy.get('.namespace')
|
||||
cy.get('.namespaces-list .namespace')
|
||||
.should('not.contain', 'Archived')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import {ListFactory} from '../../factories/list'
|
||||
import {UserFactory} from '../../factories/user'
|
||||
import {NamespaceFactory} from '../../factories/namespace'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
|
||||
export function prepareLists(setLists = () => {}) {
|
||||
beforeEach(() => {
|
||||
UserFactory.create(1)
|
||||
NamespaceFactory.create(1)
|
||||
const lists = ListFactory.create(1, {
|
||||
title: 'First List'
|
||||
})
|
||||
setLists(lists)
|
||||
TaskFactory.truncate()
|
||||
})
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import '../../support/authenticateUser'
|
||||
|
||||
const setHours = hours => {
|
||||
const date = new Date()
|
||||
date.setHours(hours)
|
||||
cy.clock(+date)
|
||||
}
|
||||
|
||||
describe('Home Page', () => {
|
||||
it('shows the right salutation in the night', () => {
|
||||
setHours(4)
|
||||
cy.visit('/')
|
||||
cy.get('h2').should('contain', 'Good Night')
|
||||
})
|
||||
it('shows the right salutation in the morning', () => {
|
||||
setHours(8)
|
||||
cy.visit('/')
|
||||
cy.get('h2').should('contain', 'Good Morning')
|
||||
})
|
||||
it('shows the right salutation in the day', () => {
|
||||
setHours(13)
|
||||
cy.visit('/')
|
||||
cy.get('h2').should('contain', 'Hi')
|
||||
})
|
||||
it('shows the right salutation in the night', () => {
|
||||
setHours(20)
|
||||
cy.visit('/')
|
||||
cy.get('h2').should('contain', 'Good Evening')
|
||||
})
|
||||
it('shows the right salutation in the night again', () => {
|
||||
setHours(23)
|
||||
cy.visit('/')
|
||||
cy.get('h2').should('contain', 'Good Night')
|
||||
})
|
||||
})
|
|
@ -7,7 +7,7 @@ describe('The Menu', () => {
|
|||
})
|
||||
|
||||
it('Can be hidden on desktop', () => {
|
||||
cy.get('button.menu-show-button:visible')
|
||||
cy.get('a.menu-show-button:visible')
|
||||
.click()
|
||||
cy.get('.namespace-container')
|
||||
.should('not.have.class', 'is-active')
|
||||
|
@ -21,7 +21,7 @@ describe('The Menu', () => {
|
|||
|
||||
it('Is can be shown on mobile', () => {
|
||||
cy.viewport('iphone-8')
|
||||
cy.get('button.menu-show-button:visible')
|
||||
cy.get('a.menu-show-button:visible')
|
||||
.click()
|
||||
cy.get('.namespace-container')
|
||||
.should('have.class', 'is-active')
|
||||
|
|
|
@ -116,7 +116,6 @@ describe('Task', () => {
|
|||
.should('be.visible')
|
||||
.should('contain', 'Done')
|
||||
cy.get('.task-view .action-buttons p.created')
|
||||
.scrollIntoView()
|
||||
.should('be.visible')
|
||||
.should('contain', 'Done')
|
||||
})
|
||||
|
@ -129,7 +128,7 @@ describe('Task', () => {
|
|||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Mark task done!')
|
||||
.contains('Done!')
|
||||
.click()
|
||||
|
||||
cy.get('.task-view .heading .is-done')
|
||||
|
@ -169,7 +168,7 @@ describe('Task', () => {
|
|||
.click()
|
||||
cy.get('.task-view .details.content.description .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll')
|
||||
.type('{selectall}New Description')
|
||||
cy.get('[data-cy="saveEditor"]')
|
||||
cy.get('.task-view .details.content.description .editor a')
|
||||
.contains('Save')
|
||||
.click()
|
||||
|
||||
|
@ -210,7 +209,7 @@ describe('Task', () => {
|
|||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Move')
|
||||
.contains('Move task')
|
||||
.click()
|
||||
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
|
||||
.type(`${lists[1].title}{enter}`)
|
||||
|
@ -237,7 +236,7 @@ describe('Task', () => {
|
|||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.should('be.visible')
|
||||
.contains('Delete')
|
||||
.contains('Delete task')
|
||||
.click()
|
||||
cy.get('.modal-mask .modal-container .modal-content .header')
|
||||
.should('contain', 'Delete this task')
|
||||
|
@ -264,7 +263,8 @@ describe('Task', () => {
|
|||
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('[data-cy="taskDetail.assign"]')
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Assign this task to a user')
|
||||
.click()
|
||||
cy.get('.task-view .column.assignees .multiselect input')
|
||||
.type(users[1].username)
|
||||
|
@ -317,7 +317,7 @@ describe('Task', () => {
|
|||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Add Labels')
|
||||
.contains('Add labels')
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.get('.task-view .details.labels-list .multiselect input')
|
||||
|
@ -344,7 +344,7 @@ describe('Task', () => {
|
|||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Add Labels')
|
||||
.contains('Add labels')
|
||||
.click()
|
||||
cy.get('.task-view .details.labels-list .multiselect input')
|
||||
.type(labels[0].title)
|
||||
|
@ -373,13 +373,13 @@ describe('Task', () => {
|
|||
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
|
||||
cy.get('.task-view .details.labels-list .multiselect .input-wrapper')
|
||||
.should('be.visible')
|
||||
.should('contain', labels[0].title)
|
||||
cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
|
||||
cy.get('.task-view .details.labels-list .multiselect .input-wrapper')
|
||||
.children()
|
||||
.first()
|
||||
.get('[data-cy="taskDetail.removeLabel"]')
|
||||
.get('a.delete')
|
||||
.click()
|
||||
|
||||
cy.get('.global-notification')
|
||||
|
@ -405,7 +405,7 @@ describe('Task', () => {
|
|||
cy.get('.datepicker .datepicker-popup a')
|
||||
.contains('Tomorrow')
|
||||
.click()
|
||||
cy.get('[data-cy="closeDatepicker"]')
|
||||
cy.get('.datepicker .datepicker-popup a.button')
|
||||
.contains('Confirm')
|
||||
.click()
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ const testAndAssertFailed = fixture => {
|
|||
|
||||
cy.wait(5000) // It can take waaaayy too long to log the user in
|
||||
cy.url().should('include', '/')
|
||||
cy.get('div.message.danger').contains('Wrong username or password.')
|
||||
cy.get('div.notification.is-danger').contains('Wrong username or password.')
|
||||
}
|
||||
|
||||
context('Login', () => {
|
||||
|
|
|
@ -6,7 +6,7 @@ describe('Log out', () => {
|
|||
|
||||
cy.get('.navbar .user .username')
|
||||
.click()
|
||||
cy.get('.navbar .user .dropdown-menu .dropdown-item')
|
||||
cy.get('.navbar .user .dropdown-menu a.dropdown-item')
|
||||
.contains('Logout')
|
||||
.click()
|
||||
|
||||
|
|
|
@ -24,14 +24,15 @@ context('Registration', () => {
|
|||
cy.visit('/register')
|
||||
cy.get('#username').type(fixture.username)
|
||||
cy.get('#email').type(fixture.email)
|
||||
cy.get('#password').type(fixture.password)
|
||||
cy.get('#password1').type(fixture.password)
|
||||
cy.get('#password2').type(fixture.password)
|
||||
cy.get('#register-submit').click()
|
||||
cy.url().should('include', '/')
|
||||
cy.clock(1625656161057) // 13:00
|
||||
cy.get('h2').should('contain', `Hi ${fixture.username}!`)
|
||||
})
|
||||
|
||||
it.only('Should fail', () => {
|
||||
it('Should fail', () => {
|
||||
const fixture = {
|
||||
username: 'test',
|
||||
password: '123456',
|
||||
|
@ -41,8 +42,9 @@ context('Registration', () => {
|
|||
cy.visit('/register')
|
||||
cy.get('#username').type(fixture.username)
|
||||
cy.get('#email').type(fixture.email)
|
||||
cy.get('#password').type(fixture.password)
|
||||
cy.get('#password1').type(fixture.password)
|
||||
cy.get('#password2').type(fixture.password)
|
||||
cy.get('#register-submit').click()
|
||||
cy.get('div.message.danger').contains('A user with this username already exists.')
|
||||
cy.get('div.notification.is-danger').contains('A user with this username already exists.')
|
||||
})
|
||||
})
|
|
@ -8,34 +8,31 @@ describe('User Settings', () => {
|
|||
})
|
||||
|
||||
it('Changes the user avatar', () => {
|
||||
cy.intercept(`${Cypress.env('API_URL')}/user/settings/avatar/upload`).as('uploadAvatar')
|
||||
|
||||
cy.visit('/user/settings/avatar')
|
||||
cy.visit('/user/settings')
|
||||
|
||||
cy.get('input[name=avatarProvider][value=upload]')
|
||||
.click()
|
||||
cy.get('input[type=file]', {timeout: 1000})
|
||||
.selectFile('cypress/fixtures/image.jpg', {force: true}) // The input is not visible, but on purpose
|
||||
cy.get('input[type=file]')
|
||||
.attachFile('image.jpg')
|
||||
cy.get('.vue-handler-wrapper.vue-handler-wrapper--south .vue-simple-handler.vue-simple-handler--south')
|
||||
.trigger('mousedown', {which: 1})
|
||||
.trigger('mousemove', {clientY: 100})
|
||||
.trigger('mouseup')
|
||||
cy.get('[data-cy="uploadAvatar"]')
|
||||
cy.get('a.button.is-primary')
|
||||
.contains('Upload Avatar')
|
||||
.click()
|
||||
|
||||
cy.wait('@uploadAvatar')
|
||||
cy.wait(3000) // Wait for the request to finish
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Updates the name', () => {
|
||||
cy.visit('/user/settings/general')
|
||||
cy.visit('/user/settings')
|
||||
|
||||
cy.get('.general-settings .control input.input')
|
||||
.first()
|
||||
cy.get('input#newName')
|
||||
.type('Lorem Ipsum')
|
||||
cy.get('[data-cy="saveGeneralSettings"]')
|
||||
cy.get('.card.general-settings .button.is-primary')
|
||||
.contains('Save')
|
||||
.click()
|
||||
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
|
||||
import './commands'
|
||||
import 'cypress-file-upload'
|
||||
import '@4tw/cypress-drag-drop'
|
||||
|
||||
// see https://github.com/cypress-io/cypress/issues/702#issuecomment-587127275
|
||||
Cypress.on('window:before:load', (win) => {
|
||||
// disable service workers
|
||||
// @ts-ignore
|
||||
delete win.navigator.__proto__.ServiceWorker
|
||||
})
|
|
@ -30,10 +30,7 @@
|
|||
// It has to be the full url, including the last /api/v1 part and port.
|
||||
// You can change this if your api is not reachable on the same port as the frontend.
|
||||
window.API_URL = 'http://localhost:3456/api/v1'
|
||||
// Enable error tracking with sentry. If this is set to true, will send anonymized data to
|
||||
// our sentry instance to notify us of potential problems.
|
||||
window.SENTRY_ENABLED = false
|
||||
window.SENTRY_DSN = 'https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480'
|
||||
//
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
15
netlify.toml
|
@ -1,15 +0,0 @@
|
|||
[build]
|
||||
command = "yarn build"
|
||||
publish = "dist-preview"
|
||||
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
status = 200
|
||||
|
||||
[[headers]]
|
||||
for = "/*"
|
||||
[headers.values]
|
||||
X-Frame-Options = "DENY"
|
||||
X-XSS-Protection = "1; mode=block"
|
||||
X-Robots-Tag = "noindex"
|
144
nginx.conf
|
@ -6,110 +6,78 @@ pid /var/run/nginx.pid;
|
|||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
types {
|
||||
application/manifest+json webmanifest;
|
||||
}
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
keepalive_timeout 65;
|
||||
gzip on;
|
||||
gzip_disable "msie6";
|
||||
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_min_length 256;
|
||||
gzip_types text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon audio/wav;
|
||||
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_min_length 256;
|
||||
gzip_types
|
||||
text/plain
|
||||
text/css
|
||||
application/json
|
||||
application/x-javascript
|
||||
application/javascript
|
||||
text/xml
|
||||
application/xml
|
||||
application/xml+rss
|
||||
text/javascript
|
||||
application/vnd.ms-fontobject
|
||||
application/x-font-ttf
|
||||
font/opentype
|
||||
image/svg+xml
|
||||
image/x-icon
|
||||
audio/wav;
|
||||
map_hash_max_size 128;
|
||||
map_hash_bucket_size 128;
|
||||
|
||||
map_hash_max_size 128;
|
||||
map_hash_bucket_size 128;
|
||||
# Expires map
|
||||
map $sent_http_content_type $expires {
|
||||
default off;
|
||||
text/html max;
|
||||
text/css max;
|
||||
application/javascript max;
|
||||
text/javascript max;
|
||||
application/vnd.ms-fontobject max;
|
||||
application/x-font-ttf max;
|
||||
font/opentype max;
|
||||
font/woff2 max;
|
||||
image/svg+xml max;
|
||||
image/x-icon max;
|
||||
audio/wav max;
|
||||
~image/ max;
|
||||
~font/ max;
|
||||
}
|
||||
|
||||
# Expires map
|
||||
map $sent_http_content_type $expires {
|
||||
default off;
|
||||
text/css max;
|
||||
application/javascript max;
|
||||
text/javascript max;
|
||||
application/vnd.ms-fontobject max;
|
||||
application/x-font-ttf max;
|
||||
font/opentype max;
|
||||
font/woff2 max;
|
||||
image/svg+xml max;
|
||||
image/x-icon max;
|
||||
audio/wav max;
|
||||
~images/ max;
|
||||
~font/ max;
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
listen 81 default_server http2 proxy_protocol; ## Needed when behind HAProxy with SSL termination + HTTP/2 support
|
||||
listen 443 default_server ssl http2;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen 81 default_server http2 proxy_protocol; ## Needed when behind HAProxy with SSL termination + HTTP/2 support
|
||||
server_name _;
|
||||
|
||||
server_name _;
|
||||
expires $expires;
|
||||
|
||||
expires $expires;
|
||||
ssl_certificate /etc/nginx/ssl/dummy.crt;
|
||||
ssl_certificate_key /etc/nginx/ssl/dummy.key;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri $uri/ /;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
# all assets contain hash in filename, cache forever
|
||||
location ^~ /assets/ {
|
||||
add_header Cache-Control "public, max-age=31536000, s-maxage=31536000, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# all workbox scripts are compiled with hash in filename, cache forever3
|
||||
location ^~ /workbox- {
|
||||
add_header Cache-Control "public, max-age=31536000, s-maxage=31536000, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# assume that everything else is handled by the application router, by injecting the index.html.
|
||||
location / {
|
||||
autoindex off;
|
||||
expires off;
|
||||
add_header Cache-Control "public, max-age=0, s-maxage=0, must-revalidate" always;
|
||||
try_files $uri /index.html =404;
|
||||
}
|
||||
|
||||
location ~* .(txt|webmanifest|css|js|mjs|map|svg|jpg|jpeg|png|ico|ttf|woff|woff2|wav)$ {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
}
|
||||
}
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
inkscape:label="ink_ext_XXXXXX 1"
|
||||
style="display:inline"
|
||||
transform="translate(-92.67749,-674.48297)"><circle
|
||||
style="fill:#196aff;fill-opacity:1;stroke:none;stroke-width:2.88757133;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
style="fill:#5974d9;fill-opacity:1;stroke:none;stroke-width:2.88757133;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="path920"
|
||||
cx="242.67749"
|
||||
cy="828.77881"
|
||||
|
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
180
package.json
|
@ -5,106 +5,89 @@
|
|||
"scripts": {
|
||||
"serve": "vite",
|
||||
"serve:dist-dev": "node scripts/serve-dist.js",
|
||||
"serve:dist": "vite preview --port 4173",
|
||||
"serve:dist": "vite preview",
|
||||
"build": "vite build && workbox copyLibraries dist/",
|
||||
"build:modern-only": "BUILD_MODERN_ONLY=true vite build && workbox copyLibraries dist/",
|
||||
"build:dev": "vite build -m development --outDir dist-dev/",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
"lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts",
|
||||
"cypress:open": "cypress open",
|
||||
"test:unit": "vitest run",
|
||||
"test:unit-watch": "vitest watch",
|
||||
"test:frontend": "cypress run",
|
||||
"browserslist:update": "npx browserslist@latest --update-db"
|
||||
"test:unit": "jest",
|
||||
"test:frontend": "cypress run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@github/hotkey": "2.0.0",
|
||||
"@kyvg/vue3-notification": "2.3.4",
|
||||
"@sentry/tracing": "6.19.7",
|
||||
"@sentry/vue": "6.19.7",
|
||||
"@types/is-touch-device": "1.0.0",
|
||||
"@types/sortablejs": "1.13.0",
|
||||
"@vue/compat": "3.2.31",
|
||||
"@vueuse/core": "8.4.2",
|
||||
"@vueuse/router": "8.4.2",
|
||||
"blurhash": "1.1.5",
|
||||
"bulma-css-variables": "0.9.33",
|
||||
"browserslist": "4.17.1",
|
||||
"bulma": "0.9.3",
|
||||
"@capacitor/android": "2.4.5",
|
||||
"@capacitor/cli": "2.4.5",
|
||||
"@capacitor/core": "2.4.5",
|
||||
"camel-case": "4.1.2",
|
||||
"codemirror": "5.65.3",
|
||||
"date-fns": "2.28.0",
|
||||
"dompurify": "2.3.6",
|
||||
"easymde": "2.16.1",
|
||||
"flatpickr": "4.6.13",
|
||||
"flexsearch": "0.7.21",
|
||||
"highlight.js": "11.5.1",
|
||||
"copy-to-clipboard": "3.3.1",
|
||||
"date-fns": "2.24.0",
|
||||
"dompurify": "2.3.3",
|
||||
"highlight.js": "11.2.0",
|
||||
"is-touch-device": "1.0.1",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"marked": "4.0.15",
|
||||
"minimist": "1.2.6",
|
||||
"marked": "3.0.4",
|
||||
"register-service-worker": "1.7.2",
|
||||
"snake-case": "3.0.4",
|
||||
"ufo": "0.8.4",
|
||||
"v-tooltip": "4.0.0-beta.17",
|
||||
"vue": "3.2.33",
|
||||
"vue-advanced-cropper": "2.8.1",
|
||||
"vue-drag-resize": "2.0.3",
|
||||
"vue-flatpickr-component": "9.0.6",
|
||||
"vue-i18n": "9.2.0-beta.30",
|
||||
"vue-router": "4.0.15",
|
||||
"vuedraggable": "4.1.0",
|
||||
"vuex": "4.0.2",
|
||||
"workbox-precaching": "6.5.3"
|
||||
"ufo": "0.7.9",
|
||||
"verte": "0.0.12",
|
||||
"vue": "2.6.14",
|
||||
"vue-advanced-cropper": "1.8.2",
|
||||
"vue-drag-resize": "1.5.4",
|
||||
"vue-easymde": "1.4.0",
|
||||
"vue-i18n": "8.26.3",
|
||||
"vue-shortkey": "3.1.7",
|
||||
"vuedraggable": "2.24.3",
|
||||
"vuex": "3.6.2",
|
||||
"workbox-precaching": "6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@4tw/cypress-drag-drop": "2.1.0",
|
||||
"@faker-js/faker": "6.3.1",
|
||||
"@fortawesome/fontawesome-svg-core": "6.1.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.1.1",
|
||||
"@fortawesome/vue-fontawesome": "3.0.0-5",
|
||||
"@types/flexsearch": "0.7.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.23.0",
|
||||
"@typescript-eslint/parser": "5.23.0",
|
||||
"@vitejs/plugin-legacy": "1.8.2",
|
||||
"@vitejs/plugin-vue": "2.3.2",
|
||||
"@vue/eslint-config-typescript": "10.0.0",
|
||||
"autoprefixer": "10.4.7",
|
||||
"axios": "0.27.2",
|
||||
"browserslist": "4.20.3",
|
||||
"caniuse-lite": "1.0.30001338",
|
||||
"cypress": "9.6.1",
|
||||
"esbuild": "0.14.38",
|
||||
"eslint": "8.15.0",
|
||||
"eslint-plugin-vue": "8.7.1",
|
||||
"express": "4.18.1",
|
||||
"happy-dom": "3.1.1",
|
||||
"netlify-cli": "10.3.0",
|
||||
"postcss": "8.4.13",
|
||||
"postcss-preset-env": "7.5.0",
|
||||
"rollup": "2.72.1",
|
||||
"rollup-plugin-visualizer": "5.6.0",
|
||||
"sass": "1.51.0",
|
||||
"typescript": "4.6.4",
|
||||
"vite": "2.9.8",
|
||||
"vite-plugin-pwa": "0.12.0",
|
||||
"vite-svg-loader": "3.3.0",
|
||||
"vitest": "0.10.5",
|
||||
"vue-tsc": "0.34.12",
|
||||
"wait-on": "6.0.1",
|
||||
"workbox-cli": "6.5.3"
|
||||
"@4tw/cypress-drag-drop": "2.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "2.0.2",
|
||||
"@types/jest": "27.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "4.32.0",
|
||||
"@typescript-eslint/parser": "4.32.0",
|
||||
"@vue/babel-preset-app": "4.5.13",
|
||||
"@vue/eslint-config-typescript": "7.0.0",
|
||||
"@vue/runtime-dom": "latest",
|
||||
"autoprefixer": "10.3.6",
|
||||
"axios": "0.21.4",
|
||||
"babel-eslint": "10.1.0",
|
||||
"cypress": "8.5.0",
|
||||
"cypress-file-upload": "5.0.8",
|
||||
"esbuild": "0.13.3",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-plugin-vue": "7.18.0",
|
||||
"express": "4.17.1",
|
||||
"faker": "5.5.3",
|
||||
"jest": "27.2.4",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-visualizer": "5.5.2",
|
||||
"sass": "1.42.1",
|
||||
"ts-jest": "27.0.5",
|
||||
"typescript": "4.4.3",
|
||||
"vite": "2.6.1",
|
||||
"vite-plugin-pwa": "0.11.2",
|
||||
"vite-plugin-vue2": "1.8.2",
|
||||
"vue-flatpickr-component": "8.1.7",
|
||||
"vue-notification": "1.3.20",
|
||||
"vue-router": "3.5.2",
|
||||
"vue-template-compiler": "2.6.14",
|
||||
"wait-on": "6.0.0",
|
||||
"workbox-cli": "6.3.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true,
|
||||
"vue/setup-compiler-macros": true
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/vue3-essential",
|
||||
"plugin:vue/essential",
|
||||
"@vue/typescript"
|
||||
],
|
||||
"rules": {
|
||||
|
@ -123,28 +106,47 @@
|
|||
"semi": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"vue/script-setup-uses-vars": "error",
|
||||
"vue/multi-word-component-names": 0
|
||||
]
|
||||
},
|
||||
"parser": "vue-eslint-parser",
|
||||
"parserOptions": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"ecmaVersion": 2022
|
||||
"ecmaVersion": 2021
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"*.test.*",
|
||||
"cypress/*"
|
||||
],
|
||||
"globals": {
|
||||
"defineProps": "readonly"
|
||||
}
|
||||
]
|
||||
},
|
||||
"postcss": {
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
}
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"packageManager": "yarn@1.22.18"
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie > 0",
|
||||
"not dead",
|
||||
"Firefox ESR"
|
||||
],
|
||||
"jest": {
|
||||
"testPathIgnorePatterns": [
|
||||
"cypress"
|
||||
],
|
||||
"testEnvironment": "jsdom",
|
||||
"preset": "ts-jest",
|
||||
"roots": [
|
||||
"<rootDir>/src"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.(js|tsx?)$": "ts-jest"
|
||||
},
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"js",
|
||||
"json"
|
||||
]
|
||||
},
|
||||
"license": "AGPL-3.0-or-later"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# Shell script because yaml doesn't understand the header is a string literal and not a yaml symbol
|
||||
|
||||
curl -d operation=pull -H "Authorization: Token $WEBLATE_TOKEN" https://hosted.weblate.org/api/projects/vikunja/repository/
|
||||
curl -d operation=push -H "Authorization: Token $WEBLATE_TOKEN" https://hosted.weblate.org/api/projects/vikunja/repository/
|
|
@ -3,17 +3,5 @@
|
|||
"labels": ["dependencies"],
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackageNames": ["netlify-cli", "caniuse-lite"],
|
||||
"extends": ["schedule:weekly"]
|
||||
},
|
||||
{
|
||||
"groupName": "vueuse",
|
||||
"matchPackagePrefixes": [
|
||||
"@vueuse/"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
5
run.sh
|
@ -3,8 +3,7 @@
|
|||
# This shell script sets the api url based on an environment variable and starts nginx in foreground.
|
||||
|
||||
VIKUNJA_API_URL="${VIKUNJA_API_URL:-"/api/v1"}"
|
||||
VIKUNJA_SENTRY_ENABLED="${VIKUNJA_SENTRY_ENABLED:-"false"}"
|
||||
VIKUNJA_SENTRY_DSN="${VIKUNJA_SENTRY_DSN:-"https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480"}"
|
||||
|
||||
VIKUNJA_HTTP_PORT="${VIKUNJA_HTTP_PORT:-80}"
|
||||
VIKUNJA_HTTPS_PORT="${VIKUNJA_HTTPS_PORT:-443}"
|
||||
|
||||
|
@ -15,8 +14,6 @@ VIKUNJA_API_URL=$(echo $VIKUNJA_API_URL |sed 's/\//\\\//g')
|
|||
|
||||
sed -i "s/http\:\/\/localhost\:3456//g" /usr/share/nginx/html/index.html # replacing in two steps to make sure api urls from releases are properly replaced as well
|
||||
sed -i "s/'\/api\/v1/'$VIKUNJA_API_URL/g" /usr/share/nginx/html/index.html
|
||||
sed -i "s/\.SENTRY_ENABLED = false/\.SENTRY_ENABLED = $VIKUNJA_SENTRY_ENABLED/g" /usr/share/nginx/html/index.html
|
||||
sed -i "s/\.SENTRY_DSN = '.*'/\.SENTRY_DSN = '$VIKUNJA_SENTRY_DSN'/g" /usr/share/nginx/html/index.html
|
||||
|
||||
sed -i "s/listen 80/listen $VIKUNJA_HTTP_PORT/g" /etc/nginx/nginx.conf
|
||||
sed -i "s/listen 443/listen $VIKUNJA_HTTPS_PORT/g" /etc/nginx/nginx.conf
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
const {exec} = require('child_process')
|
||||
const axios = require('axios')
|
||||
|
||||
const BOT_USER_ID = 513
|
||||
const giteaToken = process.env.GITEA_TOKEN
|
||||
const siteId = process.env.NETLIFY_SITE_ID
|
||||
const branchSlug = String(process.env.DRONE_SOURCE_BRANCH)
|
||||
.trim()
|
||||
.normalize('NFKD')
|
||||
.toLowerCase()
|
||||
.replace(/[.\s/]/g, '-')
|
||||
.replace(/[^A-Za-z\d-]/g, '')
|
||||
const prNumber = process.env.DRONE_PULL_REQUEST
|
||||
|
||||
const prIssueCommentsUrl = `https://kolaente.dev/api/v1/repos/vikunja/frontend/issues/${prNumber}/comments`
|
||||
const alias = `${prNumber}-${branchSlug}`.substring(0,37)
|
||||
const fullPreviewUrl = `https://${alias}--vikunja-frontend-preview.netlify.app`
|
||||
|
||||
const promiseExec = cmd => {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(cmd, (error, stdout) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
|
||||
resolve(stdout)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
(async function () {
|
||||
let stdout = await promiseExec(`./node_modules/.bin/netlify link --id ${siteId}`)
|
||||
console.log(stdout)
|
||||
stdout = await promiseExec(`./node_modules/.bin/netlify deploy --alias ${alias}`)
|
||||
console.log(stdout)
|
||||
|
||||
const {data} = await axios.get(prIssueCommentsUrl)
|
||||
const hasComment = data.some(c => c.user.id === BOT_USER_ID)
|
||||
|
||||
if (hasComment) {
|
||||
console.log(`PR #${prNumber} already has a comment with a link, not sending another comment.`)
|
||||
return
|
||||
}
|
||||
|
||||
await axios.post(prIssueCommentsUrl, {
|
||||
body: `
|
||||
Hi ${process.env.DRONE_COMMIT_AUTHOR}!
|
||||
|
||||
Thank you for creating a PR!
|
||||
|
||||
I've deployed the changes of this PR on a preview environment under this URL: ${fullPreviewUrl}
|
||||
|
||||
You can use this url to view the changes live and test them out.
|
||||
You will need to manually connect this to an api running somehwere. The easiest to use is https://try.vikunja.io/.
|
||||
|
||||
Have a nice day!
|
||||
|
||||
> Beep boop, I'm a bot.
|
||||
`,
|
||||
}, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'accept': 'application/json',
|
||||
'Authorization': `token ${giteaToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
console.log(`Preview comment sent successfully to PR #${prNumber}!`)
|
||||
})()
|
|
@ -1 +0,0 @@
|
|||
bb46342a0a08105b340ba7976cff9d80ef89901120ec0639669caa70bb7d2dbc43e78b1f635a7654ab2456e8358c98a4 ./scripts/deploy-preview-netlify.js
|
|
@ -3,7 +3,7 @@ const express = require('express')
|
|||
const app = express()
|
||||
|
||||
const p = path.join(__dirname, '..', 'dist-dev')
|
||||
const port = 4173
|
||||
const port = 5000
|
||||
|
||||
app.use(express.static(p))
|
||||
// Handle urls set by the frontend
|
||||
|
|