feat: implement tags folder as experimental feature (#788)

* feat: add tag folders support basics

* feat: add draggability to tags

* feat: add drag & drop draft

* feat: fold folders

* fix: do not select on fold / unfold tags

* style: clean the isValidTag call

* feat: add native folder toggle

* feat: add touch mobile support

* ui: add nicer design & icons

* style: render full-width tag items

* feat: nicer looking dropzone

* style: fix arguments

* fix: tag template rendering in list items

* feat: tag can be dragged over the whole item

* fix: cancel / reset title after save

* fix: disable drag completely when needed

* fix: invalid tag parents

* feat: add paying feature

* feat: with paid feature tooltip

* feat: tag has a plus feature

* feat: add premium modal

* style: simplif code

* refactor: extract feature_state & simplif code

* fix: icons and icons svg

* style: remove comment

* feat: tag folders naming

* feat: use the feature notification

* fix: tag folders copy

* style: variable names

* style: remove & clean comments

* refactor: remove is-mobile library

* feat: tags folder experimental (#10)

* feat: hide native folders behind experimental flag

* fix: better tags resizing

* fix: merge global window

* style: rename params

* refactor: remove level of indirection in feature toggle

* feat: recursively add tags to note on create (#9)

* fix: use add tags folder hierarchy & isTemplateItem (#13)

* fix: use new snjs add tag hierarchy

* fix: use new snjs isTemplateItem

* feat: tags folder premium (#774)

* feat: upgrade premium in tags section

refactor: move TagsSection to react

feat: show premium on Tag section

feat: keep drag and drop features always active

fix: drag & drop tweak with premium

feat: premium messages

fix: remove fill in svg icons

fix: change tag list color (temporary)

style: remove dead code

refactor: clarify names and modules

fix: draggable behind feature toggle

feat: add button in TagSection & design

* feat: fix features loading with app state (#775)

* fix: distinguish between app launch and start

* fix: update state for footer too

* fix: wait for application launch event

Co-authored-by: Laurent Senta <laurent@singulargarden.com>

* feat: tags folder with folder text design (#776)

* feat: add folder text

* fix: sn stylekit colors

* fix: root drop zone

* chore: upgrade stylekit

* fix: hide dropzone when feature is disabled

* chore: bump versions now that they are released

Co-authored-by: Mo <me@bitar.io>

* feat: tags folder design review (#785)

* fix: upgrade design after review

* fix: tweak dropzone

* fix: sync after assign parent

* fix: tsc error on build

* fix: vertical center the fold arrows

* fix: define our own hoist for react-dnd

* feat: hide fold when there are no folders

* fix: show children usability + resize UI

* fix: use old colors for now, theme compat

* fix: tweak alignment and add title

* fix: meta offset with folders

* fix: tweak tag size

* fix: observable setup

* fix: use link-off icon on dropzone

* fix: more tweak on text sizes

Co-authored-by: Mo <me@bitar.io>
This commit is contained in:
Laurent Senta
2021-12-23 14:34:02 +01:00
committed by GitHub
parent 4a5a202a37
commit 237cd91acd
33 changed files with 1048 additions and 221 deletions

View File

@@ -0,0 +1,153 @@
import { Platform, platformFromString } from '@standardnotes/snjs';
import { IsDesktopPlatform, IsWebPlatform } from '@/version';
import { EMAIL_REGEX } from '@Views/constants';
export { isMobile } from './isMobile';
declare const process: {
env: {
NODE_ENV: string | null | undefined;
};
};
export const isDev = process.env.NODE_ENV === 'development';
export function getPlatformString() {
try {
const platform = navigator.platform.toLowerCase();
let trimmed = '';
if (platform.includes('mac')) {
trimmed = 'mac';
} else if (platform.includes('win')) {
trimmed = 'windows';
} else if (platform.includes('linux')) {
trimmed = 'linux';
} else {
/** Treat other platforms as linux */
trimmed = 'linux';
}
return trimmed + (isDesktopApplication() ? '-desktop' : '-web');
} catch (e) {
return 'linux-web';
}
}
export function getPlatform(): Platform {
return platformFromString(getPlatformString());
}
export function isSameDay(dateA: Date, dateB: Date): boolean {
return (
dateA.getFullYear() === dateB.getFullYear() &&
dateA.getMonth() === dateB.getMonth() &&
dateA.getDate() === dateB.getDate()
);
}
/** Via https://davidwalsh.name/javascript-debounce-function */
export function debounce(
this: any,
func: any,
wait: number,
immediate = false
) {
let timeout: any;
return () => {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const context = this;
// eslint-disable-next-line prefer-rest-params
const args = arguments;
const later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) {
// eslint-disable-next-line no-extend-native
Object.defineProperty(Array.prototype, 'includes', {
value: function (searchElement: any, fromIndex: number) {
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
// 1. Let O be ? ToObject(this value).
const o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
const len = o.length >>> 0;
// 3. If len is 0, return false.
if (len === 0) {
return false;
}
// 4. Let n be ? ToInteger(fromIndex).
// (If fromIndex is undefined, this step produces the value 0.)
const n = fromIndex | 0;
// 5. If n ≥ 0, then
// a. Let k be n.
// 6. Else n < 0,
// a. Let k be len + n.
// b. If k < 0, let k be 0.
let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
function sameValueZero(x: number, y: number) {
return (
x === y ||
(typeof x === 'number' &&
typeof y === 'number' &&
isNaN(x) &&
isNaN(y))
);
}
// 7. Repeat, while k < len
while (k < len) {
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
// b. If SameValueZero(searchElement, elementK) is true, return true.
if (sameValueZero(o[k], searchElement)) {
return true;
}
// c. Increase k by 1.
k++;
}
// 8. Return false
return false;
},
});
}
export async function preventRefreshing(
message: string,
operation: () => Promise<void> | void
) {
const onBeforeUnload = window.onbeforeunload;
try {
window.onbeforeunload = () => message;
await operation();
} finally {
window.onbeforeunload = onBeforeUnload;
}
}
if (!IsWebPlatform && !IsDesktopPlatform) {
throw Error(
'Neither __WEB__ nor __DESKTOP__ is true. Check your configuration files.'
);
}
export function isDesktopApplication() {
return IsDesktopPlatform;
}
export const isEmailValid = (email: string): boolean => {
return EMAIL_REGEX.test(email);
};

View File

@@ -0,0 +1,28 @@
/**
* source: https://github.com/juliangruber/is-mobile
*
* (MIT)
* Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
const mobileRE =
/(android|bb\d+|meego).+mobile|armv7l|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series[46]0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i;
const tabletRE = /android|ipad|playbook|silk/i;
export type Opts = {
tablet?: boolean;
};
export const isMobile = (opts: Opts = {}) => {
const ua = navigator.userAgent || navigator.vendor;
if (typeof ua !== 'string') {
return false;
}
return mobileRE.test(ua) || (!!opts.tablet && tabletRE.test(ua));
};