diff --git a/package.json b/package.json index 52ded14d36..0107c44119 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "ignore": "^5.1.8", "is-svg": "4.2.2", "jszip": "^3.5.0", - "mldoc": "0.6.16", + "mldoc": "0.6.18", "path": "^0.12.7", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/resources/css/common.css b/resources/css/common.css index c8bc6cd40a..b2f775f6e8 100644 --- a/resources/css/common.css +++ b/resources/css/common.css @@ -4,7 +4,7 @@ --ls-page-text-size: 1em; --ls-page-title-size: 36px; --ls-font-family: 'Inter'; - --ls-main-content-max-width: 700px; + --ls-main-content-max-width: 740px; --ls-border-radius-low: 4px; --ls-border-radius-medium: 8px; } @@ -35,6 +35,7 @@ html[data-theme='dark'] { --ls-active-primary-color: #8ec2c2; --ls-active-secondary-color: #d0e8e8; --ls-block-properties-background-color: #06323e; + --ls-page-properties-background-color: #02171d; --ls-block-ref-link-text-color: #1a6376; --ls-search-background-color: linear-gradient( to right, @@ -93,6 +94,7 @@ html[data-theme='light'] { --ls-active-primary-color: rgb(4, 85, 145); --ls-active-secondary-color: #003761; --ls-block-properties-background-color: #f7f6f4; + --ls-page-properties-background-color: #eae7e1; --ls-block-ref-link-text-color: #d8e1e8; --ls-search-background-color: var(--ls-primary-background-color); --ls-border-color: #ccc; @@ -725,6 +727,11 @@ a.fade-link:hover { height: 20px; } +.svg-small svg { + transform: scale(0.6); + display: inline; +} + /* < > buttons */ a.navigation { @@ -757,6 +764,7 @@ mark { .block-ref { padding: 2px 5px; + display: inline; } .block-ref .block-ref { diff --git a/resources/css/tooltip.css b/resources/css/tooltip.css index bf582cd7d1..eb40338386 100644 --- a/resources/css/tooltip.css +++ b/resources/css/tooltip.css @@ -1,4 +1,4 @@ -.tippy-touch{cursor:pointer!important}.tippy-notransition{transition:none!important}.tippy-popper{max-width:800px;-webkit-perspective:800px;perspective:800px;z-index:9999;outline:0;transition-timing-function:cubic-bezier(.165,.84,.44,1);pointer-events:none}.tippy-popper.html-template{max-width:96%;max-width:calc(100% - 20px)}.tippy-popper[x-placement^=top] [x-arrow]{border-top:7px solid var(--ls-tertiary-background-color);border-right:7px solid transparent;border-left:7px solid transparent;bottom:-7px;margin:0 9px}.tippy-popper[x-placement^=top] [x-arrow].arrow-small{border-top:5px solid var(--ls-tertiary-background-color);border-right:5px solid transparent;border-left:5px solid transparent;bottom:-5px}.tippy-popper[x-placement^=top] [x-arrow].arrow-big{border-top:10px solid var(--ls-tertiary-background-color);border-right:10px solid transparent;border-left:10px solid transparent;bottom:-10px}.tippy-popper[x-placement^=top] [x-circle]{-webkit-transform-origin:0 33%;transform-origin:0 33%}.tippy-popper[x-placement^=top] [x-circle].enter{-webkit-transform:scale(1) translate(-50%,-55%);transform:scale(1) translate(-50%,-55%);opacity:1}.tippy-popper[x-placement^=top] [x-circle].leave{-webkit-transform:scale(.15) translate(-50%,-50%);transform:scale(.15) translate(-50%,-50%);opacity:0}.tippy-popper[x-placement^=top] .tippy-tooltip.light-theme [x-circle]{background-color:#fff}.tippy-popper[x-placement^=top] .tippy-tooltip.light-theme [x-arrow]{border-top:7px solid #fff;border-right:7px solid transparent;border-left:7px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.light-theme [x-arrow].arrow-small{border-top:5px solid #fff;border-right:5px solid transparent;border-left:5px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.light-theme [x-arrow].arrow-big{border-top:10px solid #fff;border-right:10px solid transparent;border-left:10px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-circle]{background-color:var(--ls-secondary-background-color)}.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-arrow]{border-top:7px solid var(--ls-secondary-background-color);border-right:7px solid transparent;border-left:7px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-arrow].arrow-small{border-top:5px solid var(--ls-secondary-background-color);border-right:5px solid transparent;border-left:5px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-arrow].arrow-big{border-top:10px solid var(--ls-secondary-background-color);border-right:10px solid transparent;border-left:10px solid transparent}.tippy-popper[x-placement^=top] [data-animation=perspective]{-webkit-transform-origin:bottom;transform-origin:bottom}.tippy-popper[x-placement^=top] [data-animation=perspective].enter{opacity:1;-webkit-transform:translateY(-10px) rotateX(0);transform:translateY(-10px) rotateX(0)}.tippy-popper[x-placement^=top] [data-animation=perspective].leave{opacity:0;-webkit-transform:translateY(0) rotateX(90deg);transform:translateY(0) rotateX(90deg)}.tippy-popper[x-placement^=top] [data-animation=fade].enter{opacity:1;-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=fade].leave{opacity:0;-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift].enter{opacity:1;-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift].leave{opacity:0;-webkit-transform:translateY(0);transform:translateY(0)}.tippy-popper[x-placement^=top] [data-animation=scale].enter{opacity:1;-webkit-transform:translateY(-10px) scale(1);transform:translateY(-10px) scale(1)}.tippy-popper[x-placement^=top] [data-animation=scale].leave{opacity:0;-webkit-transform:translateY(0) scale(0);transform:translateY(0) scale(0)}.tippy-popper[x-placement^=bottom] [x-arrow]{border-bottom:7px solid var(--ls-tertiary-background-color);border-right:7px solid transparent;border-left:7px solid transparent;top:-7px;margin:0 9px}.tippy-popper[x-placement^=bottom] [x-arrow].arrow-small{border-bottom:5px solid var(--ls-tertiary-background-color);border-right:5px solid transparent;border-left:5px solid transparent;top:-5px}.tippy-popper[x-placement^=bottom] [x-arrow].arrow-big{border-bottom:10px solid var(--ls-tertiary-background-color);border-right:10px solid transparent;border-left:10px solid transparent;top:-10px}.tippy-popper[x-placement^=bottom] [x-circle]{-webkit-transform-origin:0 -50%;transform-origin:0 -50%}.tippy-popper[x-placement^=bottom] [x-circle].enter{-webkit-transform:scale(1) translate(-50%,-45%);transform:scale(1) translate(-50%,-45%);opacity:1}.tippy-popper[x-placement^=bottom] [x-circle].leave{-webkit-transform:scale(.15) translate(-50%,-5%);transform:scale(.15) translate(-50%,-5%);opacity:0}.tippy-popper[x-placement^=bottom] .tippy-tooltip.light-theme [x-circle]{background-color:#fff}.tippy-popper[x-placement^=bottom] .tippy-tooltip.light-theme [x-arrow]{border-bottom:7px solid #fff;border-right:7px solid transparent;border-left:7px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.light-theme [x-arrow].arrow-small{border-bottom:5px solid #fff;border-right:5px solid transparent;border-left:5px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.light-theme [x-arrow].arrow-big{border-bottom:10px solid #fff;border-right:10px solid transparent;border-left:10px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-circle]{background-color:var(--ls-secondary-background-color)}.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-arrow]{border-bottom:7px solid var(--ls-secondary-background-color);border-right:7px solid transparent;border-left:7px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-arrow].arrow-small{border-bottom:5px solid var(--ls-secondary-background-color);border-right:5px solid transparent;border-left:5px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-arrow].arrow-big{border-bottom:10px solid var(--ls-secondary-background-color);border-right:10px solid transparent;border-left:10px solid transparent}.tippy-popper[x-placement^=bottom] [data-animation=perspective]{-webkit-transform-origin:top;transform-origin:top}.tippy-popper[x-placement^=bottom] [data-animation=perspective].enter{opacity:1;-webkit-transform:translateY(10px) rotateX(0);transform:translateY(10px) rotateX(0)}.tippy-popper[x-placement^=bottom] [data-animation=perspective].leave{opacity:0;-webkit-transform:translateY(0) rotateX(-90deg);transform:translateY(0) rotateX(-90deg)}.tippy-popper[x-placement^=bottom] [data-animation=fade].enter{opacity:1;-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=fade].leave{opacity:0;-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift].enter{opacity:1;-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift].leave{opacity:0;-webkit-transform:translateY(0);transform:translateY(0)}.tippy-popper[x-placement^=bottom] [data-animation=scale].enter{opacity:1;-webkit-transform:translateY(10px) scale(1);transform:translateY(10px) scale(1)}.tippy-popper[x-placement^=bottom] [data-animation=scale].leave{opacity:0;-webkit-transform:translateY(0) scale(0);transform:translateY(0) scale(0)}.tippy-popper[x-placement^=left] [x-arrow]{border-left:7px solid var(--ls-tertiary-background-color);border-top:7px solid transparent;border-bottom:7px solid transparent;right:-7px;margin:6px 0}.tippy-popper[x-placement^=left] [x-arrow].arrow-small{border-left:5px solid var(--ls-tertiary-background-color);border-top:5px solid transparent;border-bottom:5px solid transparent;right:-5px}.tippy-popper[x-placement^=left] [x-arrow].arrow-big{border-left:10px solid var(--ls-tertiary-background-color);border-top:10px solid transparent;border-bottom:10px solid transparent;right:-10px}.tippy-popper[x-placement^=left] [x-circle]{-webkit-transform-origin:50% 0;transform-origin:50% 0}.tippy-popper[x-placement^=left] [x-circle].enter{-webkit-transform:scale(1) translate(-50%,-50%);transform:scale(1) translate(-50%,-50%);opacity:1}.tippy-popper[x-placement^=left] [x-circle].leave{-webkit-transform:scale(.15) translate(-50%,-50%);transform:scale(.15) translate(-50%,-50%);opacity:0}.tippy-popper[x-placement^=left] .tippy-tooltip.light-theme [x-circle]{background-color:#fff}.tippy-popper[x-placement^=left] .tippy-tooltip.light-theme [x-arrow]{border-left:7px solid #fff;border-top:7px solid transparent;border-bottom:7px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.light-theme [x-arrow].arrow-small{border-left:5px solid #fff;border-top:5px solid transparent;border-bottom:5px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.light-theme [x-arrow].arrow-big{border-left:10px solid #fff;border-top:10px solid transparent;border-bottom:10px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-circle]{background-color:var(--ls-secondary-background-color)}.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-arrow]{border-left:7px solid var(--ls-secondary-background-color);border-top:7px solid transparent;border-bottom:7px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-arrow].arrow-small{border-left:5px solid var(--ls-secondary-background-color);border-top:5px solid transparent;border-bottom:5px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-arrow].arrow-big{border-left:10px solid var(--ls-secondary-background-color);border-top:10px solid transparent;border-bottom:10px solid transparent}.tippy-popper[x-placement^=left] [data-animation=perspective]{-webkit-transform-origin:right;transform-origin:right}.tippy-popper[x-placement^=left] [data-animation=perspective].enter{opacity:1;-webkit-transform:translateX(-10px) rotateY(0);transform:translateX(-10px) rotateY(0)}.tippy-popper[x-placement^=left] [data-animation=perspective].leave{opacity:0;-webkit-transform:translateX(0) rotateY(-90deg);transform:translateX(0) rotateY(-90deg)}.tippy-popper[x-placement^=left] [data-animation=fade].enter{opacity:1;-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=fade].leave{opacity:0;-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift].enter{opacity:1;-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift].leave{opacity:0;-webkit-transform:translateX(0);transform:translateX(0)}.tippy-popper[x-placement^=left] [data-animation=scale].enter{opacity:1;-webkit-transform:translateX(-10px) scale(1);transform:translateX(-10px) scale(1)}.tippy-popper[x-placement^=left] [data-animation=scale].leave{opacity:0;-webkit-transform:translateX(0) scale(0);transform:translateX(0) scale(0)}.tippy-popper[x-placement^=right] [x-arrow]{border-right:7px solid var(--ls-tertiary-background-color);border-top:7px solid transparent;border-bottom:7px solid transparent;left:-7px;margin:6px 0}.tippy-popper[x-placement^=right] [x-arrow].arrow-small{border-right:5px solid var(--ls-tertiary-background-color);border-top:5px solid transparent;border-bottom:5px solid transparent;left:-5px}.tippy-popper[x-placement^=right] [x-arrow].arrow-big{border-right:10px solid var(--ls-tertiary-background-color);border-top:10px solid transparent;border-bottom:10px solid transparent;left:-10px}.tippy-popper[x-placement^=right] [x-circle]{-webkit-transform-origin:-50% 0;transform-origin:-50% 0}.tippy-popper[x-placement^=right] [x-circle].enter{-webkit-transform:scale(1) translate(-50%,-50%);transform:scale(1) translate(-50%,-50%);opacity:1}.tippy-popper[x-placement^=right] [x-circle].leave{-webkit-transform:scale(.15) translate(-50%,-50%);transform:scale(.15) translate(-50%,-50%);opacity:0}.tippy-popper[x-placement^=right] .tippy-tooltip.light-theme [x-circle]{background-color:#fff}.tippy-popper[x-placement^=right] .tippy-tooltip.light-theme [x-arrow]{border-right:7px solid #fff;border-top:7px solid transparent;border-bottom:7px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.light-theme [x-arrow].arrow-small{border-right:5px solid #fff;border-top:5px solid transparent;border-bottom:5px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.light-theme [x-arrow].arrow-big{border-right:10px solid #fff;border-top:10px solid transparent;border-bottom:10px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-circle]{background-color:var(--ls-secondary-background-color)}.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-arrow]{border-right:7px solid var(--ls-secondary-background-color);border-top:7px solid transparent;border-bottom:7px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-arrow].arrow-small{border-right:5px solid var(--ls-secondary-background-color);border-top:5px solid transparent;border-bottom:5px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-arrow].arrow-big{border-right:10px solid var(--ls-secondary-background-color);border-top:10px solid transparent;border-bottom:10px solid transparent}.tippy-popper[x-placement^=right] [data-animation=perspective]{-webkit-transform-origin:left;transform-origin:left}.tippy-popper[x-placement^=right] [data-animation=perspective].enter{opacity:1;-webkit-transform:translateX(10px) rotateY(0);transform:translateX(10px) rotateY(0)}.tippy-popper[x-placement^=right] [data-animation=perspective].leave{opacity:0;-webkit-transform:translateX(0) rotateY(90deg);transform:translateX(0) rotateY(90deg)}.tippy-popper[x-placement^=right] [data-animation=fade].enter{opacity:1;-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=fade].leave{opacity:0;-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift].enter{opacity:1;-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift].leave{opacity:0;-webkit-transform:translateX(0);transform:translateX(0)}.tippy-popper[x-placement^=right] [data-animation=scale].enter{opacity:1;-webkit-transform:translateX(10px) scale(1);transform:translateX(10px) scale(1)}.tippy-popper[x-placement^=right] [data-animation=scale].leave{opacity:0;-webkit-transform:translateX(0) scale(0);transform:translateX(0) scale(0)}.tippy-popper .tippy-tooltip.transparent-theme{background-color:var(--ls-secondary-background-color)}.tippy-popper .tippy-tooltip.transparent-theme[data-animatefill]{background-color:transparent}.tippy-popper .tippy-tooltip.light-theme{color:#26323d;box-shadow:0 4px 20px 4px rgba(0,20,60,.1),0 4px 80px -8px rgba(0,20,60,.2);background-color:#fff}.tippy-popper .tippy-tooltip.light-theme[data-animatefill]{background-color:transparent}.tippy-tooltip{position:relative;color:#fff;border-radius:4px;font-size:.95rem;padding:.4rem .8rem;text-align:center;will-change:transform;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:var(--ls-tertiary-background-color)}.tippy-tooltip--small{padding:.25rem .5rem;font-size:.8rem}.tippy-tooltip--big{padding:.6rem 1.2rem;font-size:1.2rem}.tippy-tooltip[data-animatefill]{overflow:hidden;background-color:transparent}.tippy-tooltip[data-interactive]{pointer-events:auto}.tippy-tooltip[data-inertia]{transition-timing-function:cubic-bezier(.53,2,.36,.85)}.tippy-tooltip [x-arrow]{position:absolute;width:0;height:0}.tippy-tooltip [x-circle]{position:absolute;will-change:transform;background-color:var(--ls-tertiary-background-color);border-radius:50%;width:130%;width:calc(110% + 2rem);left:50%;top:50%;z-index:-1;overflow:hidden;transition:all ease}.tippy-tooltip [x-circle]:before{content:"";padding-top:90%;float:left}@media (max-width:450px){.tippy-popper{max-width:96%;max-width:calc(100% - 20px)}} +.tippy-touch{cursor:pointer!important}.tippy-notransition{transition:none!important}.tippy-popper{max-width:800px;-webkit-perspective:800px;perspective:800px;z-index:9999;outline:0;transition-timing-function:cubic-bezier(.165,.84,.44,1);pointer-events:none}.tippy-popper.html-template{max-width:96%;max-width:calc(100% - 20px)}.tippy-popper[x-placement^=top] [x-arrow]{border-top:7px solid var(--ls-tertiary-background-color);border-right:7px solid transparent;border-left:7px solid transparent;bottom:-7px;margin:0 9px}.tippy-popper[x-placement^=top] [x-arrow].arrow-small{border-top:5px solid var(--ls-tertiary-background-color);border-right:5px solid transparent;border-left:5px solid transparent;bottom:-5px}.tippy-popper[x-placement^=top] [x-arrow].arrow-big{border-top:10px solid var(--ls-tertiary-background-color);border-right:10px solid transparent;border-left:10px solid transparent;bottom:-10px}.tippy-popper[x-placement^=top] [x-circle]{-webkit-transform-origin:0 33%;transform-origin:0 33%}.tippy-popper[x-placement^=top] [x-circle].enter{-webkit-transform:scale(1) translate(-50%,-55%);transform:scale(1) translate(-50%,-55%);opacity:1}.tippy-popper[x-placement^=top] [x-circle].leave{-webkit-transform:scale(.15) translate(-50%,-50%);transform:scale(.15) translate(-50%,-50%);opacity:0}.tippy-popper[x-placement^=top] .tippy-tooltip.light-theme [x-circle]{background-color:#fff}.tippy-popper[x-placement^=top] .tippy-tooltip.light-theme [x-arrow]{border-top:7px solid #fff;border-right:7px solid transparent;border-left:7px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.light-theme [x-arrow].arrow-small{border-top:5px solid #fff;border-right:5px solid transparent;border-left:5px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.light-theme [x-arrow].arrow-big{border-top:10px solid #fff;border-right:10px solid transparent;border-left:10px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-circle]{background-color:var(--ls-secondary-background-color)}.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-arrow]{border-top:7px solid var(--ls-secondary-background-color);border-right:7px solid transparent;border-left:7px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-arrow].arrow-small{border-top:5px solid var(--ls-secondary-background-color);border-right:5px solid transparent;border-left:5px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-arrow].arrow-big{border-top:10px solid var(--ls-secondary-background-color);border-right:10px solid transparent;border-left:10px solid transparent}.tippy-popper[x-placement^=top] [data-animation=perspective]{-webkit-transform-origin:bottom;transform-origin:bottom}.tippy-popper[x-placement^=top] [data-animation=perspective].enter{opacity:1;-webkit-transform:translateY(-10px) rotateX(0);transform:translateY(-10px) rotateX(0)}.tippy-popper[x-placement^=top] [data-animation=perspective].leave{opacity:0;-webkit-transform:translateY(0) rotateX(90deg);transform:translateY(0) rotateX(90deg)}.tippy-popper[x-placement^=top] [data-animation=fade].enter{opacity:1;-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=fade].leave{opacity:0;-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift].enter{opacity:1;-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift].leave{opacity:0;-webkit-transform:translateY(0);transform:translateY(0)}.tippy-popper[x-placement^=top] [data-animation=scale].enter{opacity:1;-webkit-transform:translateY(-10px) scale(1);transform:translateY(-10px) scale(1)}.tippy-popper[x-placement^=top] [data-animation=scale].leave{opacity:0;-webkit-transform:translateY(0) scale(0);transform:translateY(0) scale(0)}.tippy-popper[x-placement^=bottom] [x-arrow]{border-bottom:7px solid var(--ls-tertiary-background-color);border-right:7px solid transparent;border-left:7px solid transparent;top:-7px;margin:0 9px}.tippy-popper[x-placement^=bottom] [x-arrow].arrow-small{border-bottom:5px solid var(--ls-tertiary-background-color);border-right:5px solid transparent;border-left:5px solid transparent;top:-5px}.tippy-popper[x-placement^=bottom] [x-arrow].arrow-big{border-bottom:10px solid var(--ls-tertiary-background-color);border-right:10px solid transparent;border-left:10px solid transparent;top:-10px}.tippy-popper[x-placement^=bottom] [x-circle]{-webkit-transform-origin:0 -50%;transform-origin:0 -50%}.tippy-popper[x-placement^=bottom] [x-circle].enter{-webkit-transform:scale(1) translate(-50%,-45%);transform:scale(1) translate(-50%,-45%);opacity:1}.tippy-popper[x-placement^=bottom] [x-circle].leave{-webkit-transform:scale(.15) translate(-50%,-5%);transform:scale(.15) translate(-50%,-5%);opacity:0}.tippy-popper[x-placement^=bottom] .tippy-tooltip.light-theme [x-circle]{background-color:#fff}.tippy-popper[x-placement^=bottom] .tippy-tooltip.light-theme [x-arrow]{border-bottom:7px solid #fff;border-right:7px solid transparent;border-left:7px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.light-theme [x-arrow].arrow-small{border-bottom:5px solid #fff;border-right:5px solid transparent;border-left:5px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.light-theme [x-arrow].arrow-big{border-bottom:10px solid #fff;border-right:10px solid transparent;border-left:10px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-circle]{background-color:var(--ls-secondary-background-color)}.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-arrow]{border-bottom:7px solid var(--ls-secondary-background-color);border-right:7px solid transparent;border-left:7px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-arrow].arrow-small{border-bottom:5px solid var(--ls-secondary-background-color);border-right:5px solid transparent;border-left:5px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-arrow].arrow-big{border-bottom:10px solid var(--ls-secondary-background-color);border-right:10px solid transparent;border-left:10px solid transparent}.tippy-popper[x-placement^=bottom] [data-animation=perspective]{-webkit-transform-origin:top;transform-origin:top}.tippy-popper[x-placement^=bottom] [data-animation=perspective].enter{opacity:1;-webkit-transform:translateY(10px) rotateX(0);transform:translateY(10px) rotateX(0)}.tippy-popper[x-placement^=bottom] [data-animation=perspective].leave{opacity:0;-webkit-transform:translateY(0) rotateX(-90deg);transform:translateY(0) rotateX(-90deg)}.tippy-popper[x-placement^=bottom] [data-animation=fade].enter{opacity:1;-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=fade].leave{opacity:0;-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift].enter{opacity:1;-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift].leave{opacity:0;-webkit-transform:translateY(0);transform:translateY(0)}.tippy-popper[x-placement^=bottom] [data-animation=scale].enter{opacity:1;-webkit-transform:translateY(10px) scale(1);transform:translateY(10px) scale(1)}.tippy-popper[x-placement^=bottom] [data-animation=scale].leave{opacity:0;-webkit-transform:translateY(0) scale(0);transform:translateY(0) scale(0)}.tippy-popper[x-placement^=left] [x-arrow]{border-left:7px solid var(--ls-tertiary-background-color);border-top:7px solid transparent;border-bottom:7px solid transparent;right:-7px;margin:6px 0}.tippy-popper[x-placement^=left] [x-arrow].arrow-small{border-left:5px solid var(--ls-tertiary-background-color);border-top:5px solid transparent;border-bottom:5px solid transparent;right:-5px}.tippy-popper[x-placement^=left] [x-arrow].arrow-big{border-left:10px solid var(--ls-tertiary-background-color);border-top:10px solid transparent;border-bottom:10px solid transparent;right:-10px}.tippy-popper[x-placement^=left] [x-circle]{-webkit-transform-origin:50% 0;transform-origin:50% 0}.tippy-popper[x-placement^=left] [x-circle].enter{-webkit-transform:scale(1) translate(-50%,-50%);transform:scale(1) translate(-50%,-50%);opacity:1}.tippy-popper[x-placement^=left] [x-circle].leave{-webkit-transform:scale(.15) translate(-50%,-50%);transform:scale(.15) translate(-50%,-50%);opacity:0}.tippy-popper[x-placement^=left] .tippy-tooltip.light-theme [x-circle]{background-color:#fff}.tippy-popper[x-placement^=left] .tippy-tooltip.light-theme [x-arrow]{border-left:7px solid #fff;border-top:7px solid transparent;border-bottom:7px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.light-theme [x-arrow].arrow-small{border-left:5px solid #fff;border-top:5px solid transparent;border-bottom:5px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.light-theme [x-arrow].arrow-big{border-left:10px solid #fff;border-top:10px solid transparent;border-bottom:10px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-circle]{background-color:var(--ls-secondary-background-color)}.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-arrow]{border-left:7px solid var(--ls-secondary-background-color);border-top:7px solid transparent;border-bottom:7px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-arrow].arrow-small{border-left:5px solid var(--ls-secondary-background-color);border-top:5px solid transparent;border-bottom:5px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-arrow].arrow-big{border-left:10px solid var(--ls-secondary-background-color);border-top:10px solid transparent;border-bottom:10px solid transparent}.tippy-popper[x-placement^=left] [data-animation=perspective]{-webkit-transform-origin:right;transform-origin:right}.tippy-popper[x-placement^=left] [data-animation=perspective].enter{opacity:1;-webkit-transform:translateX(-10px) rotateY(0);transform:translateX(-10px) rotateY(0)}.tippy-popper[x-placement^=left] [data-animation=perspective].leave{opacity:0;-webkit-transform:translateX(0) rotateY(-90deg);transform:translateX(0) rotateY(-90deg)}.tippy-popper[x-placement^=left] [data-animation=fade].enter{opacity:1;-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=fade].leave{opacity:0;-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift].enter{opacity:1;-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift].leave{opacity:0;-webkit-transform:translateX(0);transform:translateX(0)}.tippy-popper[x-placement^=left] [data-animation=scale].enter{opacity:1;-webkit-transform:translateX(-10px) scale(1);transform:translateX(-10px) scale(1)}.tippy-popper[x-placement^=left] [data-animation=scale].leave{opacity:0;-webkit-transform:translateX(0) scale(0);transform:translateX(0) scale(0)}.tippy-popper[x-placement^=right] [x-arrow]{border-right:7px solid var(--ls-tertiary-background-color);border-top:7px solid transparent;border-bottom:7px solid transparent;left:-7px;margin:6px 0}.tippy-popper[x-placement^=right] [x-arrow].arrow-small{border-right:5px solid var(--ls-tertiary-background-color);border-top:5px solid transparent;border-bottom:5px solid transparent;left:-5px}.tippy-popper[x-placement^=right] [x-arrow].arrow-big{border-right:10px solid var(--ls-tertiary-background-color);border-top:10px solid transparent;border-bottom:10px solid transparent;left:-10px}.tippy-popper[x-placement^=right] [x-circle]{-webkit-transform-origin:-50% 0;transform-origin:-50% 0}.tippy-popper[x-placement^=right] [x-circle].enter{-webkit-transform:scale(1) translate(-50%,-50%);transform:scale(1) translate(-50%,-50%);opacity:1}.tippy-popper[x-placement^=right] [x-circle].leave{-webkit-transform:scale(.15) translate(-50%,-50%);transform:scale(.15) translate(-50%,-50%);opacity:0}.tippy-popper[x-placement^=right] .tippy-tooltip.light-theme [x-circle]{background-color:#fff}.tippy-popper[x-placement^=right] .tippy-tooltip.light-theme [x-arrow]{border-right:7px solid #fff;border-top:7px solid transparent;border-bottom:7px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.light-theme [x-arrow].arrow-small{border-right:5px solid #fff;border-top:5px solid transparent;border-bottom:5px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.light-theme [x-arrow].arrow-big{border-right:10px solid #fff;border-top:10px solid transparent;border-bottom:10px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-circle]{background-color:var(--ls-secondary-background-color)}.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-arrow]{border-right:7px solid var(--ls-secondary-background-color);border-top:7px solid transparent;border-bottom:7px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-arrow].arrow-small{border-right:5px solid var(--ls-secondary-background-color);border-top:5px solid transparent;border-bottom:5px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-arrow].arrow-big{border-right:10px solid var(--ls-secondary-background-color);border-top:10px solid transparent;border-bottom:10px solid transparent}.tippy-popper[x-placement^=right] [data-animation=perspective]{-webkit-transform-origin:left;transform-origin:left}.tippy-popper[x-placement^=right] [data-animation=perspective].enter{opacity:1;-webkit-transform:translateX(10px) rotateY(0);transform:translateX(10px) rotateY(0)}.tippy-popper[x-placement^=right] [data-animation=perspective].leave{opacity:0;-webkit-transform:translateX(0) rotateY(90deg);transform:translateX(0) rotateY(90deg)}.tippy-popper[x-placement^=right] [data-animation=fade].enter{opacity:1;-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=fade].leave{opacity:0;-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift].enter{opacity:1;-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift].leave{opacity:0;-webkit-transform:translateX(0);transform:translateX(0)}.tippy-popper[x-placement^=right] [data-animation=scale].enter{opacity:1;-webkit-transform:translateX(10px) scale(1);transform:translateX(10px) scale(1)}.tippy-popper[x-placement^=right] [data-animation=scale].leave{opacity:0;-webkit-transform:translateX(0) scale(0);transform:translateX(0) scale(0)}.tippy-popper .tippy-tooltip.transparent-theme{background-color:var(--ls-secondary-background-color)}.tippy-popper .tippy-tooltip.transparent-theme[data-animatefill]{background-color:transparent}.tippy-popper .tippy-tooltip.light-theme{color:#26323d;box-shadow:0 4px 20px 4px rgba(0,20,60,.1),0 4px 80px -8px rgba(0,20,60,.2);background-color:#fff}.tippy-popper .tippy-tooltip.light-theme[data-animatefill]{background-color:transparent}.tippy-tooltip{position:relative;color:var(--ls-primary-text-color);border-radius:4px;padding:.4rem .8rem;text-align:center;will-change:transform;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:var(--ls-tertiary-background-color)}.tippy-tooltip--small{padding:.25rem .5rem;font-size:.8rem}.tippy-tooltip--big{padding:.6rem 1.2rem;font-size:1.2rem}.tippy-tooltip[data-animatefill]{overflow:hidden;background-color:transparent}.tippy-tooltip[data-interactive]{pointer-events:auto}.tippy-tooltip[data-inertia]{transition-timing-function:cubic-bezier(.53,2,.36,.85)}.tippy-tooltip [x-arrow]{position:absolute;width:0;height:0}.tippy-tooltip [x-circle]{position:absolute;will-change:transform;background-color:var(--ls-tertiary-background-color);border-radius:50%;width:130%;width:calc(110% + 2rem);left:50%;top:50%;z-index:-1;overflow:hidden;transition:all ease}.tippy-tooltip [x-circle]:before{content:"";padding-top:90%;float:left}@media (max-width:450px){.tippy-popper{max-width:96%;max-width:calc(100% - 20px)}} .tippy-popper .tippy-tooltip.customized-theme * { text-align: left; diff --git a/resources/forge.config.js b/resources/forge.config.js index fcd62fbd69..e476201dc7 100644 --- a/resources/forge.config.js +++ b/resources/forge.config.js @@ -2,24 +2,32 @@ const path = require('path') module.exports = { packagerConfig: { - icon: './icons/canary/logseq_big_sur.icns', - name: 'Logseq Canary', + icon: './icons/logseq_big_sur.icns', + osxSign: { + identity: 'Developer ID Application: Tiansheng Qin', + 'hardened-runtime': true, + entitlements: 'entitlements.plist', + 'entitlements-inherit': 'entitlements.plist', + 'signature-flags': 'library' + }, + osxNotarize: { + appleId: "my-fake-apple-id", + appleIdPassword: "my-fake-apple-id-password", + }, }, makers: [ { 'name': '@electron-forge/maker-squirrel', 'config': { - title: 'Logseq Canary', - name: 'LogseqCanary', // ID name - setupIcon: './icons/canary/logseq.ico' + 'name': 'Logseq' } }, { name: '@electron-forge/maker-dmg', config: { format: 'ULFO', - icon: './icons/canary/logseq_big_sur.icns', - name: 'Logseq Canary' + icon: './icons/logseq_big_sur.icns', + name: 'Logseq' } }, { diff --git a/resources/package.json b/resources/package.json index 2fbb7fd418..af4211f63f 100644 --- a/resources/package.json +++ b/resources/package.json @@ -1,6 +1,6 @@ { - "name": "Logseq-Canary", - "version": "0.0.1", + "name": "Logseq", + "version": "0.1.0", "main": "electron.js", "author": "Logseq", "description": "A privacy-first, open-source platform for knowledge management and collaboration.", diff --git a/shadow-cljs.edn b/shadow-cljs.edn index 881d6d3812..9812294996 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -2,8 +2,6 @@ {:deps true :nrepl {:port 8701} - :http {:host "192.168.2.227"} - :builds {:app {:target :browser diff --git a/src/electron/electron/core.cljs b/src/electron/electron/core.cljs index 0a482578c7..c7491bb375 100644 --- a/src/electron/electron/core.cljs +++ b/src/electron/electron/core.cljs @@ -28,9 +28,8 @@ win-opts (cond-> {:width (.-width win-state) :height (.-height win-state) - :frame (not mac?) + :frame true :autoHideMenuBar (not mac?) - :titleBarStyle (if mac? "hidden" nil) :webPreferences {:plugins true ; pdf :nodeIntegration false diff --git a/src/electron/electron/fs_watcher.cljs b/src/electron/electron/fs_watcher.cljs new file mode 100644 index 0000000000..e2f09af613 --- /dev/null +++ b/src/electron/electron/fs_watcher.cljs @@ -0,0 +1,62 @@ +(ns electron.fs-watcher + (:require [cljs-bean.core :as bean] + ["fs" :as fs] + ["chokidar" :as watcher] + [promesa.core :as p] + [clojure.string :as string] + [electron.utils :as utils] + ["electron" :refer [app]])) + +;; TODO: explore different solutions for different platforms +;; 1. https://github.com/Axosoft/nsfw + +(defonce polling-interval 5000) + +(defonce file-watcher-chan "file-watcher") +(defn send-file-watcher! [^js win type payload] + (.. win -webContents + (send file-watcher-chan + (bean/->js {:type type :payload payload})))) + +(defn watch-dir! + [win dir] + (when (fs/existsSync dir) + (let [watcher (.watch watcher dir + (clj->js + {:ignored (partial utils/ignored-path? dir) + :ignoreInitial true + :ignorePermissionErrors true + :interval polling-interval + :binaryInterval polling-interval + :persistent true + :disableGlobbing true + + :awaitWriteFinish true}))] + ;; TODO: batch sender + (.on watcher "add" + (fn [path] + (send-file-watcher! win "add" + {:dir (utils/fix-win-path! dir) + :path (utils/fix-win-path! path) + :content (utils/read-file path) + :stat (fs/statSync path)}))) + (.on watcher "change" + (fn [path] + (send-file-watcher! win "change" + {:dir (utils/fix-win-path! dir) + :path (utils/fix-win-path! path) + :content (utils/read-file path) + :stat (fs/statSync path)}))) + ;; (.on watcher "unlink" + ;; (fn [path] + ;; (send-file-watcher! win "unlink" + ;; {:dir (utils/fix-win-path! dir) + ;; :path (utils/fix-win-path! path)}))) + (.on watcher "error" + (fn [path] + (println "Watch error happened: " + {:path path}))) + + (.on app "quit" #(.close watcher)) + + true))) diff --git a/src/electron/electron/handler.cljs b/src/electron/electron/handler.cljs index 33f35a7581..527ecd6471 100644 --- a/src/electron/electron/handler.cljs +++ b/src/electron/electron/handler.cljs @@ -4,7 +4,7 @@ ["fs" :as fs] ["fs-extra" :as fs-extra] ["path" :as path] - ["chokidar" :as watcher] + [electron.fs-watcher :as watcher] [promesa.core :as p] [goog.object :as gobj] [clojure.string :as string] @@ -33,11 +33,8 @@ (defmethod handle :unlink [_window [_ path]] (fs/unlinkSync path)) -(defn- read-file - [path] - (.toString (fs/readFileSync path))) (defmethod handle :readFile [_window [_ path]] - (read-file path)) + (utils/read-file path)) (defmethod handle :writeFile [_window [_ path content]] ;; TODO: handle error @@ -50,21 +47,7 @@ (defmethod handle :stat [_window [_ path]] (fs/statSync path)) -(defn- fix-win-path! - [path] - (when path - (if utils/win32? - (string/replace path "\\" "/") - path))) -;; TODO: ignore according to mime types -(defn ignored-path? - [dir path] - (or - (some #(string/starts-with? path (str dir "/" %)) - ["." "assets" "node_modules"]) - (some #(string/ends-with? path %) - [".swap" ".crswap" ".tmp" ".DS_Store"]))) (defonce allowed-formats #{:org :markdown :md :edn :json :css :excalidraw}) @@ -79,16 +62,16 @@ [path] (let [result (->> (readdir path) - (remove (partial ignored-path? path)) + (remove (partial utils/ignored-path? path)) (filter #(contains? allowed-formats (get-ext %))) (map (fn [path] (let [stat (fs/statSync path)] (when-not (.isDirectory stat) - {:path (fix-win-path! path) - :content (read-file path) + {:path (utils/fix-win-path! path) + :content (utils/read-file path) :stat stat})))) (remove nil?))] - (vec (cons {:path (fix-win-path! path)} result)))) + (vec (cons {:path (utils/fix-win-path! path)} result)))) (defn- get-ls-dotdir-root [] @@ -150,7 +133,7 @@ (defn clear-cache! [] (let [path (.getPath ^object app "userData")] - (doseq [dir ["search" "IndexedDB" "Local Storage" "databases" "cache"]] + (doseq [dir ["search" "IndexedDB"]] (let [path (path/join path dir)] (try (fs-extra/removeSync path) @@ -162,63 +145,9 @@ (clear-cache!) (search/ensure-search-dir!)) -(defn- get-file-ext - [file] - (last (string/split file #"\."))) - -(defonce file-watcher-chan "file-watcher") -(defn send-file-watcher! [^js win type payload] - (.. win -webContents - (send file-watcher-chan - (bean/->js {:type type :payload payload})))) - -(defonce polling-interval 5000) -(defn watch-dir! - [win dir] - (when (fs/existsSync dir) - (let [watcher (.watch watcher dir - (clj->js - {:ignored (partial ignored-path? dir) - :ignoreInitial true - :ignorePermissionErrors true - :interval polling-interval - :binaryInterval polling-interval - :persistent true - :disableGlobbing true - - :awaitWriteFinish true}))] - ;; TODO: batch sender - (.on watcher "add" - (fn [path] - (send-file-watcher! win "add" - {:dir (fix-win-path! dir) - :path (fix-win-path! path) - :content (read-file path) - :stat (fs/statSync path)}))) - (.on watcher "change" - (fn [path] - (send-file-watcher! win "change" - {:dir (fix-win-path! dir) - :path (fix-win-path! path) - :content (read-file path) - :stat (fs/statSync path)}))) - ;; (.on watcher "unlink" - ;; (fn [path] - ;; (send-file-watcher! win "unlink" - ;; {:dir (fix-win-path! dir) - ;; :path (fix-win-path! path)}))) - (.on watcher "error" - (fn [path] - (println "Watch error happened: " - {:path path}))) - - (.on app "quit" #(.close watcher)) - - true))) - (defmethod handle :addDirWatcher [window [_ dir]] (when dir - (watch-dir! window dir))) + (watcher/watch-dir! window dir))) (defmethod handle :openDialogSync [^js window _messages] (let [result (.showOpenDialogSync dialog (bean/->js diff --git a/src/electron/electron/utils.cljs b/src/electron/electron/utils.cljs index 44bfed57de..b7ea082ef3 100644 --- a/src/electron/electron/utils.cljs +++ b/src/electron/electron/utils.cljs @@ -1,4 +1,6 @@ -(ns electron.utils) +(ns electron.utils + (:require [clojure.string :as string] + ["fs" :as fs])) (defonce mac? (= (.-platform js/process) "darwin")) (defonce win32? (= (.-platform js/process) "win32")) @@ -10,3 +12,27 @@ (defonce open (js/require "open")) (defonce fetch (js/require "node-fetch")) + +(defn get-file-ext + [file] + (last (string/split file #"\."))) + +;; TODO: ignore according to mime types +(defn ignored-path? + [dir path] + (or + (some #(string/starts-with? path (str dir "/" %)) + ["." "assets" "node_modules"]) + (some #(string/ends-with? path %) + [".swap" ".crswap" ".tmp" ".DS_Store"]))) + +(defn fix-win-path! + [path] + (when path + (if win32? + (string/replace path "\\" "/") + path))) + +(defn read-file + [path] + (.toString (fs/readFileSync path))) diff --git a/src/main/frontend/commands.cljs b/src/main/frontend/commands.cljs index 8af14cff4d..69259ee530 100644 --- a/src/main/frontend/commands.cljs +++ b/src/main/frontend/commands.cljs @@ -441,7 +441,7 @@ (defn compute-pos-delta-when-change-marker [current-input edit-content new-value marker pos] - (let [old-marker (some->> (first (re-find marker/bare-marker-pattern edit-content)) + (let [old-marker (some->> (first (util/safe-re-find marker/bare-marker-pattern edit-content)) (string/trim)) old-marker (if old-marker old-marker "") pos-delta (- (count marker) @@ -463,7 +463,7 @@ (if-let [matches (seq (util/re-pos new-line-re-pattern prefix))] (let [[start-pos content] (last matches)] (+ start-pos (count content))) - (count (re-find re-pattern prefix)))) + (count (util/safe-re-find re-pattern prefix)))) new-value (str (subs edit-content 0 pos) (string/replace-first (subs edit-content pos) marker/marker-pattern @@ -490,9 +490,9 @@ slash-pos (:pos @*slash-caret-pos) heading-pattern #"^#\+" prefix (subs edit-content 0 (dec slash-pos)) - pos (count (re-find heading-pattern prefix)) + pos (count (util/safe-re-find heading-pattern prefix)) new-value (cond - (re-find heading-pattern prefix) + (util/safe-re-find heading-pattern prefix) (str (subs edit-content 0 pos) (string/replace-first (subs edit-content pos) heading-pattern diff --git a/src/main/frontend/components/block.cljs b/src/main/frontend/components/block.cljs index ac01ae315f..69d6e5f182 100644 --- a/src/main/frontend/components/block.cljs +++ b/src/main/frontend/components/block.cljs @@ -249,11 +249,15 @@ (if (and (config/local-asset? href) (config/local-db? (state/get-current-repo))) (asset-link config title href label metadata full_text) - (let [href (if config/publishing? + (let [href (cond + (util/starts-with? href "http") + href + + config/publishing? (subs href 1) - (if (util/starts-with? href "http") - href - (get-file-absolute-path config href)))] + + :else + (get-file-absolute-path config href))] (resizable-image config title href metadata full_text false))))) (defn repetition-to-string @@ -365,10 +369,10 @@ (get page-entity :block/original-name page-name)))]) (rum/defc page-cp - [{:keys [html-export? label children contents-page?] :as config} page] + [{:keys [html-export? label children contents-page? sidebar?] :as config} page] (when-let [page-name (:block/name page)] - (let [page-entity page - page (string/lower-case page-name) + (let [page (string/lower-case page-name) + page-entity (db/entity [:block/name page]) redirect-page-name (cond (:block/alias? config) page @@ -385,12 +389,18 @@ (util/encode-str page) (rfe/href :page {:name redirect-page-name})) inner (page-inner config page-name href redirect-page-name page-entity contents-page? children html-export? label)] - inner - ;; (ui/tippy - ;; {:interactive true - ;; :html (page-preview page-name)} - ;; inner) - ))) + (ui/tippy {:html [:div.tippy-wrapper.overflow-y-auto + {:style {:width 735 + :text-align "left" + :font-weight 500 + :max-height 600 + :padding-bottom 200}} + [:h2.font-bold.text-lg page-name] + (let [page (db/entity [:block/name (string/lower-case page-name)])] + ((state/get-page-blocks-cp) (state/get-current-repo) page (:sidebar? config)))] + :interactive true + :delay 1000} + inner)))) (rum/defc asset-reference [title path] @@ -487,6 +497,7 @@ (blocks-container blocks (assoc config :id page-name :embed? true + :page-embed? true :ref? false))))])) (defn- get-label-text @@ -528,25 +539,23 @@ {:block block}) (route-handler/redirect! {:to :page :path-params {:name id}})))} - (let [title (let [title (:block/title block)] - (if (empty? title) - ;; display the content - [:div.block-ref - (block-content config block nil (:block/uuid block) (:slide? config))] - (->elem - :span.block-ref - (map-inline config title))))] - (ui/tippy {:html [:div.tippy-wrapper - {:style {:width 780 - :text-align "left"}} + [:span.block-ref + (block-content (assoc config :block-ref? true) + block nil (:block/uuid block) + (:slide? config))])] + (ui/tippy {:html [:div.tippy-wrapper.overflowy-y-auto + {:style {:width 735 + :text-align "left" + :max-height 600}} (block-container config block)] - :interactive true} - (if label - (->elem - :span.block-ref - (map-inline config label)) - title)))] + :interactive true + :delay 1000} + (if label + (->elem + :span.block-ref + (map-inline config label)) + title)))] [:span.warning.mr-1 {:title "Block ref invalid"} (util/format "((%s))" id)])))) @@ -568,10 +577,7 @@ (if macro-content (let [ast (->> (mldoc/->edn macro-content (mldoc/default-config format)) (map first)) - block? (contains? #{"Paragraph" - "Raw_Html" - "Hiccup"} - (ffirst ast))] + block? (mldoc/block-with-title? (ffirst ast))] (if block? [:div (markup-elements-cp (assoc config :block/format format) ast)] @@ -697,7 +703,7 @@ (not= \* (last s))) (->elem :a {:on-click #(route-handler/jump-to-anchor! (mldoc/anchorLink (subs s 1)))} (subs s 1)) - (re-find #"(?i)^http[s]?://" s) + (util/safe-re-find #"(?i)^http[s]?://" s) (->elem :a {:href s :data-href s :target "_blank"} @@ -850,7 +856,7 @@ (when-let [youtube-id (cond (== 11 (count url)) url :else - (nth (re-find YouTube-regex url) 5))] + (nth (util/safe-re-find YouTube-regex url) 5))] (when-not (string/blank? youtube-id) (let [width (min (- (util/get-width) 96) 560) @@ -867,7 +873,7 @@ (= name "vimeo") (when-let [url (first arguments)] (let [Vimeo-regex #"^((?:https?:)?//)?((?:www).)?((?:player.vimeo.com|vimeo.com)?)((?:/video/)?)([\w-]+)(\S+)?$"] - (when-let [vimeo-id (nth (re-find Vimeo-regex url) 5)] + (when-let [vimeo-id (nth (util/safe-re-find Vimeo-regex url) 5)] (when-not (string/blank? vimeo-id) (let [width (min (- (util/get-width) 96) 560) @@ -888,7 +894,7 @@ (when-let [id (cond (<= (count url) 15) url :else - (last (re-find id-regex url)))] + (last (util/safe-re-find id-regex url)))] (when-not (string/blank? id) (let [width (min (- (util/get-width) 96) 560) @@ -1274,7 +1280,7 @@ 2)) elem (if heading-level (keyword (str "h" heading-level)) - :div)] + :span.inline)] (->elem elem (merge @@ -1358,7 +1364,11 @@ properties (sort properties)] (cond (seq properties) - [:div.blocks-properties + [:div.block-properties + {:class (when pre-block? "page-properties") + :title (if pre-block? + "Click to edit this page's properties" + "Click to edit this block's properties")} (for [[k v] properties] (rum/with-key (property-cp config block k v) (str (:block/uuid block) "-" k)))] @@ -1464,6 +1474,7 @@ (rum/defc block-content < rum/reactive [config {:block/keys [uuid title body meta content marker dummy? page format repo children pre-block? properties idx container block-refs-count scheduled deadline repeated?] :as block} edit-input-id block-id slide?] (let [collapsed? (get properties :collapsed) + block-ref-with-title? (and (:block-ref? config) (seq title)) dragging? (rum/react *dragging?) content (if (string? content) (string/trim content) "") mouse-down-key (if (util/ios?) @@ -1475,12 +1486,17 @@ (block-content-on-mouse-down e block block-id properties content format edit-input-id)) :on-drag-over (fn [event] (block-content-on-drag-over event uuid)) :on-drag-leave (fn [_event] (block-content-on-drag-leave uuid)) - :on-drop (fn [event] (block-content-on-drop event block uuid))}] - [:div.flex.relative {:style {:width "100%"}} - [:div.flex-1.flex-col.relative.block-content - (cond-> {:id (str "block-content-" uuid)} - (not slide?) - (merge attrs)) + :on-drop (fn [event] (block-content-on-drop event block uuid)) + :style {:width "100%"}}] + [:div.block-content.inline + (cond-> {:id (str "block-content-" uuid)} + (not slide?) + (merge attrs)) + + [:span + ;; .flex.relative {:style {:width "100%"}} + [:span + ;; .flex-1.flex-col.relative.block-content (cond (seq title) @@ -1506,47 +1522,17 @@ (not (:slide? config))) (properties-cp config block)) - (when (seq body) - (do - [:div.block-body {:style {:display (if (and collapsed? (seq title)) "none" "")}} - ;; TODO: consistent id instead of the idx (since it could be changed later) - (let [body (block/trim-break-lines! (:block/body block))] - (for [[idx child] (medley/indexed body)] - (when-let [block (markup-element-cp config child)] - (rum/with-key (block-child block) - (str uuid "-" idx)))))]))] - (when (and block-refs-count (> block-refs-count 0)) - [:div - [:a.open-block-ref-link.bg-base-2 - {:title "Open block references" - :style {:margin-top -1} - :on-click (fn [] - (state/sidebar-add-block! - (state/get-current-repo) - (:db/id block) - :block-ref - {:block block}))} - block-refs-count]]) - - (when (and (= marker "DONE") - (state/enable-timetracking?)) - (let [start-time (or - (get properties :now) - (get properties :doing) - (get properties :in-progress) - (get properties :later) - (get properties :todo)) - finish-time (get properties :done)] - (when (and start-time finish-time (> finish-time start-time)) - [:div.text-sm.absolute.time-spent {:style {:top 0 - :right 0 - :padding-left 2} - :title (str (date/int->local-time start-time) " ~ " (date/int->local-time finish-time))} - [:span.opacity-70 - (utils/timeConversion (- finish-time start-time))]])))])) + (when (and (not block-ref-with-title?) (seq body)) + [:div.block-body {:style {:display (if (and collapsed? (seq title)) "none" "")}} + ;; TODO: consistent id instead of the idx (since it could be changed later) + (let [body (block/trim-break-lines! (:block/body block))] + (for [[idx child] (medley/indexed body)] + (when-let [block (markup-element-cp config child)] + (rum/with-key (block-child block) + (str uuid "-" idx)))))])]]])) (rum/defc block-content-or-editor < rum/reactive - [config {:block/keys [uuid title body meta content dummy? page format repo children pre-block? idx] :as block} edit-input-id block-id slide? heading-level] + [config {:block/keys [uuid title body meta content dummy? page format repo children marker properties block-refs-count pre-block? idx] :as block} edit-input-id block-id slide? heading-level] (let [editor-box (get config :editor-box) edit? (state/sub [:editor/editing? edit-input-id])] (if (and edit? editor-box) @@ -1562,7 +1548,45 @@ (editor-handler/select-block! uuid)))} edit-input-id config)] - (block-content config block edit-input-id block-id slide?)))) + [:div.flex.flex-row.block-content-wrapper + [:div.flex.flex-1 + (block-content config block edit-input-id block-id slide?)] + [:div.flex.flex-row + (when (and (:embed? config) (not (:page-embed? config))) + [:a.opacity-30.hover:opacity-100.svg-small.inline + {:on-mouse-down (fn [e] + (util/stop e) + (when-let [block (:block config)] + (editor-handler/edit-block! block :max (:block/format block) (:block/uuid block))))} + svg/edit]) + + (when (and (= (:block/marker block) "DONE") + (state/enable-timetracking?)) + (let [start-time (or + (get properties :now) + (get properties :doing) + (get properties :in-progress) + (get properties :later) + (get properties :todo)) + finish-time (get properties :done)] + (when (and start-time finish-time (> finish-time start-time)) + [:div.text-sm.time-spent.ml-1 {:title (str (date/int->local-time start-time) " ~ " (date/int->local-time finish-time)) + :style {:padding-top 3}} + [:a.opacity-30.hover:opacity-100 + (utils/timeConversion (- finish-time start-time))]]))) + + (when (and block-refs-count (> block-refs-count 0)) + [:div + [:a.open-block-ref-link.bg-base-2.text-sm.ml-2 + {:title "Open block references" + :style {:margin-top -1} + :on-click (fn [] + (state/sidebar-add-block! + (state/get-current-repo) + (:db/id block) + :block-ref + {:block block}))} + block-refs-count]])]]))) (rum/defc dnd-separator-wrapper < rum/reactive [block slide? top?] @@ -1604,7 +1628,10 @@ (let [parents (doall (for [{:block/keys [uuid title name]} parents] (when-not name ; not page - [:a {:href (rfe/href :page {:name uuid})} + [:a {:on-mouse-down (fn [e] + (util/stop e) + (route-handler/redirect! {:to :page + :path-params {:name uuid}}))} (map-inline config title)]))) parents (remove nil? parents)] (reset! parents-atom parents) @@ -2126,7 +2153,7 @@ ["Paragraph" l] ;; TODO: speedup - (if (re-find #"\"Export_Snippet\" \"embed\"" (str l)) + (if (util/safe-re-find #"\"Export_Snippet\" \"embed\"" (str l)) (->elem :div (map-inline config l)) (->elem :div.is-paragraph (map-inline config l))) diff --git a/src/main/frontend/components/block.css b/src/main/frontend/components/block.css index 5930ed9ba4..f69901e20b 100644 --- a/src/main/frontend/components/block.css +++ b/src/main/frontend/components/block.css @@ -1,11 +1,17 @@ .blocks-container { } +.block-content-wrapper { + width: 100%; +} + .block-content { min-height: 24px; max-width: 100%; overflow: initial; cursor: text; + white-space: pre-wrap; + overflow-wrap: break-word; word-break: break-word; img { @@ -86,11 +92,10 @@ } .open-block-ref-link { - @apply py-0 px-1 rounded opacity-50 hover:opacity-100; - font-size: 12px; - line-height: 1em; - position: relative; - right: -4px; + @apply opacity-30 hover:opacity-100; + background-color: var(--ls-page-properties-background-color); + padding: 1px 4px; + border-radius: 2px; } .block-body { @@ -160,11 +165,16 @@ } } -.blocks-properties { +.block-properties { + margin: 4px 0; padding: 4px 8px; background-color: var(--ls-block-properties-background-color, #f0f8ff); } +.page-properties { + background-color: var(--ls-page-properties-background-color); +} + .marker-switch { padding: 2px 4px; opacity: 0.5; diff --git a/src/main/frontend/components/editor.cljs b/src/main/frontend/components/editor.cljs index f83dd87097..edd0114ee1 100644 --- a/src/main/frontend/components/editor.cljs +++ b/src/main/frontend/components/editor.cljs @@ -286,6 +286,7 @@ (< delta-width (* max-width 0.5))))] ;; FIXME: for translateY layer [:div.absolute.rounded-md.shadow-lg.absolute-modal {:class (if x-overflow? "is-overflow-vw-x" "") + :on-mouse-down (fn [e] (.stopPropagation e)) :style (merge {:top (+ top offset-top) :max-height to-max-height @@ -378,7 +379,12 @@ {:init (fn [state] (assoc state ::heading-level (:heading-level (first (:rum/args state))))) :did-mount (fn [state] + ;; TODO: + ;; if we quickly click into a block when editing another block, + ;; this will happen before the `will-unmount` event, which will + ;; lost the content in the editing block. (state/set-editor-args! (:rum/args state)) + ;; (js/setTimeout #(state/set-editor-args! (:rum/args state)) 20) state)} (mixins/event-mixin setup-key-listener!) (shortcut/mixin :shortcut.handler/block-editing-only) diff --git a/src/main/frontend/components/export.cljs b/src/main/frontend/components/export.cljs index 2802350040..d7d545d643 100644 --- a/src/main/frontend/components/export.cljs +++ b/src/main/frontend/components/export.cljs @@ -14,6 +14,9 @@ [:h1.title "Export"] [:ul.mr-1 + [:li.mb-4 + [:a.font-medium {:on-click #(export/convert-repo-markdown-v2! current-repo)} + (t :convert-markdown)]] (when (util/electron?) [:li.mb-4 [:a.font-medium {:on-click #(export/export-repo-as-html! current-repo)} @@ -21,9 +24,6 @@ [:li.mb-4 [:a.font-medium {:on-click #(export/export-repo-as-markdown! current-repo)} (t :export-markdown)]] - [:li.mb-4 - [:a.font-medium {:on-click #(export/convert-repo-markdown-v2! current-repo)} - (t :convert-markdown)]] [:li.mb-4 [:a.font-medium {:on-click #(export/export-repo-as-edn! current-repo)} (t :export-edn)]]] diff --git a/src/main/frontend/components/page.cljs b/src/main/frontend/components/page.cljs index 66da1e99cd..8bba1bdf39 100644 --- a/src/main/frontend/components/page.cljs +++ b/src/main/frontend/components/page.cljs @@ -64,6 +64,7 @@ (when (:block/dummy? block) (editor-handler/edit-block! block :max (:block/format block) (:block/uuid block)))) state) + (rum/defc page-blocks-inner < {:did-mount open-first-block! :did-update open-first-block!} @@ -77,31 +78,46 @@ (declare page) +(defn- get-page-format + [page-name] + (let [block? (util/uuid-string? page-name) + block-id (and block? (uuid page-name)) + page (if block-id + (:block/name (:block/page (db/entity [:block/uuid block-id]))) + page-name)] + (db/get-page-format page))) + (rum/defc page-blocks-cp < rum/reactive db-mixins/query - [repo page-e file-path page-name page-original-name encoded-page-name sidebar? journal? block? block-id format] - (let [raw-page-blocks (get-blocks repo page-name page-original-name block? block-id) - page-blocks (block-handler/with-dummy-block raw-page-blocks format - (if (empty? raw-page-blocks) - {:block/page {:db/id (:db/id page-e)} - :block/file {:db/id (:db/id (:block/file page-e))}}) - {:journal? journal? - :page-name page-name}) - hiccup-config {:id (if block? (str block-id) page-name) - :sidebar? sidebar? - :block? block? - :editor-box editor/box - :page page} - hiccup-config (common-handler/config-with-document-mode hiccup-config) - hiccup (block/->hiccup page-blocks hiccup-config {})] - (page-blocks-inner page-name page-blocks hiccup sidebar?))) + [repo page-e sidebar?] + (when page-e + (let [page-name (or (:block/name page-e) + (str (:block/uuid page-e))) + page-original-name (or (:block/original-name page-e) page-name) + format (get-page-format page-name) + journal? (db/journal-page? page-name) + block? (util/uuid-string? page-name) + block-id (and block? (uuid page-name)) + raw-page-blocks (get-blocks repo page-name page-original-name block? block-id) + page-blocks (block-handler/with-dummy-block raw-page-blocks format + (if (empty? raw-page-blocks) + {:block/page {:db/id (:db/id page-e)} + :block/file {:db/id (:db/id (:block/file page-e))}}) + {:journal? journal? + :page-name page-name}) + hiccup-config {:id (if block? (str block-id) page-name) + :sidebar? sidebar? + :block? block? + :editor-box editor/box + :page page} + hiccup-config (common-handler/config-with-document-mode hiccup-config) + hiccup (block/->hiccup page-blocks hiccup-config {})] + (page-blocks-inner page-name page-blocks hiccup sidebar?)))) (defn contents-page - [{:block/keys [name original-name file] :as contents}] + [page] (when-let [repo (state/get-current-repo)] - (let [format (db/get-page-format name) - file-path (:file/path file)] - (page-blocks-cp repo contents file-path name original-name name true false false nil format)))) + (page-blocks-cp repo page true))) (rum/defc today-queries < rum/reactive [repo today? sidebar?] @@ -211,13 +227,6 @@ ;; A page is just a logical block (rum/defcs page < rum/reactive - #_ - {:did-mount (fn [state] - (ui-handler/scroll-and-highlight! state) - state) - :did-update (fn [state] - (ui-handler/scroll-and-highlight! state) - state)} [state {:keys [repo page-name preview?] :as option}] (when-let [path-page-name (or page-name (get-page-name state) @@ -240,153 +249,140 @@ (db/entity repo)) (db/entity repo [:block/name page-name])) ;; TODO: replace page with frontend.format.block/page->map - page (if page page (do - (db/transact! repo [{:block/name page-name - :block/original-name path-page-name - :block/uuid (db/new-block-id)}]) - (db/entity repo [:block/name page-name]))) - {:keys [title] :as properties} (:block/properties page) - page-name (:block/name page) - page-original-name (:block/original-name page) - title (or title page-original-name page-name) - file (:block/file page) - file-path (and (:db/id file) (:file/path (db/entity repo (:db/id file)))) - today? (and - journal? - (= page-name (string/lower-case (date/journal-name)))) - developer-mode? (state/sub [:ui/developer-mode?]) - public? (true? (:public properties))] - [:div.flex-1.page.relative (if (seq (:block/tags page)) - (let [page-names (model/get-page-names-by-ids (map :db/id (:block/tags page)))] - {:data-page-tags (text/build-data-value page-names)}) - {}) - [:div.relative - (when (and (not sidebar?) - (not block?)) - [:div.flex.flex-row.space-between - [:div.flex-1.flex-row - [:a {:on-click (fn [e] - (.preventDefault e) - (when (gobj/get e "shiftKey") - (when-let [page (db/pull repo '[*] [:block/name page-name])] - (state/sidebar-add-block! - repo - (:db/id page) - :page - {:page page}))))} - [:h1.title {:style {:margin-left -2}} - (if page-original-name - (if (and (string/includes? page-original-name "[[") - (string/includes? page-original-name "]]")) - (let [ast (mldoc/->edn page-original-name (mldoc/default-config format))] - (block/markup-element-cp {} (ffirst ast))) - page-original-name) - (or - page-name - path-page-name))]]] - (when (not config/publishing?) - (let [contents? (= (string/lower-case (str page-name)) "contents") - links (fn [] (->> - [(when-not contents? - {:title (t :page/add-to-contents) - :options {:on-click (fn [] (page-handler/handle-add-page-to-contents! page-original-name))}}) + page (if page page (do + (db/transact! repo [{:block/name page-name + :block/original-name path-page-name + :block/uuid (db/new-block-id)}]) + (db/entity repo [:block/name page-name]))) + {:keys [title] :as properties} (:block/properties page) + page-name (:block/name page) + page-original-name (:block/original-name page) + title (or title page-original-name page-name) + today? (and + journal? + (= page-name (string/lower-case (date/journal-name)))) + developer-mode? (state/sub [:ui/developer-mode?]) + public? (true? (:public properties))] + [:div.flex-1.page.relative (if (seq (:block/tags page)) + (let [page-names (model/get-page-names-by-ids (map :db/id (:block/tags page)))] + {:data-page-tags (text/build-data-value page-names)}) + {}) + [:div.relative + (when (and (not sidebar?) + (not block?)) + [:div.flex.flex-row.space-between + [:div.flex-1.flex-row + [:a {:on-click (fn [e] + (.preventDefault e) + (when (gobj/get e "shiftKey") + (when-let [page (db/pull repo '[*] [:block/name page-name])] + (state/sidebar-add-block! + repo + (:db/id page) + :page + {:page page}))))} + [:h1.title {:style {:margin-left -2}} + (if page-original-name + (if (and (string/includes? page-original-name "[[") + (string/includes? page-original-name "]]")) + (let [ast (mldoc/->edn page-original-name (mldoc/default-config format))] + (block/markup-element-cp {} (ffirst ast))) + page-original-name) + (or + page-name + path-page-name))]]] + (when (not config/publishing?) + (let [contents? (= (string/lower-case (str page-name)) "contents") + links (fn [] (->> + [(when-not contents? + {:title (t :page/add-to-contents) + :options {:on-click (fn [] (page-handler/handle-add-page-to-contents! page-original-name))}}) - {:title "Go to presentation mode" + {:title "Go to presentation mode" + :options {:on-click (fn [] + (state/sidebar-add-block! + repo + (:db/id page) + :page-presentation + {:page page}))}} + (when-not contents? + {:title (t :page/rename) + :options {:on-click #(state/set-modal! (rename-page-dialog title page-name))}}) + + (when-let [file-path (and (util/electron?) (page-handler/get-page-file-path))] + [{:title (t :page/open-in-finder) + :options {:on-click #(js/window.apis.showItemInFolder file-path)}} + {:title (t :page/open-with-default-app) + :options {:on-click #(js/window.apis.openPath file-path)}}]) + + (when-not contents? + {:title (t :page/delete) + :options {:on-click #(state/set-modal! (delete-page-dialog page-name))}}) + + (when (state/get-current-page) + {:title (t :export) + :options {:on-click #(state/set-modal! export/export-page)}}) + + (when (util/electron?) + {:title (t (if public? :page/make-private :page/make-public)) + :options {:on-click + (fn [] + (page-handler/update-public-attribute! + page-name + (if public? false true)) + (state/close-modal!))}}) + + (when developer-mode? + {:title "(Dev) Show page data" :options {:on-click (fn [] - (state/sidebar-add-block! - repo - (:db/id page) - :page-presentation - {:page page}))}} - (when-not contents? - {:title (t :page/rename) - :options {:on-click #(state/set-modal! (rename-page-dialog title page-name))}}) + (let [page-data (with-out-str (pprint/pprint (db/pull (:db/id page))))] + (println page-data) + (notification/show! + [:div + [:pre.code page-data] + [:br] + (ui/button "Copy to clipboard" + :on-click #(.writeText js/navigator.clipboard page-data))] + :success + false)))}})] + (flatten) + (remove nil?)))] + [:div.flex.flex-row + [:a.opacity-30.hover:opacity-100.page-op.mr-1 + {:title "Search in current page" + :on-click #(route-handler/go-to-search! :page)} + svg/search] + (ui/dropdown-with-links + (fn [{:keys [toggle-fn]}] + [:a.cp__vertial-menu-button + {:title "More options" + :on-click toggle-fn} + (svg/vertical-dots nil)]) + links + {:modal-class (util/hiccup->class + "origin-top-right.absolute.right-0.top-10.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.dropdown-overflow-auto.page-drop-options") + :z-index 1})]))]) + [:div + (when (and repo (not block?)) + (let [alias (db/get-page-alias-names repo page-name)] + (when (seq alias) + [:div.text-sm.ml-1.mb-4 {:key "page-file"} + [:span.opacity-50 "Alias: "] + (for [item alias] + [:a.ml-1.mr-1 {:href (rfe/href :page {:name item})} + item])]))) - (when-let [file-path (and (util/electron?) (page-handler/get-page-file-path))] - [{:title (t :page/open-in-finder) - :options {:on-click #(js/window.apis.showItemInFolder file-path)}} - {:title (t :page/open-with-default-app) - :options {:on-click #(js/window.apis.openPath file-path)}}]) - - (when-not contents? - {:title (t :page/delete) - :options {:on-click #(state/set-modal! (delete-page-dialog page-name))}}) - - (when (state/get-current-page) - {:title (t :export) - :options {:on-click #(state/set-modal! export/export-page)}}) - - (when (util/electron?) - {:title (t (if public? :page/make-private :page/make-public)) - :options {:on-click - (fn [] - (page-handler/update-public-attribute! - page-name - (if public? false true)) - (state/close-modal!))}}) - - (when developer-mode? - {:title "(Dev) Show page data" - :options {:on-click (fn [] - (let [page-data (with-out-str (pprint/pprint (db/pull (:db/id page))))] - (println page-data) - (notification/show! - [:div - [:pre.code page-data] - [:br] - (ui/button "Copy to clipboard" - :on-click #(.writeText js/navigator.clipboard page-data))] - :success - false)))}})] - (flatten) - (remove nil?)))] - [:div.flex.flex-row - [:a.opacity-30.hover:opacity-100.page-op.mr-1 - {:title "Search in current page" - :on-click #(route-handler/go-to-search! :page)} - svg/search] - (ui/dropdown-with-links - (fn [{:keys [toggle-fn]}] - [:a.cp__vertial-menu-button - {:title "More options" - :on-click toggle-fn} - (svg/vertical-dots nil)]) - links - {:modal-class (util/hiccup->class - "origin-top-right.absolute.right-0.top-10.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.dropdown-overflow-auto.page-drop-options") - :z-index 1})]))]) - [:div - ;; [:div.content - ;; (when (and file-path - ;; (not sidebar?) - ;; (not block?) - ;; (not (state/hide-file?)) - ;; (not config/publishing?)) - ;; [:div.text-sm.ml-1.mb-4.flex-1.inline-flex - ;; {:key "page-file"} - ;; [:span.opacity-50 {:style {:margin-top 2}} (t :file/file)] - ;; [:a.bg-base-2.px-1.ml-1.mr-3 {:style {:border-radius 4 - ;; :word-break "break-word"} - ;; :href (rfe/href :file {:path file-path})} - ;; file-path]])] - - (when (and repo (not block?)) - (let [alias (db/get-page-alias-names repo page-name)] - (when (seq alias) - [:div.text-sm.ml-1.mb-4 {:key "page-file"} - [:span.opacity-50 "Alias: "] - (for [item alias] - [:a.ml-1.mr-1 {:href (rfe/href :page {:name item})} - item])]))) - - (when (and block? (not sidebar?)) - (let [config {:id "block-parent" - :block? true}] - [:div.mb-4 - (block/block-parents config repo block-id format)])) + (when (and block? (not sidebar?)) + (let [config {:id "block-parent" + :block? true}] + [:div.mb-4 + (block/block-parents config repo block-id format)])) ;; blocks - (page-blocks-cp repo page file-path page-name page-original-name page-name sidebar? journal? block? block-id format)]] + (let [page (if block? + (db/entity repo [:block/uuid block-id]) + page)] + (page-blocks-cp repo page sidebar?))]] (when-not block? (today-queries repo today? sidebar?)) diff --git a/src/main/frontend/components/page.cljs.~e600c29046589a778b9a28a2122606358d7ab458~ b/src/main/frontend/components/page.cljs.~e600c29046589a778b9a28a2122606358d7ab458~ new file mode 100644 index 0000000000..42b44074b3 --- /dev/null +++ b/src/main/frontend/components/page.cljs.~e600c29046589a778b9a28a2122606358d7ab458~ @@ -0,0 +1,497 @@ +(ns frontend.components.page + (:require [rum.core :as rum] + [frontend.util :as util :refer-macros [profile]] + [frontend.util.marker :as marker] + [frontend.tools.html-export :as html-export] + [frontend.handler.file :as file] + [frontend.handler.page :as page-handler] + [frontend.handler.ui :as ui-handler] + [frontend.handler.common :as common-handler] + [frontend.handler.route :as route-handler] + [frontend.handler.graph :as graph-handler] + [frontend.handler.notification :as notification] + [frontend.handler.editor :as editor-handler] + [frontend.state :as state] + [clojure.string :as string] + [frontend.components.block :as block] + [frontend.components.editor :as editor] + [frontend.components.reference :as reference] + [frontend.components.svg :as svg] + [frontend.components.export :as export] + [frontend.extensions.graph-2d :as graph-2d] + [frontend.ui :as ui] + [frontend.components.content :as content] + [frontend.config :as config] + [frontend.db :as db] + [frontend.db.model :as model] + [frontend.db.utils :as db-utils] + [frontend.mixins :as mixins] + [frontend.db-mixins :as db-mixins] + [goog.dom :as gdom] + [goog.object :as gobj] + [frontend.utf8 :as utf8] + [frontend.date :as date] + [frontend.graph :as graph] + [frontend.format.mldoc :as mldoc] + [cljs-time.coerce :as tc] + [cljs-time.core :as t] + [cljs.pprint :as pprint] + [frontend.context.i18n :as i18n] + [reitit.frontend.easy :as rfe] + [frontend.text :as text] + [frontend.modules.shortcut.core :as shortcut] + [frontend.handler.block :as block-handler])) + +(defn- get-page-name + [state] + (let [route-match (first (:rum/args state))] + (get-in route-match [:parameters :path :name]))) + +(defn- get-blocks + [repo page-name page-original-name block? block-id] + (when page-name + (if block? + (db/get-block-and-children repo block-id) + (do + (page-handler/add-page-to-recent! repo page-original-name) + (db/get-page-blocks repo page-name))))) + +(defn- open-first-block! + [state] + (let [blocks (nth (:rum/args state) 1) + block (first blocks)] + (when (:block/dummy? block) + (editor-handler/edit-block! block :max (:block/format block) (:block/uuid block)))) + state) +(rum/defc page-blocks-inner < + {:did-mount open-first-block! + :did-update open-first-block!} + [page-name page-blocks hiccup sidebar?] + [:div.page-blocks-inner + (rum/with-key + (content/content page-name + {:hiccup hiccup + :sidebar? sidebar?}) + (str page-name "-hiccup"))]) + +(declare page) + +(defn- get-page-format + [page-name] + (let [block? (util/uuid-string? page-name) + block-id (and block? (uuid page-name)) + page (if block-id + (:block/name (:block/page (db/entity [:block/uuid block-id]))) + page-name)] + (db/get-page-format page))) + +(rum/defc page-blocks-cp < rum/reactive + db-mixins/query + [repo page-e sidebar?] + (let [page-name (or (:block/name page-e) + (str (:block/uuid page-e))) + page-original-name (or (:block/original-name page-e) page-name) + format (get-page-format page-name) + journal? (db/journal-page? page-name) + block? (util/uuid-string? page-name) + block-id (and block? (uuid page-name)) + raw-page-blocks (get-blocks repo page-name page-original-name block? block-id) + page-blocks (block-handler/with-dummy-block raw-page-blocks format + (if (empty? raw-page-blocks) + {:block/page {:db/id (:db/id page-e)} + :block/file {:db/id (:db/id (:block/file page-e))}}) + {:journal? journal? + :page-name page-name}) + hiccup-config {:id (if block? (str block-id) page-name) + :sidebar? sidebar? + :block? block? + :editor-box editor/box + :page page} + hiccup-config (common-handler/config-with-document-mode hiccup-config) + hiccup (block/->hiccup page-blocks hiccup-config {})] + (page-blocks-inner page-name page-blocks hiccup sidebar?))) + +(defn contents-page + [page] + (when-let [repo (state/get-current-repo)] + (page-blocks-cp repo page true))) + +(rum/defc today-queries < rum/reactive + [repo today? sidebar?] + (when (and today? (not sidebar?)) + (let [queries (state/sub [:config repo :default-queries :journals])] + (when (seq queries) + [:div#today-queries.mt-10 + (for [{:keys [title] :as query} queries] + (rum/with-key + (block/custom-query {:attr {:class "mt-10"} + :editor-box editor/box + :page page} query) + (str repo "-custom-query-" (:query query))))])))) + +(defn- delete-page! + [page-name] + (page-handler/delete! page-name + (fn [] + (notification/show! (str "Page " page-name " was deleted successfully!") + :success))) + (state/close-modal!) + (route-handler/redirect-to-home!)) + +(defn delete-page-dialog + [page-name] + (fn [close-fn] + (rum/with-context [[t] i18n/*tongue-context*] + [:div + [:div.sm:flex.sm:items-start + [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10 + [:svg.h-6.w-6.text-red-600 + {:stroke "currentColor", :view-box "0 0 24 24", :fill "none"} + [:path + {:d + "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" + :stroke-width "2" + :stroke-linejoin "round" + :stroke-linecap "round"}]]] + [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left + [:h3#modal-headline.text-lg.leading-6.font-medium + (t :page/delete-confirmation)]]] + + [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse + [:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto + [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5 + {:type "button" + :on-click (fn [] + (delete-page! page-name))} + (t :yes)]] + [:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto + [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5 + {:type "button" + :on-click close-fn} + (t :cancel)]]]]))) + +(rum/defcs rename-page-dialog-inner < + (shortcut/disable-all-shortcuts) + (rum/local "" ::input) + [state title page-name close-fn] + (let [input (get state ::input)] + (rum/with-context [[t] i18n/*tongue-context*] + [:div.w-full.sm:max-w-lg.sm:w-96 + [:div.sm:flex.sm:items-start + [:div.mt-3.text-center.sm:mt-0.sm:text-left + [:h3#modal-headline.text-lg.leading-6.font-medium + (t :page/rename-to title)]]] + + [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2 + {:auto-focus true + :on-change (fn [e] + (reset! input (util/evalue e)))}] + + [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse + [:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto + [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5 + {:type "button" + :on-click (fn [] + (let [value (string/trim @input)] + (when-not (string/blank? value) + (page-handler/rename! page-name value) + (state/close-modal!))))} + (t :submit)]] + [:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto + [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5 + {:type "button" + :on-click close-fn} + (t :cancel)]]]]))) + +(defn rename-page-dialog + [title page-name] + (fn [close-fn] + (rename-page-dialog-inner title page-name close-fn))) + +(defn tagged-pages + [repo tag] + (let [pages (db/get-tag-pages repo tag)] + (when (seq pages) + [:div.references.mt-6.flex-1.flex-row + [:div.content + (ui/foldable + [:h2.font-bold.opacity-50 (util/format "Pages tagged with \"%s\"" tag)] + [:ul.mt-2 + (for [[original-name name] pages] + [:li {:key (str "tagged-page-" name)} + [:a {:href (rfe/href :page {:name name})} + original-name]])] false)]]))) + +;; A page is just a logical block +(rum/defcs page < rum/reactive + [state {:keys [repo page-name preview?] :as option}] + (when-let [path-page-name (or page-name + (get-page-name state) + (state/get-current-page))] + (let [current-repo (state/sub :git/current-repo) + repo (or repo current-repo) + page-name (string/lower-case path-page-name) + block? (util/uuid-string? page-name) + block-id (and block? (uuid page-name)) + format (let [page (if block-id + (:block/name (:block/page (db/entity [:block/uuid block-id]))) + page-name)] + (db/get-page-format page)) + journal? (db/journal-page? page-name) + sidebar? (:sidebar? option)] + (rum/with-context [[t] i18n/*tongue-context*] + (let [route-page-name path-page-name + page (if block? + (->> (:db/id (:block/page (db/entity repo [:block/uuid block-id]))) + (db/entity repo)) + (db/entity repo [:block/name page-name])) + ;; TODO: replace page with frontend.format.block/page->map + page (if page page (do + (db/transact! repo [{:block/name page-name + :block/original-name path-page-name + :block/uuid (db/new-block-id)}]) + (db/entity repo [:block/name page-name]))) + {:keys [title] :as properties} (:block/properties page) + page-name (:block/name page) + page-original-name (:block/original-name page) + title (or title page-original-name page-name) + today? (and + journal? + (= page-name (string/lower-case (date/journal-name)))) + developer-mode? (state/sub [:ui/developer-mode?]) + public? (true? (:public properties))] + [:div.flex-1.page.relative (if (seq (:block/tags page)) + (let [page-names (model/get-page-names-by-ids (map :db/id (:block/tags page)))] + {:data-page-tags (text/build-data-value page-names)}) + {}) + [:div.relative + (when (and (not sidebar?) + (not block?)) + [:div.flex.flex-row.space-between + [:div.flex-1.flex-row + [:a {:on-click (fn [e] + (.preventDefault e) + (when (gobj/get e "shiftKey") + (when-let [page (db/pull repo '[*] [:block/name page-name])] + (state/sidebar-add-block! + repo + (:db/id page) + :page + {:page page}))))} + [:h1.title {:style {:margin-left -2}} + (if page-original-name + (if (and (string/includes? page-original-name "[[") + (string/includes? page-original-name "]]")) + (let [ast (mldoc/->edn page-original-name (mldoc/default-config format))] + (block/markup-element-cp {} (ffirst ast))) + page-original-name) + (or + page-name + path-page-name))]]] + (when (not config/publishing?) + (let [contents? (= (string/lower-case (str page-name)) "contents") + links (fn [] (->> + [(when-not contents? + {:title (t :page/add-to-contents) + :options {:on-click (fn [] (page-handler/handle-add-page-to-contents! page-original-name))}}) + + {:title "Go to presentation mode" + :options {:on-click (fn [] + (state/sidebar-add-block! + repo + (:db/id page) + :page-presentation + {:page page}))}} + (when-not contents? + {:title (t :page/rename) + :options {:on-click #(state/set-modal! (rename-page-dialog title page-name))}}) + + (when-let [file-path (and (util/electron?) (page-handler/get-page-file-path))] + [{:title (t :page/open-in-finder) + :options {:on-click #(js/window.apis.showItemInFolder file-path)}} + {:title (t :page/open-with-default-app) + :options {:on-click #(js/window.apis.openPath file-path)}}]) + + (when-not contents? + {:title (t :page/delete) + :options {:on-click #(state/set-modal! (delete-page-dialog page-name))}}) + + (when (state/get-current-page) + {:title (t :export) + :options {:on-click #(state/set-modal! export/export-page)}}) + + (when (util/electron?) + {:title (t (if public? :page/make-private :page/make-public)) + :options {:on-click + (fn [] + (page-handler/update-public-attribute! + page-name + (if public? false true)) + (state/close-modal!))}}) + + (when developer-mode? + {:title "(Dev) Show page data" + :options {:on-click (fn [] + (let [page-data (with-out-str (pprint/pprint (db/pull (:db/id page))))] + (println page-data) + (notification/show! + [:div + [:pre.code page-data] + [:br] + (ui/button "Copy to clipboard" + :on-click #(.writeText js/navigator.clipboard page-data))] + :success + false)))}})] + (flatten) + (remove nil?)))] + [:div.flex.flex-row + [:a.opacity-30.hover:opacity-100.page-op.mr-1 + {:title "Search in current page" + :on-click #(route-handler/go-to-search! :page)} + svg/search] + (ui/dropdown-with-links + (fn [{:keys [toggle-fn]}] + [:a.cp__vertial-menu-button + {:title "More options" + :on-click toggle-fn} + (svg/vertical-dots nil)]) + links + {:modal-class (util/hiccup->class + "origin-top-right.absolute.right-0.top-10.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.dropdown-overflow-auto.page-drop-options") + :z-index 1})]))]) + [:div + (when (and repo (not block?)) + (let [alias (db/get-page-alias-names repo page-name)] + (when (seq alias) + [:div.text-sm.ml-1.mb-4 {:key "page-file"} + [:span.opacity-50 "Alias: "] + (for [item alias] + [:a.ml-1.mr-1 {:href (rfe/href :page {:name item})} + item])]))) + + (when (and block? (not sidebar?)) + (let [config {:id "block-parent" + :block? true}] + [:div.mb-4 + (block/block-parents config repo block-id format)])) + + ;; blocks + (let [page (if block? + (db/entity repo [:block/uuid block-id]) + page)] + (page-blocks-cp repo page sidebar?))]] + + (when-not block? + (today-queries repo today? sidebar?)) + + (tagged-pages repo page-name) + + ;; referenced blocks + [:div {:key "page-references"} + (rum/with-key + (reference/references route-page-name false) + (str route-page-name "-refs"))] + + ;; TODO: or we can lazy load them + (when-not sidebar? + [:div {:key "page-unlinked-references"} + (reference/unlinked-references route-page-name)])]))))) + +(defonce layout (atom [js/window.outerWidth js/window.outerHeight])) + +(defonce graph-ref (atom nil)) +(defonce show-journal? (atom false)) + +(rum/defcs global-graph < rum/reactive + (mixins/event-mixin + (fn [state] + (mixins/listen state js/window "resize" + (fn [e] + (reset! layout [js/window.outerWidth js/window.outerHeight]))))) + [state] + (let [theme (state/sub :ui/theme) + sidebar-open? (state/sub :ui/sidebar-open?) + [width height] (rum/react layout) + dark? (= theme "dark") + graph (graph-handler/build-global-graph theme (rum/react show-journal?))] + (rum/with-context [[t] i18n/*tongue-context*] + [:div.relative#global-graph + (if (seq (:nodes graph)) + (graph-2d/graph + (graph/build-graph-opts + graph + dark? + {:width (if (and (> width 1280) sidebar-open?) + (- width 24 600) + (- width 24)) + :height height + :ref (fn [v] (reset! graph-ref v)) + :ref-atom graph-ref})) + [:div.ls-center.mt-20 + [:p.opacity-70.font-medium "Empty"]]) + [:div.absolute.top-10.left-5 + [:div.flex.flex-col + [:a.text-sm.font-medium + {:on-click (fn [_e] + (swap! show-journal? not))} + (str (t :page/show-journals) + (if @show-journal? " (ON)"))]]]]))) + +(rum/defc all-pages < rum/reactive + ;; {:did-mount (fn [state] + ;; (let [current-repo (state/sub :git/current-repo)] + ;; (js/setTimeout #(db/remove-orphaned-pages! current-repo) 0)) + ;; state)} + [] + (let [current-repo (state/sub :git/current-repo)] + (rum/with-context [[t] i18n/*tongue-context*] + [:div.flex-1 + [:h1.title (t :all-pages)] + (when current-repo + (let [pages (page-handler/get-pages-with-modified-at current-repo)] + [:table.table-auto + [:thead + [:tr + [:th (t :block/name)] + [:th (t :file/last-modified-at)]]] + [:tbody + (for [page pages] + [:tr {:key page} + [:td [:a {:on-click (fn [e] + (let [repo (state/get-current-repo) + page (db/pull repo '[*] [:block/name (string/lower-case page)])] + (when (gobj/get e "shiftKey") + (state/sidebar-add-block! + repo + (:db/id page) + :page + {:page page})))) + :href (rfe/href :page {:name page})} + page]] + [:td [:span.text-gray-500.text-sm + (t :file/no-data)]]])]]))]))) + +(rum/defcs new < rum/reactive + (rum/local "" ::title) + (mixins/event-mixin + (fn [state] + (mixins/on-enter state + :node (gdom/getElement "page-title") + :on-enter (fn [] + (let [title @(get state ::title)] + (when-not (string/blank? title) + (page-handler/create! title))))))) + [state] + (rum/with-context [[t] i18n/*tongue-context*] + (let [title (get state ::title)] + [:div#page-new.flex-1.flex-col {:style {:flex-wrap "wrap"}} + [:div.mt-10.mb-2 {:style {:font-size "1.5rem"}} + (t :page/new-title)] + [:input#page-title.focus:outline-none.ml-1 + {:style {:border "none" + :font-size "1.8rem" + :max-width 300} + :auto-focus true + :auto-complete "off" + :on-change (fn [e] + (reset! title (util/evalue e)))}]]))) diff --git a/src/main/frontend/components/search.cljs b/src/main/frontend/components/search.cljs index 1e356967dc..39046adf19 100644 --- a/src/main/frontend/components/search.cljs +++ b/src/main/frontend/components/search.cljs @@ -33,7 +33,7 @@ lc-content (string/lower-case content) lc-q (string/lower-case q)] (if (or (string/includes? lc-content lc-q) - (not (re-find #" " q))) + (not (util/safe-re-find #" " q))) (let [i (string/index-of lc-content lc-q) [before after] [(subs content 0 i) (subs content (+ i (count q)))]] [:p diff --git a/src/main/frontend/components/settings.cljs b/src/main/frontend/components/settings.cljs index 4adb93b926..143d007168 100644 --- a/src/main/frontend/components/settings.cljs +++ b/src/main/frontend/components/settings.cljs @@ -8,6 +8,7 @@ [frontend.handler.repo :as repo-handler] [frontend.handler.config :as config-handler] [frontend.handler.page :as page-handler] + [frontend.handler.route :as route-handler] [frontend.handler :as handler] [frontend.state :as state] [frontend.version :refer [version]] @@ -253,7 +254,12 @@ {:on-change (fn [e] (let [format (util/evalue e)] (when-not (string/blank? format) - (config-handler/set-config! :date-formatter format))))} + (config-handler/set-config! :date-formatter format) + (notification/show! + [:div "You need to re-index your graph to make the change works"] + :success) + (state/close-modal!) + (route-handler/redirect! {:to :repos}))))} (for [format (sort (date/journal-title-formatters))] [:option (cond-> {:key format} @@ -366,8 +372,7 @@ [:div.mt-1.sm:mt-0.sm:col-span-2 [:div.max-w-lg.rounded-md.sm:max-w-xs (ui/button (t :settings-page/clear) - :on-click (fn [] - (handler/clear-cache!)))]]]] + :on-click handler/clear-cache!)]]]] [:div.panel-wrap [:div.it.app-updater.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start diff --git a/src/main/frontend/components/svg.cljs b/src/main/frontend/components/svg.cljs index 12a7c259c7..b8251eca59 100644 --- a/src/main/frontend/components/svg.cljs +++ b/src/main/frontend/components/svg.cljs @@ -291,7 +291,6 @@ {:d "M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"}]])) - (rum/defc pinned [] [:svg.h-8.w-8.pinned @@ -529,3 +528,13 @@ "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" :clip-rule "evenodd" :fill-rule "evenodd"}]]) + +(def edit + [:svg.h-6.w-6 + {:stroke "currentColor", :viewbox "0 0 24 24", :fill "none"} + [:path + {:d + "M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z", + :stroke-width "2", + :stroke-linejoin "round", + :stroke-linecap "round"}]]) diff --git a/src/main/frontend/components/theme.css b/src/main/frontend/components/theme.css index 423e6b72aa..63266a86dc 100644 --- a/src/main/frontend/components/theme.css +++ b/src/main/frontend/components/theme.css @@ -92,119 +92,6 @@ html[data-theme='light'] { } } -html.is-electron { - --frame-top-height: 24px; - - .theme-inner { - } - - .cp__header { - height: 2.6rem; - background-color: var(--ls-primary-background-color); - top: 0; - } - - &.is-mac { - .cp__header { - height: calc(2.2rem + var(--frame-top-height)); - padding-top: var(--frame-top-height); - - &-logo { - height: var(--frame-top-height); - } - - &:before { - content: ' '; - position: fixed; - top: 0; - left: 0; - z-index: 8; - -webkit-app-region: drag; - width: 100%; - height: var(--frame-top-height); - } - } - - .cp__right-sidebar { - top: 4rem; - } - - &.is-fullscreen { - .cp__header { - padding-top: 0; - height: 2.6rem; - - &:before { - display: none; - } - } - } - } - - #search { - -webkit-app-region: drag; - - #search-wrapper { - -webkit-app-region: no-drag; - } - } - - .ls-window-frame-title-bar { - background-color: var(--ls-primary-background-color); - position: fixed; - left: 0; - right: 0; - z-index: 9; - height: var(--frame-top-height); - display: flex; - align-items: center; - justify-content: space-between; - user-select: none; - -webkit-app-region: drag; - - & > .l { - display: flex; - } - - & > .r { - & > .inner { - display: flex; - } - } - - & > .c { - font-size: 0.7rem; - } - - a.it { - padding: 0 2px; - cursor: pointer; - -webkit-app-region: no-drag; - - &:hover { - background-color: var(--ls-secondary-background-color); - } - - &:active { - background-color: var(--ls-primary-background-color); - } - - svg { - transform: scale(0.6); - color: var(--ls-primary-text-color); - cursor: pointer; - } - - &.maximize { - svg { - transform: scale(0.5) translateY(2px) translateX(1px); - opacity: 0.7; - } - } - } - } -} - html.locked-scroll { overflow: hidden !important; } diff --git a/src/main/frontend/config.cljs b/src/main/frontend/config.cljs index b5fbb7f22a..8cea8b1e73 100644 --- a/src/main/frontend/config.cljs +++ b/src/main/frontend/config.cljs @@ -101,7 +101,7 @@ (def mobile? (when-not util/node-test? - (re-find #"Mobi" js/navigator.userAgent))) + (util/safe-re-find #"Mobi" js/navigator.userAgent))) ;; TODO: protocol design for future formats support @@ -309,7 +309,7 @@ (defn local-asset? [s] - (re-find (re-pattern (str "^[./]*" local-assets-dir)) s)) + (util/safe-re-find (re-pattern (str "^[./]*" local-assets-dir)) s)) (defn get-local-asset-absolute-path [s] diff --git a/src/main/frontend/db.cljs b/src/main/frontend/db.cljs index dc1e11fb35..5fa71ce6e7 100644 --- a/src/main/frontend/db.cljs +++ b/src/main/frontend/db.cljs @@ -106,9 +106,9 @@ (d/listen! conn :persistence (fn [tx-report] (when-not (util/electron?) - (let [tx-id (get-tx-id tx-report)] - (state/set-last-transact-time! repo (util/time-ms)) - (persist-if-idle! repo))) + (let [tx-id (get-tx-id tx-report)] + (state/set-last-transact-time! repo (util/time-ms)) + (persist-if-idle! repo))) ;; rebuild search indices (let [data (:tx-data tx-report) diff --git a/src/main/frontend/db/model.cljs b/src/main/frontend/db/model.cljs index c52a14c047..c96c3eac9c 100644 --- a/src/main/frontend/db/model.cljs +++ b/src/main/frontend/db/model.cljs @@ -72,6 +72,7 @@ :block/updated-at :block/file :block/parent + :block/unordered {:block/page [:db/id :block/name :block/original-name :block/journal-day]} {:block/_parent ...}]) @@ -316,9 +317,11 @@ (defn get-page-format [page-name] - (when-let [file (:block/file (db-utils/entity [:block/name page-name]))] - (when-let [path (:file/path (db-utils/entity (:db/id file)))] - (format/get-format path)))) + (or + (when-let [file (:block/file (db-utils/entity [:block/name page-name]))] + (when-let [path (:file/path (db-utils/entity (:db/id file)))] + (format/get-format path))) + :markdown)) (defn page-alias-set [repo-url page] @@ -967,17 +970,14 @@ (when-let [conn (conn/get-conn repo)] (let [page-id (:db/id (db-utils/entity [:block/name page])) pattern (re-pattern (str "(?i)(?> (react/q repo [:block/unlinked-refs page-id] {} - '[:find [(pull ?block ?block-attrs) ...] - :in $ ?pattern ?block-attrs ?page-id - :where - [?block :block/content ?content] - [?block :block/page ?page] - [(not= ?page ?page-id)] - [(re-find ?pattern ?content)]] - pattern - block-attrs - page-id) + (->> (react/q repo [:block/unlinked-refs page-id] + {:query-fn (fn [db] + (let [ids (->> (d/datoms db :aevt :block/content) + (filter #(re-find pattern (:v %))) + (map :e)) + result (d/pull-many db block-attrs ids)] + (remove (fn [block] (= page-id (:db/id (:block/page block)))) result)))} + nil) react (sort-by-left-recursive) db-utils/group-by-page))))) diff --git a/src/main/frontend/db/query_dsl.cljs b/src/main/frontend/db/query_dsl.cljs index 10809f24dc..7dad7d0b4b 100644 --- a/src/main/frontend/db/query_dsl.cljs +++ b/src/main/frontend/db/query_dsl.cljs @@ -218,8 +218,11 @@ (and (= 'property fe) (= 3 (count e))) - (let [v (some-> (name (nth e 2)) - (text/page-ref-un-brackets!)) + (let [v (nth e 2) + v (if (or (string? v) (symbol? v)) + (some-> (name v) + (text/page-ref-un-brackets!)) + v) sym (if (= current-filter 'or) '?v (uniq-symbol counter "?v"))] @@ -321,7 +324,7 @@ (remove string/blank?) (map (fn [x] (if (or (contains? #{"+" "-"} (first x)) - (and (re-find #"\d" (first x)) + (and (util/safe-re-find #"\d" (first x)) (some #(string/ends-with? x %) ["y" "m" "d" "h" "min"]))) (keyword (name x)) x))) diff --git a/src/main/frontend/db/query_react.cljs b/src/main/frontend/db/query_react.cljs index 15c2b734b3..4f8cf0f6a6 100644 --- a/src/main/frontend/db/query_react.cljs +++ b/src/main/frontend/db/query_react.cljs @@ -26,12 +26,12 @@ (= :current-page input) (string/lower-case (state/get-current-page)) (and (keyword? input) - (re-find #"^\d+d(-before)?$" (name input))) + (util/safe-re-find #"^\d+d(-before)?$" (name input))) (let [input (name input) days (util/parse-int (subs input 0 (dec (count input))))] (date->int (t/minus (t/today) (t/days days)))) (and (keyword? input) - (re-find #"^\d+d(-after)?$" (name input))) + (util/safe-re-find #"^\d+d(-after)?$" (name input))) (let [input (name input) days (util/parse-int (subs input 0 (dec (count input))))] (date->int (t/plus (t/today) (t/days days)))) diff --git a/src/main/frontend/dicts.cljs b/src/main/frontend/dicts.cljs index 1447803936..9734e27e61 100644 --- a/src/main/frontend/dicts.cljs +++ b/src/main/frontend/dicts.cljs @@ -243,7 +243,7 @@ :publishing "Publishing" :export "Export" :export-json "Export as JSON" - :export-markdown "Export as Markdown" + :export-markdown "Export as standard Markdown (no block properties)" :export-public-pages "Export public pages" :export-edn "Export as EDN" :convert-markdown "Convert Markdown headings to unordered lists (# -> -)" diff --git a/src/main/frontend/extensions/html_parser.cljs b/src/main/frontend/extensions/html_parser.cljs index a478c0eea6..29d8fdb1b5 100644 --- a/src/main/frontend/extensions/html_parser.cljs +++ b/src/main/frontend/extensions/html_parser.cljs @@ -77,7 +77,7 @@ :a (let [href (:href attrs) title (:title attrs) label (map-join children) - has-img-tag? (re-find #"\[:img" (str x))] + has-img-tag? (util/safe-re-find #"\[:img" (str x))] (if has-img-tag? (export-hiccup x) (case format diff --git a/src/main/frontend/format/block.cljs b/src/main/frontend/format/block.cljs index 131d597989..31612f0ce4 100644 --- a/src/main/frontend/format/block.cljs +++ b/src/main/frontend/format/block.cljs @@ -159,7 +159,7 @@ (atom #{"background-color" "background_color"})) (defn extract-properties - [[_ properties] _start-pos _end-pos] + [properties] (let [properties (into {} properties) page-refs (->> (map (fn [v] @@ -170,40 +170,46 @@ (filter (fn [s] (= \# (first s)))) (map (fn [s] (subs s 1))))] (concat page-refs tags)))) - (vals properties)) + (vals properties)) (apply concat) (remove string/blank?)) properties (->> properties (medley/map-kv (fn [k v] - (if (coll? v) - [(keyword k) v] - (let [k (name k) - v (string/trim v) - k (string/replace k " " "-") - k (string/lower-case k) - v (cond - (= v "true") - true - (= v "false") - false + (let [v (if (coll? v) + v + (let [k (name k) + v (string/trim v) + k (string/replace k " " "-") + k (string/lower-case k) + v (cond + (= v "true") + true + (= v "false") + false - (re-find #"^\d+$" v) - (util/safe-parse-int v) + (util/safe-re-find #"^\d+$" v) + (util/safe-parse-int v) - (and (= "\"" (first v) (last v))) ; wrapped in "" - (string/trim (subs v 1 (dec (count v)))) + (and (= "\"" (first v) (last v))) ; wrapped in "" + (string/trim (subs v 1 (dec (count v)))) - (contains? @non-parsing-properties (string/lower-case k)) - v + (contains? @non-parsing-properties (string/lower-case k)) + v - :else - (let [v' v] - (if (and k v' - (contains? config/markers k) - (util/safe-parse-int v')) - (util/safe-parse-int v') - (text/split-page-refs-without-brackets v' true))))] - [(keyword k) v])))))] + :else + (if (and k v + (contains? config/markers k) + (util/safe-parse-int v)) + (util/safe-parse-int v) + (text/split-page-refs-without-brackets v true)))] + v)) + k (keyword k) + v (if (and + (string? v) + (contains? #{:alias :aliases :tags} k)) + (set [v]) + v)] + [k v]))))] {:properties properties :page-refs page-refs})) @@ -445,7 +451,7 @@ (recur headings (conj block-body ["Paragraph" other-body]) (rest blocks) timestamps' properties last-pos last-level children)) (property/properties-ast? block) - (let [properties (extract-properties block start_pos end_pos)] + (let [properties (extract-properties (second block))] (recur headings block-body (rest blocks) timestamps properties last-pos last-level children)) (heading-block? block) diff --git a/src/main/frontend/format/mldoc.cljs b/src/main/frontend/format/mldoc.cljs index 5ed4db15c4..0052f2f9e4 100644 --- a/src/main/frontend/format/mldoc.cljs +++ b/src/main/frontend/format/mldoc.cljs @@ -118,15 +118,21 @@ directive? (fn [[item _]] (= "directive" (string/lower-case (first item)))) grouped-ast (group-by directive? original-ast) - [directive-ast other-ast] - [(get grouped-ast true) (get grouped-ast false)] - properties (->> (map first directive-ast) - (map (fn [[_ k v]] - (let [k (keyword (string/lower-case k)) - v (if (contains? #{:title :description :roam_tags} k) - v - (text/split-page-refs-without-brackets v true))] - [k v]))) + directive-ast (get grouped-ast true) + [properties-ast other-ast] (if (= "Property_Drawer" (ffirst ast)) + [(last (first ast)) + (rest original-ast)] + [(->> (map first directive-ast) + (map rest)) + (get grouped-ast false)]) + properties (->> + properties-ast + (map (fn [[k v]] + (let [k (keyword (string/lower-case k)) + v (if (contains? #{:title :description :roam_tags} k) + v + (text/split-page-refs-without-brackets v true))] + [k v]))) (reverse) (into {})) macro-properties (filter (fn [x] (= :macro (first x))) properties) @@ -190,6 +196,12 @@ [block pos-meta]) [block pos-meta])) ast))) +(defn block-with-title? + [type] + (contains? #{"Paragraph" + "Raw_Html" + "Hiccup"} type)) + (defn ->edn [content config] (try diff --git a/src/main/frontend/fs/watcher_handler.cljs b/src/main/frontend/fs/watcher_handler.cljs index f2d5dac43b..cee80eb56b 100644 --- a/src/main/frontend/fs/watcher_handler.cljs +++ b/src/main/frontend/fs/watcher_handler.cljs @@ -23,7 +23,9 @@ (when-not (db/file-exists? repo path) (let [_ (file-handler/alter-file repo path content {:re-render-root? true :from-disk? true})] - (db/set-file-last-modified-at! repo path mtime))) + (db/set-file-last-modified-at! repo path mtime) + ;; return nil, otherwise the entire db will be transfered by ipc + nil)) (and (= "change" type) (not (db/file-exists? repo path))) @@ -34,7 +36,8 @@ (> mtime last-modified-at))) (let [_ (file-handler/alter-file repo path content {:re-render-root? true :from-disk? true})] - (db/set-file-last-modified-at! repo path mtime)) + (db/set-file-last-modified-at! repo path mtime) + nil) (contains? #{"add" "change" "unlink"} type) nil diff --git a/src/main/frontend/handler.cljs b/src/main/frontend/handler.cljs index 9ba763e6ef..46452511e6 100644 --- a/src/main/frontend/handler.cljs +++ b/src/main/frontend/handler.cljs @@ -28,7 +28,9 @@ [frontend.handler.common :as common-handler] [electron.listener :as el] [electron.ipc :as ipc] - [frontend.version :as version])) + [frontend.version :as version] + [frontend.components.page :as page] + [frontend.components.editor :as editor])) (defn- watch-for-date! [] @@ -157,10 +159,16 @@ (ipc/ipc "clearCache"))] (js/window.location.reload))) +(defn- register-components-fns! + [] + (state/set-page-blocks-cp! page/page-blocks-cp) + (state/set-editor-cp! editor/box)) + (defn start! [render] (let [{:keys [me logged? repos]} (get-me-and-repos)] (when me (state/set-state! :me me)) + (register-components-fns!) (state/set-db-restoring! true) (render) (on-load-events) diff --git a/src/main/frontend/handler/block.cljs b/src/main/frontend/handler/block.cljs index cfd17078a5..b87ef7f915 100644 --- a/src/main/frontend/handler/block.cljs +++ b/src/main/frontend/handler/block.cljs @@ -39,7 +39,7 @@ (if (seq blocks) blocks (let [page-block (when page-name (db/pull [:block/name (string/lower-case page-name)])) - create-title-property? (util/include-windows-reserved-chars? page-name) + create-title-property? (and page-name (util/include-windows-reserved-chars? page-name)) content (if create-title-property? (let [title (or (:block/original-name page-block) (:block/name page-block)) diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index c709f5ea59..4edbebfe77 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/src/main/frontend/handler/editor.cljs @@ -301,7 +301,7 @@ top-level? (= parent page) markdown-heading? (and (= format :markdown) (= "Heading" first-elem-type)) - heading? (= "Paragraph" first-elem-type) + block-with-title? (mldoc/block-with-title? first-elem-type) content (string/triml content) content (string/replace content (util/format "((%s))" (str uuid)) "") [content content'] (cond @@ -316,7 +316,7 @@ [content (str (config/get-block-pattern format) " " content)]) :else - (let [content' (str (config/get-block-pattern format) (if heading? " " "\n") content)] + (let [content' (str (config/get-block-pattern format) (if block-with-title? " " "\n") content)] [content content'])) block (assoc block :block/content content') block (apply dissoc block (remove #{:block/pre-block?} db-schema/retract-attributes)) @@ -563,7 +563,7 @@ (defn- with-timetracking-properties [block value] - (let [new-marker (first (re-find marker/bare-marker-pattern (or value ""))) + (let [new-marker (first (util/safe-re-find marker/bare-marker-pattern (or value ""))) new-marker (if new-marker (string/lower-case (string/trim new-marker))) new-marker? (and new-marker @@ -695,7 +695,7 @@ format (or (db/get-page-format (state/get-current-page)) (state/get-preferred-format)) cond-fn (fn [marker] (or (and (= :markdown format) - (re-find (re-pattern (str "#*\\s*" marker)) content)) + (util/safe-re-find (re-pattern (str "#*\\s*" marker)) content)) (util/starts-with? content "TODO"))) [new-content marker] (cond (cond-fn "TODO") @@ -980,16 +980,22 @@ zip/up (zip/append-child [block])))] loc**)) (zip/vector-zip []) blocks)] - (zip/root loc))) + + (clojure.walk/postwalk (fn [e] (if (map? e) (dissoc e :level) e)) (zip/root loc)))) (defn- compose-copied-blocks-contents-&-block-tree [repo block-ids] (let [blocks (db-utils/pull-many repo '[*] (mapv (fn [id] [:block/uuid id]) block-ids)) - unordered? (:block/unordered (first blocks)) - format (:block/format (first blocks)) - level-blocks-map (blocks-with-level blocks) + blocks* (flatten + (mapv (fn [b] (if (:collapsed (:block/properties b)) + (vec (tree/sort-blocks (db/get-block-children repo (:block/uuid b)) b)) + [b])) blocks)) + block-ids* (mapv :block/uuid blocks*) + unordered? (:block/unordered (first blocks*)) + format (:block/format (first blocks*)) + level-blocks-map (blocks-with-level blocks*) level-blocks-uuid-map (into {} (mapv (fn [b] [(:block/uuid b) b]) (vals level-blocks-map))) - level-blocks (mapv (fn [uuid] (get level-blocks-uuid-map uuid)) block-ids) + level-blocks (mapv (fn [uuid] (get level-blocks-uuid-map uuid)) block-ids*) tree (blocks-vec->tree level-blocks) contents (mapv (fn [block] @@ -1565,7 +1571,7 @@ (defn get-matched-commands [input] (try - (let [edit-content (gobj/get input "value") + (let [edit-content (or (gobj/get input "value") "") pos (util/get-input-pos input) last-slash-caret-pos (:pos @*slash-caret-pos) last-command (and last-slash-caret-pos (subs edit-content last-slash-caret-pos pos))] @@ -2525,7 +2531,7 @@ (let [tree (->> (block/extract-blocks (mldoc/->edn text (mldoc/default-config format)) text true format)) - min-level (apply min (mapv #(:block/level %) tree)) + min-level (apply min (mapv :block/level tree)) prefix-level (if (> min-level 1) (- min-level 1) 0) tree* (->> tree (mapv #(assoc % :level (- (:block/level %) prefix-level))) @@ -2539,7 +2545,7 @@ (string/join "\n" (mapv (fn [p] (->> (string/trim p) ((fn [p] - (if (re-find (if (= format :org) + (if (util/safe-re-find (if (= format :org) #"\s*\*+\s+" #"\s*-\s+") p) p @@ -2569,9 +2575,9 @@ ;; from external (let [format (or (db/get-page-format (state/get-current-page)) :markdown)] (match [format - (nil? (re-find #"(?m)^\s*(?:[-+*]|#+)\s+" text)) - (nil? (re-find #"(?m)^\s*\*+\s+" text)) - (nil? (re-find #"(?:\r?\n){2,}" text))] + (nil? (util/safe-re-find #"(?m)^\s*(?:[-+*]|#+)\s+" text)) + (nil? (util/safe-re-find #"(?m)^\s*\*+\s+" text)) + (nil? (util/safe-re-find #"(?:\r?\n){2,}" text))] [:markdown false _ _] (do (paste-text-parseable format text) @@ -2609,7 +2615,7 @@ (string? existed-file-path) (not util/mac?) (not util/win32?)) ; FIXME: linux - (when (re-find #"^(/[^/ ]*)+/?$" existed-file-path) + (when (util/safe-re-find #"^(/[^/ ]*)+/?$" existed-file-path) existed-file-path) existed-file-path) has-file-path? (not (string/blank? existed-file-path)) diff --git a/src/main/frontend/handler/editor/keyboards.cljs b/src/main/frontend/handler/editor/keyboards.cljs index 0f4f8edf10..89b6638bcb 100644 --- a/src/main/frontend/handler/editor/keyboards.cljs +++ b/src/main/frontend/handler/editor/keyboards.cljs @@ -21,11 +21,7 @@ (let [{:keys [on-hide format value block id repo dummy?]} (editor-handler/get-state)] (when on-hide (on-hide value event)) - (when - (or (= event :esc) - (= event :visibilitychange) - (and (= event :click) - (not (editor-handler/auto-complete?)))) + (when (contains? #{:esc :visibilitychange :click} event) (state/clear-edit!)))))) :node (gdom/getElement id) ;; :visibilitychange? true diff --git a/src/main/frontend/handler/editor/lifecycle.cljs b/src/main/frontend/handler/editor/lifecycle.cljs index 973755266b..68a5aaa7ab 100644 --- a/src/main/frontend/handler/editor/lifecycle.cljs +++ b/src/main/frontend/handler/editor/lifecycle.cljs @@ -10,7 +10,6 @@ [frontend.handler.notification :as notification] [frontend.db :as db] [frontend.date :as date] - [frontend.handler.file :as file] [promesa.core :as p])) (defn did-mount! diff --git a/src/main/frontend/handler/export.cljs b/src/main/frontend/handler/export.cljs index 3256c442a3..3c83feda19 100644 --- a/src/main/frontend/handler/export.cljs +++ b/src/main/frontend/handler/export.cljs @@ -163,7 +163,8 @@ (let [path (string/lower-case path)] (or (string/ends-with? path ".md") (string/ends-with? path ".markdown")))) - (get-file-contents repo {:init-level 1 :heading-to-list? true})))) + (get-file-contents repo {:init-level 1 + :heading-to-list? true})))) (defn- get-embed-and-refs-blocks-pages-aux [] (let [mem (atom {})] diff --git a/src/main/frontend/handler/extract.cljs b/src/main/frontend/handler/extract.cljs index c7faae923c..0392277276 100644 --- a/src/main/frontend/handler/extract.cljs +++ b/src/main/frontend/handler/extract.cljs @@ -28,8 +28,6 @@ (map string/lower-case) (distinct)))) - - (defn get-page-name [file ast] ;; headline @@ -191,7 +189,9 @@ (fn [{:file/keys [path content]} contents] (println "Parsing : " path) (when content - (let [utf8-content (utf8/encode content)] + ;; TODO: remove `text/scheduled-deadline-dash->star` once migration is done + (let [content (text/scheduled-deadline-dash->star content) + utf8-content (utf8/encode content)] (extract-blocks-pages repo-url path content utf8-content))))) (remove empty?))] (when (seq result) diff --git a/src/main/frontend/handler/history.cljs b/src/main/frontend/handler/history.cljs index 55221128b6..059cd42fca 100644 --- a/src/main/frontend/handler/history.cljs +++ b/src/main/frontend/handler/history.cljs @@ -1,7 +1,6 @@ (ns frontend.handler.history (:require [frontend.state :as state] [frontend.db :as db] - [frontend.handler.file :as file] [frontend.handler.editor :as editor] [frontend.handler.ui :as ui-handler] [promesa.core :as p] diff --git a/src/main/frontend/handler/page.cljs b/src/main/frontend/handler/page.cljs index 58d8e73762..3d4deb433c 100644 --- a/src/main/frontend/handler/page.cljs +++ b/src/main/frontend/handler/page.cljs @@ -7,7 +7,6 @@ [frontend.config :as config] [frontend.handler.common :as common-handler] [frontend.handler.route :as route-handler] - [frontend.handler.file :as file-handler] [frontend.handler.repo :as repo-handler] [frontend.handler.editor :as editor-handler] [frontend.handler.web.nfs :as web-nfs] @@ -73,7 +72,7 @@ tx (block/page-name->map title true) format (state/get-preferred-format) page-entity [:block/uuid (:block/uuid tx)] - create-title-property? (util/include-windows-reserved-chars? title) + create-title-property? (and title (util/include-windows-reserved-chars? title)) default-properties (default-properties-block title format page-entity) empty-block {:block/uuid (db/new-block-id) :block/left [:block/uuid (:block/uuid default-properties)] @@ -440,15 +439,21 @@ (fn [chosen _click?] (state/set-editor-show-page-search! false) (let [wrapped? (= "[[" (util/safe-subs edit-content (- pos 2) pos)) - chosen (if (and (re-find #"\s+" chosen) (not wrapped?)) + chosen (if (and (util/safe-re-find #"\s+" chosen) (not wrapped?)) (util/format "[[%s]]" chosen) - chosen)] + chosen) + q (if @editor-handler/*selected-text "" q) + [last-pattern forward-pos] (if wrapped? + [q 3] + (if (= \# (first q)) + [(subs q 1) 1] + [q 2])) + last-pattern (str "#" (when wrapped? "[[") last-pattern)] (editor-handler/insert-command! id (str "#" (when wrapped? "[[") chosen) format - {:last-pattern (let [q (if @editor-handler/*selected-text "" q)] - (str "#" (when wrapped? "[[") q)) - :forward-pos (if wrapped? 3 2)}))) + {:last-pattern last-pattern + :forward-pos forward-pos}))) (fn [chosen _click?] (state/set-editor-show-page-search! false) (let [page-ref-text (get-page-ref-text chosen)] diff --git a/src/main/frontend/handler/web/nfs.cljs b/src/main/frontend/handler/web/nfs.cljs index 7eee45ea9b..f71bd44cc6 100644 --- a/src/main/frontend/handler/web/nfs.cljs +++ b/src/main/frontend/handler/web/nfs.cljs @@ -42,27 +42,29 @@ (defn- ->db-files [electron? dir-name result] - (if electron? - (map (fn [{:keys [path stat content]}] - (let [{:keys [mtime size]} stat] - {:file/path path - :file/last-modified-at mtime - :file/size size - :file/content content})) - result) - (let [result (flatten (bean/->clj result))] - (map (fn [file] - (let [handle (gobj/get file "handle") - get-attr #(gobj/get file %) - path (-> (get-attr "webkitRelativePath") - (string/replace-first (str dir-name "/") ""))] - {:file/name (get-attr "name") - :file/path path - :file/last-modified-at (get-attr "lastModified") - :file/size (get-attr "size") - :file/type (get-attr "type") - :file/file file - :file/handle handle})) result)))) + (->> + (if electron? + (map (fn [{:keys [path stat content]}] + (let [{:keys [mtime size]} stat] + {:file/path path + :file/last-modified-at mtime + :file/size size + :file/content content})) + result) + (let [result (flatten (bean/->clj result))] + (map (fn [file] + (let [handle (gobj/get file "handle") + get-attr #(gobj/get file %) + path (-> (get-attr "webkitRelativePath") + (string/replace-first (str dir-name "/") ""))] + {:file/name (get-attr "name") + :file/path path + :file/last-modified-at (get-attr "lastModified") + :file/size (get-attr "size") + :file/type (get-attr "type") + :file/file file + :file/handle handle})) result))) + (sort-by :file/path))) (defn- filter-markup-and-built-in-files [files] diff --git a/src/main/frontend/modules/file/core.cljs b/src/main/frontend/modules/file/core.cljs index 8fa78b324a..c957c5c879 100644 --- a/src/main/frontend/modules/file/core.cljs +++ b/src/main/frontend/modules/file/core.cljs @@ -21,68 +21,70 @@ (string/join (str "\n" spaces-tabs) lines))) (defn transform-content - [{:block/keys [format pre-block? title content unordered body heading-level left page]} level heading-to-list?] + [{:block/keys [format pre-block? title content unordered body heading-level left page scheduled deadline]} level {:keys [heading-to-list?]}] (let [content (or content "") heading-with-title? (seq title) first-block? (= left page) pre-block? (and first-block? pre-block?) - markdown-heading? (and (= format :markdown) (not unordered) (not heading-to-list?))] - (cond - (and first-block? pre-block?) - (let [content (-> (string/trim content) - ;; FIXME: should only works with :filters - (string/replace "\"" "\\\""))] - (str content "\n")) - - :else - (let [[prefix spaces-tabs] - (cond - (= format :org) - [(->> - (repeat level "*") - (apply str)) ""] - - markdown-heading? - ["" ""] - - :else - (let [level (if (and heading-to-list? heading-level) - (if (> heading-level 1) - (dec heading-level) - heading-level) - level) - spaces-tabs (->> - (repeat (dec level) (state/get-export-bullet-indentation)) - (apply str))] - [(str spaces-tabs "-") (str spaces-tabs " ")])) - content (if heading-to-list? - (-> (string/replace content #"^\s?#+\s+" "") - (string/replace #"^\s?#+\s?$" "")) - content) - new-content (indented-block-content (string/trim content) spaces-tabs) - sep (cond - markdown-heading? - "" - - heading-with-title? - " " - - (string/blank? new-content) - "" + markdown-heading? (and (= format :markdown) (not unordered) (not heading-to-list?)) + content (cond + (and first-block? pre-block?) + (let [content (-> (string/trim content) + ;; FIXME: should only works with :filters + (string/replace "\"" "\\\""))] + (str content "\n")) :else - (str "\n" spaces-tabs))] - (str prefix sep new-content))))) + (let [[prefix spaces-tabs] + (cond + (= format :org) + [(->> + (repeat level "*") + (apply str)) ""] + + markdown-heading? + ["" ""] + + :else + (let [level (if (and heading-to-list? heading-level) + (if (> heading-level 1) + (dec heading-level) + heading-level) + level) + spaces-tabs (->> + (repeat (dec level) (state/get-export-bullet-indentation)) + (apply str))] + [(str spaces-tabs "-") (str spaces-tabs " ")])) + content (if heading-to-list? + (-> (string/replace content #"^\s?#+\s+" "") + (string/replace #"^\s?#+\s?$" "")) + content) + new-content (indented-block-content (string/trim content) spaces-tabs) + sep (cond + markdown-heading? + "" + + heading-with-title? + " " + + (string/blank? new-content) + "" + + :else + (str "\n" spaces-tabs))] + (str prefix sep new-content)))] + content)) (defn tree->file-content [tree {:keys [init-level heading-to-list?] - :or {heading-to-list? false}}] + :or {heading-to-list? false} + :as opts}] (loop [block-contents [] [f & r] tree level init-level] (if (nil? f) (string/join "\n" block-contents) - (let [content (transform-content f level heading-to-list?) + (let [content (transform-content f level opts) new-content (if-let [children (seq (:block/children f))] [content (tree->file-content children {:init-level (inc level)})] diff --git a/src/main/frontend/modules/outliner/tree.cljs b/src/main/frontend/modules/outliner/tree.cljs index 9fdd72042e..2563d8e256 100644 --- a/src/main/frontend/modules/outliner/tree.cljs +++ b/src/main/frontend/modules/outliner/tree.cljs @@ -77,7 +77,7 @@ root-block (with-children-and-refs root-block result)] [root-block])))) -(defn sort-blocks-aux +(defn- sort-blocks-aux [parents parent-groups] (mapv (fn [parent] (let [parent-id {:db/id (:db/id parent)} @@ -90,5 +90,5 @@ (defn sort-blocks "sort blocks by parent & left" [blocks-exclude-root root] - (let [parent-groups (atom (group-by #(:block/parent %) blocks-exclude-root))] + (let [parent-groups (atom (group-by :block/parent blocks-exclude-root))] (flatten (concat (sort-blocks-aux [root] parent-groups) (vals @parent-groups))))) diff --git a/src/main/frontend/modules/shortcut/dict.cljs b/src/main/frontend/modules/shortcut/dict.cljs index 63db43c314..242119d6ad 100644 --- a/src/main/frontend/modules/shortcut/dict.cljs +++ b/src/main/frontend/modules/shortcut/dict.cljs @@ -32,7 +32,7 @@ :shortcut.ui/toggle-theme "在暗色/亮色主题之间切换" :shortcut.ui/toggle-right-sidebar "启用/关闭右侧栏" :shortcut.ui/toggle-settings "显示/关闭设置" - :shortcut.ui/toggle-new-block "切换 Enter/Alt+Enter 以插入新块" + :shortcut.ui/toggle-new-block "切换 Enter/Shift+Enter 以插入新块" :shortcut.go/journals "跳转到日记" ;; TODO translate those in fr/de/etc.. :shortcut.category/basics "基础操作" @@ -96,7 +96,7 @@ :shortcut.ui/toggle-document-mode "切換文檔模式" :shortcut.ui/toggle-theme "“在暗色/亮色主題之間切換”" :shortcut.ui/toggle-right-sidebar "啟用/關閉右側欄" - :shortcut.ui/toggle-new-block "切換 Enter/Alt+Enter 以插入新塊" + :shortcut.ui/toggle-new-block "切換 Enter/Shift+Enter 以插入新塊" :shortcut.go/journals "跳轉到日記" :shortcut.category/formatting "格式化"} :de @@ -118,7 +118,7 @@ :shortcut.ui/toggle-document-mode "Dokumentenmodus umschalten" :shortcut.ui/toggle-theme "Umschalten zwischen dunklem/hellem Thema" :shortcut.ui/toggle-right-sidebar "Rechte Seitenleiste umschalten" - :shortcut.ui/toggle-new-block "Umschalten von Enter/Alt+Enter zum Einfügen eines neuen Blocks" + :shortcut.ui/toggle-new-block "Umschalten von Enter/Shift+Enter zum Einfügen eines neuen Blocks" :shortcut.go/journals "Zu Journalen springen" :shortcut.git/commit "Git Commit-Nachricht" :shortcut.editor/select-block-down "Block unterhalb auswählen" @@ -146,7 +146,7 @@ :shortcut.ui/toggle-document-mode "Intervertir le mode document" :shortcut.ui/toggle-theme "Intervertir le thème foncé/clair" :shortcut.ui/toggle-right-sidebar "Afficher/cacher la barre latérale" - :shortcut.ui/toggle-new-block "Activer Entreée ou Alt+Enter pour insérer un bloc" + :shortcut.ui/toggle-new-block "Activer Entreée ou Shift+Enter pour insérer un bloc" :shortcut.go/journals "Aller au Journal" :shortcut.category/formatting "Formats"} :af @@ -173,4 +173,4 @@ :shortcut.category/formatting "Formatering" :shortcut.ui/toggle-theme "Wissel tussen donker/lig temas" :shortcut.ui/toggle-right-sidebar "Wissel regter sybalk" - :shortcut.ui/toggle-new-block "Wissel Enter/Alt+enter vir die byvoeging van nuwe blokke"}})) + :shortcut.ui/toggle-new-block "Wissel Enter/Shift+enter vir die byvoeging van nuwe blokke"}})) diff --git a/src/main/frontend/security.cljs b/src/main/frontend/security.cljs index 4622c554b8..6936b5fb7b 100644 --- a/src/main/frontend/security.cljs +++ b/src/main/frontend/security.cljs @@ -1,5 +1,6 @@ (ns frontend.security - (:require [clojure.walk :as walk])) + (:require [clojure.walk :as walk] + [frontend.util :as util])) ;; To prevent from cross-site scripting vulnerability, we should add security checks for both hiccup and raw html. ;; Hiccup: [:a {:href "javascript:alert('hei')"} "click me"] @@ -11,7 +12,7 @@ (= :a (first f)) (:href (second f)) (:href (second f)) - (re-find #"(?i)javascript" (:href (second f))))) + (util/safe-re-find #"(?i)javascript" (:href (second f))))) (defn remove-javascript-links-in-href [hiccup] diff --git a/src/main/frontend/state.cljs b/src/main/frontend/state.cljs index 15f10679bb..1ac3dad046 100644 --- a/src/main/frontend/state.cljs +++ b/src/main/frontend/state.cljs @@ -129,7 +129,9 @@ ;; copied blocks :copy/blocks {:copy/content nil :copy/block-tree nil} - :date-picker/date nil})) + :date-picker/date nil + + :view/components {}})) (defn get-route-match [] @@ -313,7 +315,7 @@ (or (when-let [workflow (:preferred-workflow (get-config))] (let [workflow (name workflow)] - (if (re-find #"now|NOW" workflow) + (if (util/safe-re-find #"now|NOW" workflow) :now :todo))) (get-in @state [:me :preferred_workflow] :now)))) @@ -1257,7 +1259,7 @@ (defn get-export-bullet-indentation [] - (case (get (get-config) :export/bullet-indentation :two-spaces) + (case (get (get-config) :export/bullet-indentation :tab) :eight-spaces " " :four-spaces @@ -1266,3 +1268,19 @@ " " :tab "\t")) + +(defn set-page-blocks-cp! + [value] + (set-state! [:view/components :page-blocks] value)) + +(defn get-page-blocks-cp + [] + (get-in @state [:view/components :page-blocks])) + +(defn set-editor-cp! + [value] + (set-state! [:view/components :editor] value)) + +(defn get-editor-cp + [] + (get-in @state [:view/components :editor])) diff --git a/src/main/frontend/text.cljs b/src/main/frontend/text.cljs index 38de1128d8..3203ab6095 100644 --- a/src/main/frontend/text.cljs +++ b/src/main/frontend/text.cljs @@ -78,8 +78,8 @@ (cond (and (string? s) ;; Either a page ref, a tag or a comma separated collection - (or (re-find page-ref-re s) - (re-find (if comma? #"[\,|,|#]+" #"#") s))) + (or (util/safe-re-find page-ref-re s) + (util/safe-re-find (if comma? #"[\,|,|#]+" #"#") s))) (let [result (->> (string/split s page-ref-re-2) (map (fn [s] (if (string/ends-with? (string/trimr s) "]],") (let [s (string/trimr s)] @@ -109,7 +109,7 @@ (let [pattern (util/format "^[%s]+\\s?" (config/get-block-pattern format))] - (re-find (re-pattern pattern) text)) + (util/safe-re-find (re-pattern pattern) text)) "")) (defn- remove-level-space-aux! @@ -136,18 +136,6 @@ :else (remove-level-space-aux! text (config/get-block-pattern format) space?)))) -(defn append-newline-after-level-spaces - [text format] - (if-not (string/blank? text) - (let [pattern (util/format - "^[%s]+\\s?\n?" - (config/get-block-pattern format)) - matched-text (re-find (re-pattern pattern) text)] - (if matched-text - (string/replace-first text matched-text (str (string/trimr matched-text) "\n")) - text)))) - - (defn build-data-value [col] (let [items (map (fn [item] (str "\"" item "\"")) col)] @@ -156,4 +144,12 @@ (defn image-link? [img-formats s] - (some (fn [fmt] (re-find (re-pattern (str "(?i)\\." fmt "(?:\\?([^#]*))?(?:#(.*))?$")) s)) img-formats)) + (some (fn [fmt] (util/safe-re-find (re-pattern (str "(?i)\\." fmt "(?:\\?([^#]*))?(?:#(.*))?$")) s)) img-formats)) + +(defn scheduled-deadline-dash->star + [content] + (-> content + (string/replace "- TODO -> DONE [" "* TODO -> DONE [") + (string/replace "- DOING -> DONE [" "* DOING -> DONE [") + (string/replace "- LATER -> DONE [" "* LATER -> DONE [") + (string/replace "- NOW -> DONE [" "* NOW -> DONE ["))) diff --git a/src/main/frontend/ui.cljs b/src/main/frontend/ui.cljs index fe53794112..1342359890 100644 --- a/src/main/frontend/ui.cljs +++ b/src/main/frontend/ui.cljs @@ -340,12 +340,12 @@ {:id (str "ac-" idx) :class (when (= @current-idx idx) "chosen") - ;; :tab-index -1 - :on-click (fn [e] - (.preventDefault e) - (if (and (gobj/get e "shiftKey") on-shift-chosen) - (on-shift-chosen item) - (on-chosen item)))} + ;; :tab-index -1 + :on-mouse-down (fn [e] + (util/stop e) + (if (and (gobj/get e "shiftKey") on-shift-chosen) + (on-shift-chosen item) + (on-chosen item)))} (if item-render (item-render item) item)) idx))] (when empty-div diff --git a/src/main/frontend/util.cljc b/src/main/frontend/util.cljc index c223ab79b1..f405ded8a8 100644 --- a/src/main/frontend/util.cljc +++ b/src/main/frontend/util.cljc @@ -49,11 +49,20 @@ (and (string/includes? ua "webkit") (not (string/includes? ua "chrome")))))) +(defn safe-re-find + [pattern s] + #?(:cljs + (when-not (string? s) + ;; TODO: sentry + (js/console.trace))) + (when (string? s) + (re-find pattern s))) + #?(:cljs (defn mobile? [] (when-not node-test? - (re-find #"Mobi" js/navigator.userAgent)))) + (safe-re-find #"Mobi" js/navigator.userAgent)))) #?(:cljs (defn electron? @@ -292,29 +301,29 @@ ;; Caret #?(:cljs (defn caret-range [node] - (let [doc (or (gobj/get node "ownerDocument") - (gobj/get node "document")) - win (or (gobj/get doc "defaultView") - (gobj/get doc "parentWindow")) - selection (.getSelection win)] - (if selection - (let [range-count (gobj/get selection "rangeCount")] - (when (> range-count 0) - (let [range (-> (.getSelection win) - (.getRangeAt 0)) - pre-caret-range (.cloneRange range)] - (.selectNodeContents pre-caret-range node) - (.setEnd pre-caret-range - (gobj/get range "endContainer") - (gobj/get range "endOffset")) - (.toString pre-caret-range)))) - (when-let [selection (gobj/get doc "selection")] - (when (not= "Control" (gobj/get selection "type")) - (let [text-range (.createRange selection) - pre-caret-text-range (.createTextRange (gobj/get doc "body"))] - (.moveToElementText pre-caret-text-range node) - (.setEndPoint pre-caret-text-range "EndToEnd" text-range) - (gobj/get pre-caret-text-range "text")))))))) + (when-let [doc (or (gobj/get node "ownerDocument") + (gobj/get node "document"))] + (let [win (or (gobj/get doc "defaultView") + (gobj/get doc "parentWindow")) + selection (.getSelection win)] + (if selection + (let [range-count (gobj/get selection "rangeCount")] + (when (> range-count 0) + (let [range (-> (.getSelection win) + (.getRangeAt 0)) + pre-caret-range (.cloneRange range)] + (.selectNodeContents pre-caret-range node) + (.setEnd pre-caret-range + (gobj/get range "endContainer") + (gobj/get range "endOffset")) + (.toString pre-caret-range)))) + (when-let [selection (gobj/get doc "selection")] + (when (not= "Control" (gobj/get selection "type")) + (let [text-range (.createRange selection) + pre-caret-text-range (.createTextRange (gobj/get doc "body"))] + (.moveToElementText pre-caret-text-range node) + (.setEndPoint pre-caret-text-range "EndToEnd" text-range) + (gobj/get pre-caret-text-range "text"))))))))) #?(:cljs (defn set-caret-pos! @@ -391,7 +400,7 @@ #?(:cljs (defn scroll-to-element [elem-id] - (when-not (re-find #"^/\d+$" elem-id) + (when-not (safe-re-find #"^/\d+$" elem-id) (when elem-id (when-let [elem (gdom/getElement elem-id)] (.scroll (app-scroll-container-node) @@ -860,11 +869,11 @@ (defonce exactly-uuid-pattern (re-pattern (str "^" uuid-pattern "$"))) (defn uuid-string? [s] - (re-find exactly-uuid-pattern s)) + (safe-re-find exactly-uuid-pattern s)) (defn extract-uuid [s] - (re-find (re-pattern uuid-pattern) s)) + (safe-re-find (re-pattern uuid-pattern) s)) (defn drop-nth [n coll] (keep-indexed #(if (not= %1 n) %2) coll)) @@ -876,7 +885,7 @@ (defn file-page? [page-name] - (when page-name (re-find #"\." page-name))) + (when page-name (safe-re-find #"\." page-name))) #?(:cljs (defn react @@ -925,7 +934,7 @@ (defn get-prev-block-with-same-level [block] (let [id (gobj/get block "id") - prefix (re-find #"ls-block-[\d]+" id)] + prefix (safe-re-find #"ls-block-[\d]+" id)] (when-let [blocks (d/by-class "ls-block")] (when-let [index (.indexOf blocks block)] (let [level (d/attr block "level")] @@ -996,8 +1005,8 @@ [tag-name] (when tag-name (and - (not (re-find #"#" tag-name)) - (re-find regex/valid-tag-pattern tag-name)))) + (not (safe-re-find #"#" tag-name)) + (safe-re-find regex/valid-tag-pattern tag-name)))) (defn encode-str [s] @@ -1032,8 +1041,8 @@ [] (let [user-agent js/navigator.userAgent vendor js/navigator.vendor] - (and (re-find #"Chrome" user-agent) - (re-find #"Google Inc" user-agent))))) + (and (safe-re-find #"Chrome" user-agent) + (safe-re-find #"Google Inc" user-agent))))) #?(:cljs (defn indexeddb-check? @@ -1089,7 +1098,7 @@ (defn include-windows-reserved-chars? [s] - (re-find windows-reserved-chars s)) + (safe-re-find windows-reserved-chars s)) (defn page-name-sanity [page-name] diff --git a/src/main/frontend/util/marker.cljs b/src/main/frontend/util/marker.cljs index a84892b8eb..bb40bbacd1 100644 --- a/src/main/frontend/util/marker.cljs +++ b/src/main/frontend/util/marker.cljs @@ -26,7 +26,7 @@ (if-let [matches (seq (util/re-pos new-line-re-pattern content))] (let [[start-pos content] (last matches)] (+ start-pos (count content))) - (count (re-find re-pattern content))) + (count (util/safe-re-find re-pattern content))) new-content (str (subs content 0 pos) (string/replace-first (subs content pos) diff --git a/src/main/frontend/util/priority.cljs b/src/main/frontend/util/priority.cljs index 36767f267c..42b1906188 100644 --- a/src/main/frontend/util/priority.cljs +++ b/src/main/frontend/util/priority.cljs @@ -14,7 +14,7 @@ (if-let [matches (seq (util/re-pos new-line-re-pattern content))] (let [[start-pos content] (last matches)] (+ start-pos (count content))) - (count (re-find re-pattern content))) + (count (util/safe-re-find re-pattern content))) skip-marker-pos (if-let [matches (seq (util/re-pos marker/bare-marker-pattern (subs content skip-hash-pos)))] (let [[start-pos content] (last matches)] diff --git a/src/main/frontend/util/property.cljs b/src/main/frontend/util/property.cljs index 84e674408e..088c2b9af7 100644 --- a/src/main/frontend/util/property.cljs +++ b/src/main/frontend/util/property.cljs @@ -3,7 +3,8 @@ [frontend.util :as util] [clojure.set :as set] [frontend.config :as config] - [medley.core :as medley])) + [medley.core :as medley] + [frontend.format.mldoc :as mldoc])) (defonce properties-start ":PROPERTIES:") (defonce properties-end ":END:") @@ -24,34 +25,34 @@ (defn contains-properties? [content] (and (string/includes? content properties-start) - (re-find properties-end-pattern content))) + (util/safe-re-find properties-end-pattern content))) (defn simplified-property? [line] (boolean (and (string? line) - (re-find #"^\s?[^ ]+:: " line)))) + (util/safe-re-find #"^\s?[^ ]+:: " line)))) (defn front-matter-property? [line] (boolean (and (string? line) - (re-find #"^\s*[^ ]+: " line)))) + (util/safe-re-find #"^\s*[^ ]+: " line)))) (defn get-property-key [line format] (and (string? line) (when-let [key (last (if (= format :org) - (re-find #"^\s*:([^: ]+): " line) - (re-find #"^\s*([^ ]+):: " line)))] + (util/safe-re-find #"^\s*:([^: ]+): " line) + (util/safe-re-find #"^\s*([^ ]+):: " line)))] (keyword key)))) (defn org-property? [line] (boolean (and (string? line) - (re-find #"^\s*:[^: ]+: " line) + (util/safe-re-find #"^\s*:[^: ]+: " line) (when-let [key (get-property-key line :org)] (not (contains? #{:PROPERTIES :END} key)))))) @@ -92,7 +93,7 @@ (let [org? (= format :org) kv-format (if org? ":%s: %s" "%s:: %s") full-format (if org? ":PROPERTIES:\n%s\n:END:\n" "%s\n") - properties-content (->> (map (fn [[k v]] (util/format kv-format k v)) properties) + properties-content (->> (map (fn [[k v]] (util/format kv-format (name k) v)) properties) (string/join "\n"))] (util/format full-format properties-content)))) @@ -141,19 +142,26 @@ ([format content key value] (insert-property format content key value false)) ([format content key value front-matter?] - (when (and (not (string/blank? (name key))) + (when (and (string? content) + (not (string/blank? (name key))) (not (string/blank? (str value)))) - (let [org? (= :org format) + (let [ast (mldoc/->edn content (mldoc/default-config format)) + title? (mldoc/block-with-title? (ffirst (map first ast))) + lines (string/split-lines content) + [title body] (if title? + [(first lines) (string/join "\n" (rest lines))] + [nil (string/join "\n" lines)]) + org? (= :org format) key (string/lower-case (name key)) value (string/trim (str value)) - lines (string/split-lines content) start-idx (.indexOf lines properties-start) end-idx (.indexOf lines properties-end)] (cond (and org? (not (contains-properties? content))) - (let [properties (build-properties-str format {key value}) - [title body] (util/safe-split-first "\n" content)] - (str title "\n" properties body)) + (let [properties (build-properties-str format {key value})] + (if title + (str title "\n" properties body) + (str properties content))) (and (>= start-idx 0) (> end-idx 0) (> end-idx start-idx)) (let [exists? (atom false) @@ -198,9 +206,15 @@ lines)) groups) lines (if no-properties? - (if (string/blank? content) + (cond + (string/blank? content) [new-property-s] - (cons (first lines) (cons new-property-s (rest lines)))) + + title? + (cons (first lines) (cons new-property-s (rest lines))) + + :else + (cons new-property-s lines)) lines)] (string/join "\n" lines)) diff --git a/src/test/frontend/format/block_test.cljs b/src/test/frontend/format/block_test.cljs new file mode 100644 index 0000000000..b3a2a60c86 --- /dev/null +++ b/src/test/frontend/format/block_test.cljs @@ -0,0 +1,36 @@ +(ns frontend.format.block-test + (:require [frontend.format.block :as block] + [cljs.test :refer [deftest is are testing use-fixtures run-tests]])) + +(deftest test-extract-properties + (are [x y] (= (:properties (block/extract-properties x)) y) + [["year" "1000"]] {:year 1000} + [["year" "\"1000\""]] {:year "1000"} + [["background-color" "#000000"]] {:background-color "#000000"} + [["alias" "name/with space"]] {:alias #{"name/with space"}} + [["year" "1000"] ["alias" "name/with space"]] {:year 1000, :alias #{"name/with space"}} + [["year" "1000"] ["tags" "name/with space"]] {:year 1000, :tags #{"name/with space"}} + [["year" "1000"] ["tags" "name/with space, another"]] {:year 1000, :tags #{"name/with space" "another"}} + [["year" "1000"] ["alias" "name/with space, another"]] {:year 1000, :alias #{"name/with space" "another"}} + [["year" "1000"] ["alias" "name/with space, [[another [[nested]]]]"]] {:year 1000, :alias #{"name/with space" "another [[nested]]"}} + ;; FIXME: + ;; [["year" "1000"] ["alias" "name/with space, [[[[nested]] another]]"]] {:year 1000, :alias #{"name/with space" "[[nested]] another"}} + [["foo" "bar"]] {:foo "bar"} + [["foo" "bar, baz"]] {:foo #{"bar" "baz"}} + [["foo" "bar, [[baz]]"]] {:foo #{"bar" "baz"}} + [["foo" "[[bar]], [[baz]]"]] {:foo #{"bar" "baz"}} + [["foo" "[[bar]], [[nested [[baz]]]]"]] {:foo #{"bar" "nested [[baz]]"}} + [["foo" "[[bar]], [[nested [[baz]]]]"]] {:foo #{"bar" "nested [[baz]]"}} + [["foo" "bar, [[baz, test]]"]] {:foo #{"bar" "baz, test"}} + [["foo" "bar, [[baz, test, [[nested]]]]"]] {:foo #{"bar" "baz, test, [[nested]]"}}) + + (are [x y] (= (vec (:page-refs (block/extract-properties x))) y) + [["year" "1000"]] [] + [["year" "\"1000\""]] [] + [["foo" "[[bar]] test"]] ["bar"] + [["foo" "[[bar]] test [[baz]]"]] ["bar" "baz"] + ;; FIXME: + ;; [["foo" "[[bar]] test [[baz]] [[nested [[baz]]]]"]] ["bar" "baz" "nested [[baz]]"] + )) + +#_(run-tests) diff --git a/src/test/frontend/text_test.cljs b/src/test/frontend/text_test.cljs index 4b603f243d..ef44175a89 100644 --- a/src/test/frontend/text_test.cljs +++ b/src/test/frontend/text_test.cljs @@ -83,16 +83,4 @@ "**foobar" "foobar" "*********************foobar" "foobar"))) -(deftest append-newline-after-level-spaces - [] - (are [x y] (= (text/append-newline-after-level-spaces x :markdown) y) - "# foobar" "#\nfoobar" - "# foobar\nfoo" "#\nfoobar\nfoo" - "## foobar\nfoo" "##\nfoobar\nfoo") - - (are [x y] (= (text/append-newline-after-level-spaces x :org) y) - "* foobar" "*\nfoobar" - "* foobar\nfoo" "*\nfoobar\nfoo" - "** foobar\nfoo" "**\nfoobar\nfoo")) - #_(cljs.test/test-ns 'frontend.text-test) diff --git a/src/test/frontend/util/property_test.cljs b/src/test/frontend/util/property_test.cljs index 574321fd81..ad6af91fbf 100644 --- a/src/test/frontend/util/property_test.cljs +++ b/src/test/frontend/util/property_test.cljs @@ -1,4 +1,4 @@ -(ns frontend.util.property_test +(ns frontend.util.property-test (:require [cljs.test :refer [deftest is are testing]] [frontend.util.property :as property])) @@ -19,10 +19,7 @@ "hello\n\nworld" "hello\naa:: bb\nid:: f9873a81-07b9-4246-b910-53a6f5ec7e04\n\nworld" - "hello\naa:: bb\n\nworld" - ) - ) - ) + "hello\naa:: bb\n\nworld"))) (deftest test-remove-properties (testing "properties with non-blank lines" @@ -61,11 +58,24 @@ (property/insert-property :org "hello\n:PROPERTIES:\n:a: b\n:END:\n" "c" "d") "hello\n:PROPERTIES:\n:a: b\n:c: d\n:END:" - (property/insert-property :org "hello\n:PROPERTIES:\n:a: b\n:END: world\n" "c" "d") - "hello\n:PROPERTIES:\n:c: d\n:END:\n:PROPERTIES:\n:a: b\n:END: world\n" + (property/insert-property :org "hello\n:PROPERTIES:\n:a: b\n:END:\nworld\n" "c" "d") + "hello\n:PROPERTIES:\n:a: b\n:c: d\n:END:\nworld" + + (property/insert-property :org "#+BEGIN_QUOTE + hello world + #+END_QUOTE" "c" "d") + ":PROPERTIES:\n:c: d\n:END:\n#+BEGIN_QUOTE\n hello world\n #+END_QUOTE" (property/insert-property :markdown "hello\na:: b\nworld\n" "c" "d") - "hello\na:: b\nc:: d\nworld")) + "hello\na:: b\nc:: d\nworld" + + (property/insert-property :markdown "> quote" "c" "d") + "c:: d\n> quote" + + (property/insert-property :markdown "#+BEGIN_QUOTE + hello world + #+END_QUOTE" "c" "d") + "c:: d\n#+BEGIN_QUOTE\n hello world\n #+END_QUOTE")) (deftest test->new-properties (are [x y] (= (property/->new-properties x) y) @@ -90,5 +100,20 @@ "hello\n:PROPERTIES:\n:foo: bar\n:nice\n:END:\nnice" "hello\nfoo:: bar\n:nice\nnice")) +(deftest test-build-properties-str + (are [x y] (= (property/build-properties-str :mardown x) y) + {:title "a"} + "title:: a\n" + {:title "a/b/c"} + "title:: a/b/c\n" + {:title "a/b/c" :tags "d,e"} + "title:: a/b/c\ntags:: d,e\n") + (are [x y] (= (property/build-properties-str :org x) y) + {:title "a"} + ":PROPERTIES:\n:title: a\n:END:\n" + {:title "a/b/c"} + ":PROPERTIES:\n:title: a/b/c\n:END:\n" + {:title "a/b/c" :tags "d,e"} + ":PROPERTIES:\n:title: a/b/c\n:tags: d,e\n:END:\n")) #_(cljs.test/run-tests) diff --git a/yarn.lock b/yarn.lock index 0997aee2b3..9584883b32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3860,10 +3860,10 @@ mkdirp@^0.5.4, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" -mldoc@0.6.16: - version "0.6.16" - resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-0.6.16.tgz#6cad0c9ce0015c92a630d7dc4c4fb2835f34eb98" - integrity sha512-9f9hCPaxIjd5bFugR1pgcH6m5Fwjnme9eGVty/uUch+k81wdey+fCzbp8szazP2J3Eneu5Z/181Tre501mpwwQ== +mldoc@0.6.18: + version "0.6.18" + resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-0.6.18.tgz#a1c0b76d193ef9cd98c2763de3e8414cd83b9368" + integrity sha512-Bg460Jdp4kBgmAYUdVtWG7AZSQ0EWVNsiWQ/7mljVLQp5yyERcySzw1nM4Hs5eZ5IKStzl6qz4m6XN/q11AUJw== dependencies: yargs "^12.0.2"