mirror of
https://github.com/logseq/logseq.git
synced 2026-04-24 22:25:01 +00:00
Merge branch 'master' into fix/wb-fixes
This commit is contained in:
2
.github/workflows/build-android.yml
vendored
2
.github/workflows/build-android.yml
vendored
@@ -105,7 +105,7 @@ jobs:
|
||||
|
||||
- name: Set Build Environment Variables
|
||||
run: |
|
||||
echo "ENABLE_FILE_SYNC_PRODUCTION=${{ inputs.enable-file-sync-production == 'true' || github.event.inputs.enable-file-sync-production == 'true' }}" >> $GITHUB_ENV
|
||||
echo "ENABLE_FILE_SYNC_PRODUCTION=${{ inputs.enable-file-sync-production || github.event.inputs.enable-file-sync-production || inputs.build-target == '' }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Compile CLJS - android variant, use es6 instead of es-next
|
||||
run: yarn install && yarn release-android-app
|
||||
|
||||
@@ -37,3 +37,12 @@ test('custom hiccup should not spawn any dialogs', async ({ page, block }) => {
|
||||
|
||||
expect(true).toBeTruthy()
|
||||
})
|
||||
|
||||
test('"is" attribute should be allowed for plugin purposes', async ({ page, block }) => {
|
||||
await createRandomPage(page)
|
||||
|
||||
await page.keyboard.type('[:div {:is "custom-element" :id "custom-element-id"}]', { delay: 5 })
|
||||
await block.enterNext()
|
||||
|
||||
await expect(page.locator('#custom-element-id')).toHaveAttribute('is', 'custom-element');
|
||||
})
|
||||
|
||||
@@ -40,11 +40,11 @@ test('can right click title to show context menu', async ({ page }) => {
|
||||
await page.click('.whiteboard-page-title', {
|
||||
button: 'right',
|
||||
})
|
||||
|
||||
|
||||
await expect(page.locator('#custom-context-menu')).toBeVisible()
|
||||
|
||||
|
||||
await page.keyboard.press('Escape')
|
||||
|
||||
|
||||
await expect(page.locator('#custom-context-menu')).toHaveCount(0)
|
||||
})
|
||||
|
||||
@@ -69,7 +69,7 @@ test('set whiteboard title', async ({ page }) => {
|
||||
})
|
||||
|
||||
test('select rectangle tool', async ({ page }) => {
|
||||
await page.keyboard.press('8')
|
||||
await page.keyboard.press('7')
|
||||
await expect(page.locator('.tl-geometry-tools-pane-anchor [title*="Rectangle"]')).toHaveAttribute('data-selected', 'true')
|
||||
})
|
||||
|
||||
@@ -77,7 +77,7 @@ test('draw a rectangle', async ({ page }) => {
|
||||
const canvas = await page.waitForSelector('.logseq-tldraw');
|
||||
const bounds = (await canvas.boundingBox())!;
|
||||
|
||||
await page.keyboard.press('8')
|
||||
await page.keyboard.press('7')
|
||||
|
||||
await page.mouse.move(bounds.x + 5, bounds.y + 5);
|
||||
await page.mouse.down();
|
||||
@@ -112,11 +112,11 @@ test('quick add another whiteboard', async ({ page }) => {
|
||||
// create a new board first
|
||||
await page.click('.nav-header .whiteboard')
|
||||
await page.click('#tl-create-whiteboard')
|
||||
|
||||
|
||||
await page.click('.whiteboard-page-title')
|
||||
await page.fill('.whiteboard-page-title input', "my-whiteboard-3")
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
|
||||
const canvas = await page.waitForSelector('.logseq-tldraw');
|
||||
await canvas.dblclick({
|
||||
position: {
|
||||
@@ -138,7 +138,7 @@ test('quick add another whiteboard', async ({ page }) => {
|
||||
test('go to another board and check reference', async ({ page }) => {
|
||||
await page.locator('.tl-logseq-portal-container >> text=my-whiteboard-2').click()
|
||||
await expect(page.locator('.whiteboard-page-title .title')).toContainText("my-whiteboard-2");
|
||||
|
||||
|
||||
const pageRefCount$ = page.locator('.whiteboard-page-refs-count')
|
||||
await expect(pageRefCount$.locator('.open-page-ref-link')).toContainText('1')
|
||||
|
||||
|
||||
@@ -592,6 +592,7 @@ export interface IEditorProxy extends Record<string, any> {
|
||||
before: boolean
|
||||
sibling: boolean
|
||||
isPageBlock: boolean
|
||||
focus: boolean
|
||||
customUUID: string
|
||||
properties: {}
|
||||
}>
|
||||
|
||||
@@ -48,12 +48,6 @@ html[data-theme='dark'] {
|
||||
--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,
|
||||
#021c23 0,
|
||||
#021b21 200px,
|
||||
#002b36 100%
|
||||
);
|
||||
--ls-border-color: #0e5263;
|
||||
--ls-secondary-border-color: #126277;
|
||||
--ls-tertiary-border-color: rgba(0, 2, 0, 0.10);
|
||||
@@ -131,7 +125,6 @@ html[data-theme='light'] {
|
||||
--ls-block-properties-background-color: #f7f7f7;
|
||||
--ls-page-properties-background-color: #f7f7f7;
|
||||
--ls-block-ref-link-text-color: #d8e1e8;
|
||||
--ls-search-background-color: var(--ls-primary-background-color);
|
||||
--ls-border-color: #ccc;
|
||||
--ls-secondary-border-color: #e2e2e2;
|
||||
--ls-tertiary-border-color: rgba(200, 200, 200, 0.30);
|
||||
@@ -151,6 +144,7 @@ html[data-theme='light'] {
|
||||
--ls-block-bullet-color: rgba(67, 63, 56, 0.25);
|
||||
--ls-block-highlight-color: #c0e6fd;
|
||||
--ls-selection-background-color: #e4f2ff;
|
||||
--ls-selection-text-color: var(--ls-secondary-text-color);
|
||||
--ls-page-checkbox-color: #9dbbd8;
|
||||
--ls-page-checkbox-border-color: var(--ls-page-checkbox-color);
|
||||
--ls-page-blockquote-color: var(--ls-primary-text-color);
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
[frontend.util.drawer :as drawer]
|
||||
[frontend.util.property :as property]
|
||||
[frontend.util.text :as text-util]
|
||||
[frontend.handler.notification :as notification]
|
||||
[goog.dom :as gdom]
|
||||
[goog.object :as gobj]
|
||||
[lambdaisland.glogi :as log]
|
||||
@@ -264,14 +265,6 @@
|
||||
(when (seq images)
|
||||
(lightbox/preview-images! images))))
|
||||
|
||||
(defn copy-image-to-clipboard
|
||||
[src]
|
||||
(-> (js/fetch src)
|
||||
(.then (fn [data]
|
||||
(-> (.blob data)
|
||||
(.then (fn [blob]
|
||||
(js/navigator.clipboard.write (clj->js [(js/ClipboardItem. (clj->js {(.-type blob) blob}))])))))))))
|
||||
|
||||
(defonce *resizing-image? (atom false))
|
||||
(rum/defcs resizable-image <
|
||||
(rum/local nil ::size)
|
||||
@@ -353,12 +346,13 @@
|
||||
(ui/icon "trash")]
|
||||
|
||||
[:button.asset-action-btn
|
||||
{:title (t :asset/copy)
|
||||
:tabIndex "-1"
|
||||
{:title (t :asset/copy)
|
||||
:tabIndex "-1"
|
||||
:on-mouse-down util/stop
|
||||
:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(copy-image-to-clipboard image-src))}
|
||||
:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(-> (util/copy-image-to-clipboard image-src)
|
||||
(p/then #(notification/show! "Copied!" :success))))}
|
||||
(ui/icon "copy")]
|
||||
|
||||
[:button.asset-action-btn
|
||||
@@ -1045,7 +1039,7 @@
|
||||
[:a.asset-ref.is-pdf
|
||||
{:on-mouse-down (fn [_event]
|
||||
(when-let [current (pdf-assets/inflate-asset s)]
|
||||
(state/set-state! :pdf/current current)))}
|
||||
(state/set-current-pdf! current)))}
|
||||
(or label-text
|
||||
(->elem :span (map-inline config label)))]
|
||||
|
||||
@@ -3252,9 +3246,9 @@
|
||||
:else
|
||||
[:<>
|
||||
(lazy-editor/editor config (str (d/squuid)) attr code options)
|
||||
(let [options (:options options)]
|
||||
(let [options (:options options) block (:block config)]
|
||||
(when (and (= language "clojure") (contains? (set options) ":results"))
|
||||
(sci/eval-result code)))])])))))
|
||||
(sci/eval-result code block)))])])))))
|
||||
|
||||
(defn ^:large-vars/cleanup-todo markup-element-cp
|
||||
[{:keys [html-export?] :as config} item]
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
[frontend.db.model :as model]
|
||||
[frontend.extensions.graph :as graph]
|
||||
[frontend.extensions.pdf.assets :as pdf-assets]
|
||||
[frontend.extensions.pdf.utils :as pdf-utils]
|
||||
[frontend.format.block :as block]
|
||||
[frontend.handler.common :as common-handler]
|
||||
[frontend.handler.config :as config-handler]
|
||||
@@ -282,7 +283,7 @@
|
||||
whiteboard-page? (model/whiteboard-page? page-name)
|
||||
untitled? (and whiteboard-page? (parse-uuid page-name)) ;; normal page cannot be untitled right?
|
||||
title (if hls-page?
|
||||
[:a.asset-ref (pdf-assets/fix-local-asset-pagename title)]
|
||||
[:a.asset-ref (pdf-utils/fix-local-asset-pagename title)]
|
||||
(if fmt-journal? (date/journal-title->custom-format title) title))
|
||||
old-name (or title page-name)]
|
||||
[:h1.page-title.flex.cursor-pointer.gap-1.w-full
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
[frontend.db.model :as model]
|
||||
[frontend.handler.search :as search-handler]
|
||||
[frontend.handler.whiteboard :as whiteboard-handler]
|
||||
[frontend.extensions.pdf.assets :as pdf-assets]
|
||||
[frontend.extensions.pdf.utils :as pdf-utils]
|
||||
[frontend.ui :as ui]
|
||||
[frontend.state :as state]
|
||||
[frontend.mixins :as mixins]
|
||||
@@ -203,7 +203,7 @@
|
||||
(defn- search-item-render
|
||||
[search-q {:keys [type data alias]}]
|
||||
(let [search-mode (state/get-search-mode)
|
||||
data (if (string? data) (pdf-assets/fix-local-asset-pagename data) data)]
|
||||
data (if (string? data) (pdf-utils/fix-local-asset-pagename data) data)]
|
||||
[:div {:class "py-2"}
|
||||
(case type
|
||||
:graph-add-filter
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#search {
|
||||
> .inner {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.search-result {
|
||||
@@ -8,10 +11,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.search-ac {
|
||||
background-color: var(--ls-primary-background-color);
|
||||
}
|
||||
|
||||
#search-wrapper svg {
|
||||
color: var(--ls-search-icon-color, #9fa6b2);
|
||||
opacity: 0.6;
|
||||
@@ -23,37 +22,11 @@
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#search-field {
|
||||
background-color: var(--ls-search-background-color, #fff);
|
||||
color: var(--ls-secondary-text-color, #161e2e);
|
||||
transition: background .3s;
|
||||
max-width: 545px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#search-wrapper {
|
||||
transition: .3s;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
#search-field:hover,
|
||||
#search-field:focus-within {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#search>.inner {
|
||||
max-width: 100%;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#search-field:focus {
|
||||
background: var(--ls-search-background-color);
|
||||
}
|
||||
|
||||
.dark-theme #search-field:focus {
|
||||
box-shadow: 0px 0px 20px 0px rgba(18, 18, 18, .3);
|
||||
}
|
||||
|
||||
.search-item svg {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
[frontend.db :as db]
|
||||
[frontend.db-mixins :as db-mixins]
|
||||
[frontend.db.model :as db-model]
|
||||
[frontend.extensions.pdf.assets :as pdf-assets]
|
||||
[frontend.extensions.pdf.utils :as pdf-utils]
|
||||
[frontend.extensions.srs :as srs]
|
||||
[frontend.handler.common :as common-handler]
|
||||
[frontend.handler.editor :as editor-handler]
|
||||
@@ -89,7 +89,7 @@
|
||||
(route-handler/redirect-to-whiteboard! name)
|
||||
(route-handler/redirect-to-page! name {:click-from-recent? recent?})))))}
|
||||
[:span.page-icon (if whiteboard-page? (ui/icon "whiteboard" {:extension? true}) icon)]
|
||||
[:span.page-title (pdf-assets/fix-local-asset-pagename original-name)]]))
|
||||
[:span.page-title (pdf-utils/fix-local-asset-pagename original-name)]]))
|
||||
|
||||
(defn get-page-icon [page-entity]
|
||||
(let [default-icon (ui/icon "page" {:extension? true})
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
(.add cls "dark")
|
||||
(.remove cls "dark"))
|
||||
(ui/apply-custom-theme-effect! theme)
|
||||
(plugin-handler/hook-plugin-app :theme-mode-changed {:mode theme} nil))
|
||||
(plugin-handler/hook-plugin-app :theme-mode-changed {:mode theme}))
|
||||
[theme])
|
||||
|
||||
(rum/use-effect!
|
||||
|
||||
@@ -4605,8 +4605,8 @@
|
||||
:file-rn/format-deprecated "Şu anda güncel olmayan bir biçim kullanıyorsunuz. En son biçime güncellemeniz kesinlikle önerilir. Lütfen işlemden önce verilerinizi yedekleyin ve Logseq istemcilerini diğer cihazlarda kapatın."
|
||||
:file-rn/filename-desc-1 "Bu ayar, bir sayfanın bir dosyaya nasıl saklanacağını yapılandırır. Logseq, aynı ada sahip bir dosyaya bir sayfa depolar."
|
||||
:file-rn/filename-desc-2 "\"/\" vaya \"?\" gibi bazı karakterler bir dosya adı için geçersizdir."
|
||||
:file-rn/filename-desc-3 "Logseq, geçersiz karakterleri geçerli kılmak için URL kodlu eşdeğerleriyle değiştirir (ör. \"?\", \"%3F\" olur)."
|
||||
:file-rn/filename-desc-4 "Ad boşluğu ayırıcısı \"/\", estetik değerlendirme için \"___\" (üçlü altçizgi) ile de değiştirilir."
|
||||
:file-rn/filename-desc-3 "Logseq, geçersiz karakterleri URL kodlu eşdeğerleriyle değiştirir (ör. \"?\", \"%3F\" olur)."
|
||||
:file-rn/filename-desc-4 "Ad boşluğu ayırıcısı \"/\", estetik değerlendirme için \"___\" (üçlü altçizgi) ile değiştirilir."
|
||||
:file-rn/instruct-1 "Dosya adı biçimini güncellemek 2 adımlı bir işlemdir:"
|
||||
:file-rn/instruct-2 "1. Tıklayın "
|
||||
:file-rn/instruct-3 "2. Dosyaları yeni biçimde yeniden adlandırmak için aşağıdaki talimatları izleyin:"
|
||||
@@ -4671,8 +4671,10 @@
|
||||
:settings-page/disable-sentry "Kullanım verilerini ve tanılamayı Logseq'e gönderin"
|
||||
:settings-page/preferred-outdenting "Mantıksal girinti"
|
||||
:settings-page/custom-date-format "Tercih edilen tarih biçimi"
|
||||
:settings-page/custom-date-format-warning "Yeniden dizin oluşturma gerekli! Mevcut günlük referansları bozulabilir!"
|
||||
:settings-page/preferred-file-format "Tercih edilen dosya biçimi"
|
||||
:settings-page/preferred-workflow "Tercih edilen iş akışı"
|
||||
:settings-page/preferred-pasting-file "Dosya yapıştırmayı tercih et"
|
||||
:settings-page/enable-shortcut-tooltip "Kısayol araç ipuçlarını etkinleştir"
|
||||
:settings-page/enable-timetracking "Zaman takibi"
|
||||
:settings-page/enable-tooltip "Araç ipuçları"
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
[frontend.handler.editor :as editor-handler]
|
||||
[frontend.handler.page :as page-handler]
|
||||
[frontend.handler.assets :as assets-handler]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.ui :as ui]
|
||||
[frontend.context.i18n :refer [t]]
|
||||
[frontend.extensions.lightbox :as lightbox]
|
||||
[frontend.util.page-property :as page-property]
|
||||
[frontend.state :as state]
|
||||
[frontend.util :as util]
|
||||
@@ -59,11 +63,11 @@
|
||||
data))))
|
||||
|
||||
(defn persist-hls-data$
|
||||
[{:keys [hls-file]} highlights]
|
||||
[{:keys [hls-file]} highlights extra]
|
||||
(when hls-file
|
||||
(let [repo-cur (state/get-current-repo)
|
||||
repo-dir (config/get-repo-dir repo-cur)
|
||||
data (pr-str {:highlights highlights})]
|
||||
data (pr-str {:highlights highlights :extra extra})]
|
||||
(fs/write-file! repo-cur repo-dir hls-file data {:skip-compare? true}))))
|
||||
|
||||
(defn resolve-hls-data-by-key$
|
||||
@@ -226,7 +230,7 @@
|
||||
(do
|
||||
(state/set-state! :pdf/ref-highlight matched)
|
||||
;; open pdf viewer
|
||||
(state/set-state! :pdf/current (inflate-asset file-path)))
|
||||
(state/set-current-pdf! (inflate-asset file-path)))
|
||||
(js/console.debug "[Unmatched highlight ref]" block)))))))
|
||||
|
||||
(defn goto-block-ref!
|
||||
@@ -242,32 +246,57 @@
|
||||
(when-let [name (:key current)]
|
||||
(rfe/push-state :page {:name (str "hls__" name)} (if id {:anchor (str "block-content-" + id)} nil)))))
|
||||
|
||||
(defn open-lightbox
|
||||
[e]
|
||||
(let [images (js/document.querySelectorAll ".hl-area img")
|
||||
images (to-array images)
|
||||
images (if-not (= (count images) 1)
|
||||
(let [^js image (.closest (.-target e) ".hl-area")
|
||||
image (. image querySelector "img")]
|
||||
(->> images
|
||||
(sort-by (juxt #(.-y %) #(.-x %)))
|
||||
(split-with (complement #{image}))
|
||||
reverse
|
||||
(apply concat)))
|
||||
images)
|
||||
images (for [^js it images] {:src (.-src it)
|
||||
:w (.-naturalWidth it)
|
||||
:h (.-naturalHeight it)})]
|
||||
|
||||
(when (seq images)
|
||||
(lightbox/preview-images! images))))
|
||||
|
||||
(rum/defc area-display
|
||||
[block]
|
||||
(when-let [asset-path' (and block (pdf-utils/get-area-block-asset-url
|
||||
block (db-utils/pull (:db/id (:block/page block)))))]
|
||||
(let [asset-path (editor-handler/make-asset-url asset-path')]
|
||||
(let [asset-path (editor-handler/make-asset-url asset-path')]
|
||||
[:span.hl-area
|
||||
[:img {:src asset-path}]])))
|
||||
[:span.actions
|
||||
(when-not config/publishing?
|
||||
[:button.asset-action-btn.px-1
|
||||
{:title (t :asset/copy)
|
||||
:tabIndex "-1"
|
||||
:on-mouse-down util/stop
|
||||
:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(-> (util/copy-image-to-clipboard (gp-config/remove-asset-protocol asset-path))
|
||||
(p/then #(notification/show! "Copied!" :success))))}
|
||||
(ui/icon "copy")])
|
||||
|
||||
(defn fix-local-asset-pagename
|
||||
[filename]
|
||||
(when-not (string/blank? filename)
|
||||
(let [local-asset? (re-find #"[0-9]{13}_\d$" filename)
|
||||
hls? (re-find #"^hls__" filename)
|
||||
len (count filename)]
|
||||
(if (or local-asset? hls?)
|
||||
(-> filename
|
||||
(subs 0 (if local-asset? (- len 15) len))
|
||||
(string/replace #"^hls__" "")
|
||||
(string/replace "_" " ")
|
||||
(string/trimr))
|
||||
filename))))
|
||||
[:button.asset-action-btn.px-1
|
||||
{:title (t :asset/maximize)
|
||||
:tabIndex "-1"
|
||||
:on-mouse-down util/stop
|
||||
:on-click open-lightbox}
|
||||
|
||||
(ui/icon "maximize")]]
|
||||
[:img {:src asset-path}]])))
|
||||
|
||||
(defn human-page-name
|
||||
[page-name]
|
||||
(cond
|
||||
(string/starts-with? page-name "hls__")
|
||||
(fix-local-asset-pagename page-name)
|
||||
(pdf-utils/fix-local-asset-pagename page-name)
|
||||
|
||||
:else (util/trim-safe page-name)))
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
[frontend.commands :as commands]
|
||||
[frontend.rum :refer [use-atom]]
|
||||
[frontend.state :as state]
|
||||
[frontend.storage :as storage]
|
||||
[frontend.util :as util]
|
||||
[medley.core :as medley]
|
||||
[promesa.core :as p]
|
||||
@@ -44,14 +43,12 @@
|
||||
(rum/use-effect!
|
||||
(fn []
|
||||
(when viewer
|
||||
(when-let [current (:pdf/current @state/state)]
|
||||
(let [active-hl (:pdf/ref-highlight @state/state)
|
||||
page-key (:filename current)
|
||||
last-page (and page-key
|
||||
(util/safe-parse-int (storage/get (str "ls-pdf-last-page-" page-key))))]
|
||||
|
||||
(when (and last-page (nil? active-hl))
|
||||
(set! (.-currentPageNumber viewer) last-page))))))
|
||||
(when-let [_ (:pdf/current @state/state)]
|
||||
(let [active-hl (:pdf/ref-highlight @state/state)]
|
||||
(when-not active-hl
|
||||
(.on (.-eventBus viewer) (name :restore-last-page)
|
||||
(fn [last-page]
|
||||
(set! (.-currentPageNumber viewer) (util/safe-parse-int last-page)))))))))
|
||||
[viewer])
|
||||
nil)
|
||||
|
||||
@@ -665,7 +662,7 @@
|
||||
})]))
|
||||
|
||||
(rum/defc pdf-viewer
|
||||
[url initial-hls ^js pdf-document ops]
|
||||
[_url initial-hls initial-page ^js pdf-document ops]
|
||||
|
||||
(let [*el-ref (rum/create-ref)
|
||||
[state, set-state!] (rum/use-state {:viewer nil :bus nil :link nil :el nil})
|
||||
@@ -675,33 +672,50 @@
|
||||
|
||||
;; instant pdfjs viewer
|
||||
(rum/use-effect!
|
||||
(fn [] (let [^js event-bus (js/pdfjsViewer.EventBus.)
|
||||
^js link-service (js/pdfjsViewer.PDFLinkService. #js {:eventBus event-bus :externalLinkTarget 2})
|
||||
^js el (rum/deref *el-ref)
|
||||
^js viewer (js/pdfjsViewer.PDFViewer.
|
||||
#js {:container el
|
||||
:eventBus event-bus
|
||||
:linkService link-service
|
||||
:findController (js/pdfjsViewer.PDFFindController.
|
||||
#js {:linkService link-service :eventBus event-bus})
|
||||
:textLayerMode 2
|
||||
:annotationMode 2
|
||||
:removePageBorders true})]
|
||||
(. link-service setDocument pdf-document)
|
||||
(. link-service setViewer viewer)
|
||||
(fn []
|
||||
(let [^js event-bus (js/pdfjsViewer.EventBus.)
|
||||
^js link-service (js/pdfjsViewer.PDFLinkService. #js {:eventBus event-bus :externalLinkTarget 2})
|
||||
^js el (rum/deref *el-ref)
|
||||
^js viewer (js/pdfjsViewer.PDFViewer.
|
||||
#js {:container el
|
||||
:eventBus event-bus
|
||||
:linkService link-service
|
||||
:findController (js/pdfjsViewer.PDFFindController.
|
||||
#js {:linkService link-service :eventBus event-bus})
|
||||
:textLayerMode 2
|
||||
:annotationMode 2
|
||||
:removePageBorders true})]
|
||||
|
||||
;; TODO: debug
|
||||
(set! (. js/window -lsPdfViewer) viewer)
|
||||
(. link-service setDocument pdf-document)
|
||||
(. link-service setViewer viewer)
|
||||
|
||||
(p/then (. viewer setDocument pdf-document)
|
||||
#(set-state! {:viewer viewer :bus event-bus :link link-service :el el}))
|
||||
;; events
|
||||
(doto event-bus
|
||||
;; it must be initialized before set-up document
|
||||
(.on "pagesinit"
|
||||
(fn []
|
||||
(set! (. viewer -currentScaleValue) "auto")
|
||||
(set-page-ready! true)))
|
||||
|
||||
;;TODO: destroy
|
||||
(fn []
|
||||
(when-let [last-page (.-currentPageNumber viewer)]
|
||||
(storage/set (str "ls-pdf-last-page-" (util/node-path.basename url)) last-page))
|
||||
(.on (name :ls-update-extra-state)
|
||||
#(when-let [extra (bean/->clj %)]
|
||||
(apply (:set-hls-extra! ops) [extra]))))
|
||||
|
||||
(when pdf-document (.destroy pdf-document)))))
|
||||
(p/then (. viewer setDocument pdf-document)
|
||||
#(set-state! {:viewer viewer :bus event-bus :link link-service :el el}))
|
||||
|
||||
;; TODO: debug
|
||||
(set! (. js/window -lsPdfViewer) viewer)
|
||||
|
||||
;; set initial page
|
||||
(js/setTimeout
|
||||
#(set! (.-currentPageNumber viewer) initial-page) 16)
|
||||
|
||||
;; destroy
|
||||
(fn []
|
||||
(.destroy pdf-document)
|
||||
(set! (. js/window -lsPdfViewer) nil)
|
||||
(.cleanup viewer))))
|
||||
[])
|
||||
|
||||
;; interaction events
|
||||
@@ -710,20 +724,13 @@
|
||||
(when-let [^js viewer (:viewer state)]
|
||||
(let [fn-textlayer-ready
|
||||
(fn [^js p]
|
||||
(set-ano-state! {:loaded-pages (conj (:loaded-pages ano-state) (int (.-pageNumber p)))}))
|
||||
|
||||
fn-page-ready
|
||||
(fn []
|
||||
(set! (. viewer -currentScaleValue) "auto")
|
||||
(set-page-ready! true))]
|
||||
(set-ano-state! {:loaded-pages (conj (:loaded-pages ano-state) (int (.-pageNumber p)))}))]
|
||||
|
||||
(doto (.-eventBus viewer)
|
||||
(.on "pagesinit" fn-page-ready)
|
||||
(.on "textlayerrendered" fn-textlayer-ready))
|
||||
|
||||
#(do
|
||||
(doto (.-eventBus viewer)
|
||||
(.off "pagesinit" fn-page-ready)
|
||||
(.off "textlayerrendered" fn-textlayer-ready))))))
|
||||
|
||||
[(:viewer state)
|
||||
@@ -750,23 +757,27 @@
|
||||
(rum/defc ^:large-vars/data-var pdf-loader
|
||||
[{:keys [url hls-file] :as pdf-current}]
|
||||
(let [*doc-ref (rum/use-ref nil)
|
||||
[state, set-state!] (rum/use-state {:error nil :pdf-document nil :status nil})
|
||||
[hls-state, set-hls-state!] (rum/use-state {:initial-hls nil :latest-hls nil})
|
||||
set-dirty-hls! (fn [latest-hls] ;; TODO: incremental
|
||||
(set-hls-state! {:initial-hls [] :latest-hls latest-hls}))]
|
||||
[loader-state, set-loader-state!] (rum/use-state {:error nil :pdf-document nil :status nil})
|
||||
[hls-state, set-hls-state!] (rum/use-state {:initial-hls nil :latest-hls nil :extra nil :loaded false})
|
||||
[initial-page, set-initial-page!] (rum/use-state 0)
|
||||
set-dirty-hls! (fn [latest-hls] ;; TODO: incremental
|
||||
(set-hls-state! #(merge % {:initial-hls [] :latest-hls latest-hls})))
|
||||
set-hls-extra! (fn [extra]
|
||||
(set-hls-state! #(merge % {:extra extra})))]
|
||||
|
||||
;; load highlights
|
||||
(rum/use-effect!
|
||||
(fn []
|
||||
(p/catch
|
||||
(p/let [data (pdf-assets/load-hls-data$ pdf-current)
|
||||
highlights (:highlights data)]
|
||||
(set-hls-state! {:initial-hls highlights}))
|
||||
(p/let [data (pdf-assets/load-hls-data$ pdf-current)
|
||||
{:keys [highlights extra]} data]
|
||||
(set-initial-page! (util/safe-parse-int (:page extra)))
|
||||
(set-hls-state! {:initial-hls highlights :latest-hls highlights :extra extra :loaded true}))
|
||||
|
||||
;; error
|
||||
(fn [e]
|
||||
(js/console.error "[load hls error]" e)
|
||||
(set-hls-state! {:initial-hls []})))
|
||||
(set-hls-state! {:initial-hls [] :loaded true})))
|
||||
|
||||
;; cancel
|
||||
#())
|
||||
@@ -775,15 +786,16 @@
|
||||
;; cache highlights
|
||||
(rum/use-effect!
|
||||
(fn []
|
||||
(when-let [hls (:latest-hls hls-state)]
|
||||
(when (= :completed (:status loader-state))
|
||||
(p/catch
|
||||
(pdf-assets/persist-hls-data$ pdf-current hls)
|
||||
(pdf-assets/persist-hls-data$
|
||||
pdf-current (:latest-hls hls-state) (:extra hls-state))
|
||||
|
||||
;; write hls file error
|
||||
(fn [e]
|
||||
(js/console.error "[write hls error]" e)))))
|
||||
|
||||
[(:latest-hls hls-state)])
|
||||
[(:latest-hls hls-state) (:extra hls-state)])
|
||||
|
||||
;; load document
|
||||
(rum/use-effect!
|
||||
@@ -795,20 +807,18 @@
|
||||
;;:cMapUrl "https://cdn.jsdelivr.net/npm/pdfjs-dist@2.8.335/cmaps/"
|
||||
:cMapPacked true}]
|
||||
|
||||
(set-state! {:status :loading})
|
||||
(set-loader-state! {:status :loading})
|
||||
|
||||
(-> (get-doc$ (clj->js opts))
|
||||
(p/then #(set-state! {:pdf-document %}))
|
||||
(p/catch #(set-state! {:error %}))
|
||||
(p/finally #(set-state! {:status :completed})))
|
||||
|
||||
(p/then #(set-loader-state! {:pdf-document % :status :completed}))
|
||||
(p/catch #(set-loader-state! {:error %})))
|
||||
#()))
|
||||
[url])
|
||||
|
||||
(rum/use-effect!
|
||||
(fn []
|
||||
(when-let [error (:error state)]
|
||||
(dd "[ERROR loader]" (:error state))
|
||||
(when-let [error (:error loader-state)]
|
||||
(dd "[ERROR loader]" (:error loader-state))
|
||||
(case (.-name error)
|
||||
"MissingPDFException"
|
||||
(do
|
||||
@@ -835,24 +845,24 @@
|
||||
:error
|
||||
false)
|
||||
(state/set-state! :pdf/current nil)))))
|
||||
[(:error state)])
|
||||
[(:error loader-state)])
|
||||
|
||||
(rum/bind-context
|
||||
[*highlights-ctx* hls-state]
|
||||
[:div.extensions__pdf-loader {:ref *doc-ref}
|
||||
(let [status-doc (:status state)
|
||||
(let [status-doc (:status loader-state)
|
||||
initial-hls (:initial-hls hls-state)]
|
||||
|
||||
(if (or (= status-doc :loading)
|
||||
(nil? initial-hls))
|
||||
(if (= status-doc :loading)
|
||||
|
||||
[:div.flex.justify-center.items-center.h-screen.text-gray-500.text-lg
|
||||
svg/loading]
|
||||
|
||||
[(rum/with-key (pdf-viewer
|
||||
url initial-hls
|
||||
(:pdf-document state)
|
||||
{:set-dirty-hls! set-dirty-hls!}) "pdf-viewer")]))])))
|
||||
(when-let [pdf-document (and (:loaded hls-state) (:pdf-document loader-state))]
|
||||
[(rum/with-key (pdf-viewer
|
||||
url initial-hls initial-page pdf-document
|
||||
{:set-dirty-hls! set-dirty-hls!
|
||||
:set-hls-extra! set-hls-extra!}) "pdf-viewer")])))])))
|
||||
|
||||
(rum/defc pdf-container
|
||||
[{:keys [identity] :as pdf-current}]
|
||||
|
||||
@@ -101,15 +101,21 @@ input::-webkit-inner-spin-button {
|
||||
|
||||
> .nu {
|
||||
padding-right: 4px;
|
||||
|
||||
|
||||
input {
|
||||
user-select: inherit;
|
||||
width: 35px;
|
||||
text-align: right;
|
||||
padding-right: 4px;
|
||||
padding-left: 2px;
|
||||
height: 18px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: 15px;
|
||||
|
||||
&.is-long {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -841,6 +847,16 @@ input::-webkit-inner-spin-button {
|
||||
overflow: hidden;
|
||||
margin-top: 4px;
|
||||
|
||||
.actions {
|
||||
@apply absolute right-1 top-1 flex opacity-0 transition-opacity;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.actions {
|
||||
@apply opacity-100;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
|
||||
@@ -433,6 +433,14 @@
|
||||
#(js-delete (. el -dataset) "theme")))
|
||||
[viewer-theme])
|
||||
|
||||
;; export page state
|
||||
(rum/use-effect!
|
||||
(fn []
|
||||
(when viewer
|
||||
(.dispatch (.-eventBus viewer) (name :ls-update-extra-state)
|
||||
#js {:page current-page-num})))
|
||||
[viewer current-page-num])
|
||||
|
||||
;; pager hooks
|
||||
(rum/use-effect!
|
||||
(fn []
|
||||
@@ -511,14 +519,16 @@
|
||||
[:span.nu.flex.items-center.opacity-70
|
||||
[:input {:ref *page-ref
|
||||
:type "number"
|
||||
:class (util/classnames [{:is-long (> (util/safe-parse-int current-page-num) 999)}])
|
||||
:default-value current-page-num
|
||||
:on-mouse-enter #(.select ^js (.-target %))
|
||||
:on-key-up (fn [^js e]
|
||||
(let [^js input (.-target e)
|
||||
value (util/safe-parse-int (.-value input))]
|
||||
(set-current-page-num! value)
|
||||
(when (and (= (.-keyCode e) 13) value (> value 0))
|
||||
(set! (. viewer -currentPageNumber)
|
||||
(if (> value total-page-num) total-page-num value)))))}]
|
||||
(->> (if (> value total-page-num) total-page-num value)
|
||||
(set! (. viewer -currentPageNumber))))))}]
|
||||
[:small "/ " total-page-num]]
|
||||
|
||||
[:span.ct.flex.items-center
|
||||
|
||||
@@ -173,6 +173,20 @@
|
||||
(string/replace #"\|#\|([a-zA-Z_])" " $1")
|
||||
(string/replace sp "")))))
|
||||
|
||||
(defn fix-local-asset-pagename
|
||||
[filename]
|
||||
(when-not (string/blank? filename)
|
||||
(let [local-asset? (re-find #"[0-9]{13}_\d$" filename)
|
||||
hls? (re-find #"^hls__" filename)
|
||||
len (count filename)]
|
||||
(if (or local-asset? hls?)
|
||||
(-> filename
|
||||
(subs 0 (if local-asset? (- len 15) len))
|
||||
(string/replace #"^hls__" "")
|
||||
(string/replace "_" " ")
|
||||
(string/trimr))
|
||||
filename))))
|
||||
|
||||
;; TODO: which viewer instance?
|
||||
(defn next-page
|
||||
[]
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
(ns frontend.extensions.sci
|
||||
(:require [sci.core :as sci]
|
||||
[frontend.util :as util]))
|
||||
[frontend.util :as util]
|
||||
[goog.dom]
|
||||
[goog.object]
|
||||
[goog.string]))
|
||||
|
||||
;; Some helpers
|
||||
(def sum (partial apply +))
|
||||
@@ -9,27 +12,37 @@
|
||||
(/ (reduce + coll) (count coll)))
|
||||
|
||||
(defn eval-string
|
||||
[s]
|
||||
(try
|
||||
(sci/eval-string s {:bindings {'sum sum
|
||||
'average average
|
||||
'parseFloat js/parseFloat
|
||||
'isNaN js/isNaN
|
||||
'log js/console.log
|
||||
'pprint util/pp-str}})
|
||||
(catch :default e
|
||||
(println "Query: sci eval failed:")
|
||||
(js/console.error e))))
|
||||
"Second arg is a map of options for sci/eval-string"
|
||||
([s]
|
||||
(eval-string s {}))
|
||||
([s options]
|
||||
(try
|
||||
(sci/eval-string s (merge-with merge
|
||||
{:bindings {'sum sum
|
||||
'average average
|
||||
'parseFloat js/parseFloat
|
||||
'isNaN js/isNaN
|
||||
'log js/console.log
|
||||
'pprint util/pp-str}}
|
||||
options))
|
||||
(catch :default e
|
||||
(println "Query: sci eval failed:")
|
||||
(js/console.error e)))))
|
||||
|
||||
(defn call-fn
|
||||
[f & args]
|
||||
(apply f args))
|
||||
|
||||
(defn eval-result
|
||||
[code]
|
||||
"Evaluate code with sci in a block context"
|
||||
[code block]
|
||||
[:div
|
||||
[:code "Results:"]
|
||||
[:div.results.mt-1
|
||||
[:pre.code
|
||||
(let [result (eval-string code)]
|
||||
(str result))]]])
|
||||
(let [result (eval-string code {:bindings {'block block}
|
||||
:classes {'logseq-api js/logseq.api
|
||||
'logseq-gp js/logseq.graph_parser
|
||||
:allow :all}})]
|
||||
(if (and (vector? result) (:hiccup (meta result)))
|
||||
result
|
||||
[:pre.code (str result)]))]])
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
(string/trim (or (state/get-default-journal-template) "")))
|
||||
(= (string/trim content) "-")
|
||||
(= (string/trim content) "*")))
|
||||
(handle-add-and-change! repo path content db-content mtime true))
|
||||
(handle-add-and-change! repo path content db-content mtime (not global-dir))) ;; no backup for global dir
|
||||
|
||||
(and (= "unlink" type)
|
||||
(db/file-exists? repo path))
|
||||
|
||||
@@ -632,8 +632,8 @@
|
||||
(when mode
|
||||
(state/set-custom-theme! mode theme)
|
||||
(state/set-theme-mode! mode))
|
||||
(hook-plugin-app :theme-changed theme)
|
||||
(state/set-state! :plugin/selected-theme url))))
|
||||
(state/set-state! :plugin/selected-theme url)
|
||||
(hook-plugin-app :theme-changed theme))))
|
||||
|
||||
(.on "reset-custom-theme" (fn [^js themes]
|
||||
(let [themes (bean/->clj themes)
|
||||
|
||||
@@ -231,7 +231,7 @@ html.is-zoomed-native-ios {
|
||||
|
||||
.cp__graph-picker {
|
||||
padding: 58px 20px 20px 20px;
|
||||
background: var(--ls-search-background-color);
|
||||
background: var(--ls-primary-background-color);
|
||||
|
||||
> h1 {
|
||||
position: absolute;
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
(:require ["dompurify" :as DOMPurify]))
|
||||
|
||||
(def sanitization-options (clj->js {:ADD_TAGS ["iframe"]
|
||||
:ALLOW_UNKNOWN_PROTOCOLS true}))
|
||||
:ADD_ATTR ["is"]
|
||||
:ALLOW_UNKNOWN_PROTOCOLS true }))
|
||||
|
||||
(defn sanitize-html
|
||||
[html]
|
||||
|
||||
@@ -296,10 +296,6 @@
|
||||
;; (re-)fetches get-current-repo needlessly
|
||||
;; TODO: Add consistent validation. Only a few config options validate at get time
|
||||
|
||||
(defn get-current-pdf
|
||||
[]
|
||||
(:pdf/current @state))
|
||||
|
||||
(def default-config
|
||||
"Default config for a repo-specific, user config"
|
||||
{:feature/enable-search-remove-accents? true
|
||||
@@ -1967,3 +1963,16 @@ Similar to re-frame subscriptions"
|
||||
[]
|
||||
(when (mobile-util/native-ios?)
|
||||
(get-in @state [:mobile/container-urls :iCloudContainerUrl])))
|
||||
|
||||
(defn get-current-pdf
|
||||
[]
|
||||
(:pdf/current @state))
|
||||
|
||||
(defn set-current-pdf!
|
||||
[inflated-file]
|
||||
(let [settle-file! #(set-state! :pdf/current inflated-file)]
|
||||
(if-not (get-current-pdf)
|
||||
(settle-file!)
|
||||
(when (apply not= (map :identity [inflated-file (get-current-pdf)]))
|
||||
(set-state! :pdf/current nil)
|
||||
(js/setTimeout #(settle-file!) 16)))))
|
||||
|
||||
@@ -366,10 +366,12 @@
|
||||
(defn apply-custom-theme-effect! [theme]
|
||||
(when config/lsp-enabled?
|
||||
(when-let [custom-theme (state/sub [:ui/custom-theme (keyword theme)])]
|
||||
(when-let [url (:url custom-theme)]
|
||||
;; If the name is nil, the user has not set a custom theme (initially {:mode light/dark}).
|
||||
;; The url is not used because the default theme does not have an url.
|
||||
(if (some? (:name custom-theme))
|
||||
(js/LSPluginCore.selectTheme (bean/->js custom-theme)
|
||||
(bean/->js {:emit true}))
|
||||
(state/set-state! :plugin/selected-theme url)))))
|
||||
(bean/->js {:emit false}))
|
||||
(state/set-state! :plugin/selected-theme (:url custom-theme))))))
|
||||
|
||||
(defn setup-system-theme-effect!
|
||||
[]
|
||||
|
||||
@@ -1419,3 +1419,13 @@
|
||||
(<= (+ (.-bottom r) 64)
|
||||
(or (.-innerHeight js/window)
|
||||
(js/document.documentElement.clientHeight))))))))
|
||||
|
||||
#?(:cljs
|
||||
(defn copy-image-to-clipboard
|
||||
[src]
|
||||
(-> (js/fetch src)
|
||||
(.then (fn [data]
|
||||
(-> (.blob data)
|
||||
(.then (fn [blob]
|
||||
(js/navigator.clipboard.write (clj->js [(js/ClipboardItem. (clj->js {(.-type blob) blob}))]))))
|
||||
(.catch js/console.error)))))))
|
||||
@@ -525,9 +525,10 @@
|
||||
|
||||
(def ^:export insert_block
|
||||
(fn [block-uuid-or-page-name content ^js opts]
|
||||
(let [{:keys [before sibling isPageBlock customUUID properties]} (bean/->clj opts)
|
||||
(let [{:keys [before sibling focus isPageBlock customUUID properties]} (bean/->clj opts)
|
||||
page-name (and isPageBlock block-uuid-or-page-name)
|
||||
custom-uuid (or customUUID (:id properties))
|
||||
edit-block? (if (nil? focus) true focus)
|
||||
_ (when (not (string/blank? custom-uuid))
|
||||
(when-not (util/uuid-string? custom-uuid)
|
||||
(throw (js/Error.
|
||||
@@ -557,6 +558,7 @@
|
||||
{:block-uuid block-uuid'
|
||||
:sibling? sibling?
|
||||
:before? before?
|
||||
:edit-block? edit-block?
|
||||
:page page-name
|
||||
:custom-uuid custom-uuid
|
||||
:properties (merge properties
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
(ns frontend.extensions.pdf.assets-test
|
||||
(:require [clojure.test :as test :refer [are deftest testing]]
|
||||
[frontend.extensions.pdf.assets :as assets]))
|
||||
[frontend.extensions.pdf.utils :as pdf-utils]))
|
||||
|
||||
(deftest fix-local-asset-pagename
|
||||
(testing "matched filenames"
|
||||
(are [x y] (= y (assets/fix-local-asset-pagename x))
|
||||
(are [x y] (= y (pdf-utils/fix-local-asset-pagename x))
|
||||
"2015_Book_Intertwingled_1659920114630_0" "2015 Book Intertwingled"
|
||||
"hls__2015_Book_Intertwingled_1659920114630_0" "2015 Book Intertwingled"
|
||||
"hls/2015_Book_Intertwingled_1659920114630_0" "hls/2015 Book Intertwingled"))
|
||||
(testing "non matched filenames"
|
||||
(are [x y] (= y (assets/fix-local-asset-pagename x))
|
||||
(are [x y] (= y (pdf-utils/fix-local-asset-pagename x))
|
||||
"foo" "foo"
|
||||
"foo_bar" "foo_bar"
|
||||
"foo__bar" "foo__bar"
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
;; Setup custom shortcuts under `:shortcuts` key
|
||||
;; Syntax:
|
||||
;; 1. `+` means keys pressing simultaneously. eg: `ctrl+shift+a`
|
||||
;; 2. ` ` empty space between keys represents key chords. eg: `t s` means press `s` follow by `t`
|
||||
;; 2. ` ` empty space between keys represents key chords. eg: `t s` means press `t` followed by `s`
|
||||
;; 3. `mod` means `Ctrl` for Windows/Linux and `Command` for Mac
|
||||
;; 4. use `false` to disable particular shortcut
|
||||
;; 4. you can define multiple bindings for one action, eg `["ctrl+j" "down"]`
|
||||
|
||||
@@ -10,37 +10,37 @@
|
||||
"dev:vite": "tsup --watch --sourcemap inline"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@radix-ui/react-context-menu": "^1.0.0",
|
||||
"@radix-ui/react-dropdown-menu": "^1.0.0",
|
||||
"@radix-ui/react-context-menu": "^2.0.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.1",
|
||||
"@radix-ui/react-popover": "^1.0.0",
|
||||
"@radix-ui/react-select": "^1.0.0",
|
||||
"@radix-ui/react-separator": "^1.0.0",
|
||||
"@radix-ui/react-select": "^1.1.2",
|
||||
"@radix-ui/react-separator": "^1.0.1",
|
||||
"@radix-ui/react-slider": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.0.0",
|
||||
"@radix-ui/react-toggle": "^1.0.0",
|
||||
"@radix-ui/react-toggle-group": "^1.0.0",
|
||||
"@radix-ui/react-switch": "^1.0.1",
|
||||
"@radix-ui/react-toggle": "^1.0.1",
|
||||
"@radix-ui/react-toggle-group": "^1.0.1",
|
||||
"@tldraw/core": "2.0.0-alpha.1",
|
||||
"@tldraw/react": "2.0.0-alpha.1",
|
||||
"@tldraw/vec": "2.0.0-alpha.1",
|
||||
"@types/node": "^17.0.42",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"concurrently": "^7.2.1",
|
||||
"esbuild": "^0.15.10",
|
||||
"mobx": "^6.6.2",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"concurrently": "^7.5.0",
|
||||
"esbuild": "^0.15.14",
|
||||
"mobx": "^6.7.0",
|
||||
"mobx-react-lite": "^3.4.0",
|
||||
"perfect-freehand": "^1.2.0",
|
||||
"polished": "^4.0.0",
|
||||
"postcss": "^8.4.17",
|
||||
"postcss": "^8.4.19",
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0",
|
||||
"react-virtuoso": "^3.1.0",
|
||||
"react-virtuoso": "^3.1.3",
|
||||
"rimraf": "3.0.2",
|
||||
"shadow-cljs": "^2.19.5",
|
||||
"tsup": "^6.2.3",
|
||||
"typescript": "^4.8.2",
|
||||
"zx": "^7.1.0"
|
||||
"shadow-cljs": "^2.20.11",
|
||||
"tsup": "^6.5.0",
|
||||
"typescript": "^4.9.3",
|
||||
"zx": "^7.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as React from 'react'
|
||||
import type { Shape } from '../../lib'
|
||||
import { TablerIcon } from '../icons'
|
||||
import { Button } from '../Button'
|
||||
import { ToolButton } from '../ToolButton'
|
||||
import { ZoomMenu } from '../ZoomMenu'
|
||||
import * as Separator from '@radix-ui/react-separator'
|
||||
|
||||
@@ -30,6 +31,13 @@ export const ActionBar = observer(function ActionBar(): JSX.Element {
|
||||
return (
|
||||
<div className="tl-action-bar">
|
||||
<div className="tl-toolbar tl-history-bar">
|
||||
<ToolButton title="Select" id="select" icon="select-cursor" />
|
||||
<ToolButton
|
||||
title="Move"
|
||||
id="move"
|
||||
icon={app.isIn('move.panning') ? 'hand-grab' : 'hand-stop'}
|
||||
/>
|
||||
<Separator.Root className="tl-toolbar-separator" orientation="vertical" />
|
||||
<Button title="Undo" onClick={undo}>
|
||||
<TablerIcon name="arrow-back-up" />
|
||||
</Button>
|
||||
|
||||
@@ -356,6 +356,7 @@ const SwatchAction = observer(() => {
|
||||
return (
|
||||
<ColorInput
|
||||
title="Color Picker"
|
||||
popoverSide="top"
|
||||
color={color}
|
||||
opacity={shapes[0].props.opacity}
|
||||
collisionRef={document.getElementById('main-content-container')}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useApp } from '@tldraw/react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import * as React from 'react'
|
||||
import { ToolButton } from '../ToolButton'
|
||||
import * as Popover from '@radix-ui/react-popover'
|
||||
import { TablerIcon } from '../icons'
|
||||
|
||||
export const GeometryTools = observer(function GeometryTools() {
|
||||
const geometries = [
|
||||
{
|
||||
id: 'box',
|
||||
icon: 'square',
|
||||
title: 'Rectangle',
|
||||
},
|
||||
{
|
||||
id: 'ellipse',
|
||||
icon: 'circle',
|
||||
title: 'Circle',
|
||||
},
|
||||
{
|
||||
id: 'polygon',
|
||||
icon: 'triangle',
|
||||
title: 'Triangle',
|
||||
},
|
||||
]
|
||||
|
||||
const app = useApp()
|
||||
const [activeGeomId, setActiveGeomId] = React.useState(
|
||||
() => (geometries.find(geo => geo.id === app.selectedTool.id) ?? geometries[0]).id
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
setActiveGeomId(prevId => {
|
||||
return geometries.find(geo => geo.id === app.selectedTool.id)?.id ?? prevId
|
||||
})
|
||||
}, [app.selectedTool.id])
|
||||
|
||||
return (
|
||||
<Popover.Root>
|
||||
<Popover.Trigger className="tl-geometry-tools-pane-anchor">
|
||||
<ToolButton {...geometries.find(geo => geo.id === activeGeomId)!} />
|
||||
<TablerIcon className="tl-popover-indicator" name="chevron-down-left" />
|
||||
</Popover.Trigger>
|
||||
|
||||
<Popover.Content className="tl-popover-content" side="left" sideOffset={15}>
|
||||
<div className="tl-toolbar tl-geometry-toolbar">
|
||||
{geometries.map(props => (
|
||||
<ToolButton key={props.id} {...props} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Popover.Arrow className="tl-popover-arrow" />
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
export * from './GeometryTools'
|
||||
@@ -1,114 +1,41 @@
|
||||
import { TLMoveTool, TLSelectTool } from '@tldraw/core'
|
||||
import { useApp } from '@tldraw/react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import * as React from 'react'
|
||||
import { Button } from '../Button'
|
||||
import { TablerIcon, LogseqIcon } from '../icons'
|
||||
|
||||
interface ToolButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
id: string
|
||||
icon: string | React.ReactNode
|
||||
}
|
||||
|
||||
const ToolButton = observer(({ id, icon, title, ...props }: ToolButtonProps) => {
|
||||
const app = useApp()
|
||||
|
||||
const handleToolClick = React.useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const tool = e.currentTarget.dataset.tool
|
||||
if (tool) app.selectTool(tool)
|
||||
},
|
||||
[app]
|
||||
)
|
||||
|
||||
// Tool must exist
|
||||
const Tool = [...app.Tools, TLSelectTool, TLMoveTool]?.find(T => T.id === id)
|
||||
|
||||
const shortcut = ((Tool as any)['shortcut'] as string[])?.[0]
|
||||
|
||||
const titleWithShortcut = shortcut ? `${title} (${shortcut})` : title
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
title={titleWithShortcut}
|
||||
data-tool={id}
|
||||
data-selected={id === app.selectedTool.id}
|
||||
onClick={handleToolClick}
|
||||
>
|
||||
{typeof icon === 'string' ? <TablerIcon name={icon} /> : icon}
|
||||
</Button>
|
||||
)
|
||||
})
|
||||
|
||||
const GeometryToolButtons = observer(() => {
|
||||
const geometries = [
|
||||
{
|
||||
id: 'box',
|
||||
icon: 'square',
|
||||
title: 'Rectangle',
|
||||
},
|
||||
{
|
||||
id: 'ellipse',
|
||||
icon: 'circle',
|
||||
title: 'Circle',
|
||||
},
|
||||
{
|
||||
id: 'polygon',
|
||||
icon: 'triangle',
|
||||
title: 'Triangle',
|
||||
},
|
||||
]
|
||||
|
||||
const app = useApp()
|
||||
const [activeGeomId, setActiveGeomId] = React.useState(
|
||||
() => (geometries.find(geo => geo.id === app.selectedTool.id) ?? geometries[0]).id
|
||||
)
|
||||
|
||||
const [paneActive, setPaneActive] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
setActiveGeomId(prevId => {
|
||||
return geometries.find(geo => geo.id === app.selectedTool.id)?.id ?? prevId
|
||||
})
|
||||
}, [app.selectedTool.id])
|
||||
|
||||
return (
|
||||
<div
|
||||
className="tl-geometry-tools-pane-anchor"
|
||||
onMouseEnter={() => setPaneActive(true)}
|
||||
onMouseLeave={() => setPaneActive(false)}
|
||||
>
|
||||
{<ToolButton {...geometries.find(geo => geo.id === activeGeomId)!} />}
|
||||
{paneActive && (
|
||||
<div className="tl-geometry-tools-pane">
|
||||
{geometries.map(props => (
|
||||
<ToolButton key={props.id} {...props} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
import { ToolButton } from '../ToolButton'
|
||||
import { GeometryTools } from '../GeometryTools'
|
||||
import { ColorInput } from '../inputs/ColorInput'
|
||||
import * as Separator from '@radix-ui/react-separator'
|
||||
|
||||
export const PrimaryTools = observer(function PrimaryTools() {
|
||||
const app = useApp()
|
||||
|
||||
const handleSetColor = React.useCallback((color: string) => {
|
||||
app.api.setColor(color)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="tl-primary-tools">
|
||||
<div className="tl-toolbar tl-tools-floating-panel">
|
||||
<ToolButton title="Select" id="select" icon="select-cursor" />
|
||||
<ToolButton
|
||||
title="Move"
|
||||
id="move"
|
||||
icon={app.isIn('move.panning') ? 'hand-grab' : 'hand-stop'}
|
||||
/>
|
||||
<ToolButton title="Add block or page" id="logseq-portal" icon="circle-plus" />
|
||||
<Separator.Root className="tl-toolbar-separator" orientation="horizontal" />
|
||||
<ToolButton title="Draw" id="pencil" icon="ballpen" />
|
||||
<ToolButton title="Highlight" id="highlighter" icon="highlight" />
|
||||
<ToolButton title="Eraser" id="erase" icon="eraser" />
|
||||
<ToolButton title="Connector" id="line" icon="connector" />
|
||||
<ToolButton title="Text" id="text" icon="text" />
|
||||
<GeometryToolButtons />
|
||||
<ToolButton title="Logseq Portal" id="logseq-portal" icon={<LogseqIcon />} />
|
||||
<GeometryTools />
|
||||
<Separator.Root
|
||||
className="tl-toolbar-separator"
|
||||
orientation="horizontal"
|
||||
style={{ margin: '0 -4px' }}
|
||||
/>
|
||||
<ColorInput
|
||||
title="Color Picker"
|
||||
popoverSide="left"
|
||||
color={app.settings.color}
|
||||
collisionRef={document.getElementById('main-content-container')}
|
||||
setColor={handleSetColor}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { TLMoveTool, TLSelectTool } from '@tldraw/core'
|
||||
import { useApp } from '@tldraw/react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import * as React from 'react'
|
||||
import { Button } from '../Button'
|
||||
import { TablerIcon } from '../icons'
|
||||
|
||||
export interface ToolButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
id: string
|
||||
icon: string | React.ReactNode
|
||||
}
|
||||
|
||||
export const ToolButton = observer(({ id, icon, title, ...props }: ToolButtonProps) => {
|
||||
const app = useApp()
|
||||
|
||||
const handleToolClick = React.useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const tool = e.currentTarget.dataset.tool
|
||||
if (tool) app.selectTool(tool)
|
||||
},
|
||||
[app]
|
||||
)
|
||||
|
||||
// Tool must exist
|
||||
const Tool = [...app.Tools, TLSelectTool, TLMoveTool]?.find(T => T.id === id)
|
||||
|
||||
const shortcut = ((Tool as any)['shortcut'] as string[])?.[0]
|
||||
|
||||
const titleWithShortcut = shortcut ? `${title} (${shortcut})` : title
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
title={titleWithShortcut}
|
||||
data-tool={id}
|
||||
data-selected={id === app.selectedTool.id}
|
||||
onClick={handleToolClick}
|
||||
>
|
||||
{typeof icon === 'string' ? <TablerIcon name={icon} /> : icon}
|
||||
</Button>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
export * from './ToolButton'
|
||||
File diff suppressed because one or more lines are too long
@@ -1,2 +1 @@
|
||||
export * from './LogseqIcon'
|
||||
export * from './TablerIcon'
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import * as React from 'react'
|
||||
import * as Popover from '@radix-ui/react-popover'
|
||||
import type { Side } from '@radix-ui/react-popper'
|
||||
import * as Slider from '@radix-ui/react-slider'
|
||||
import { TablerIcon } from '../icons'
|
||||
import { Color } from '@tldraw/core'
|
||||
|
||||
interface ColorInputProps extends React.InputHTMLAttributes<HTMLButtonElement> {
|
||||
color: string
|
||||
opacity: number
|
||||
color?: string
|
||||
opacity?: number
|
||||
collisionRef: HTMLElement | null
|
||||
popoverSide: Side
|
||||
setColor: (value: string) => void
|
||||
setOpacity: (value: number) => void
|
||||
setOpacity?: (value: number) => void
|
||||
}
|
||||
|
||||
export function ColorInput({
|
||||
color,
|
||||
opacity,
|
||||
collisionRef,
|
||||
popoverSide,
|
||||
setColor,
|
||||
setOpacity,
|
||||
...rest
|
||||
@@ -35,11 +39,11 @@ export function ColorInput({
|
||||
|
||||
return (
|
||||
<Popover.Root>
|
||||
<Popover.Trigger className="tl-color-drip mx-1">{renderColor(color)}</Popover.Trigger>
|
||||
<Popover.Trigger className="tl-color-drip">{renderColor(color)}</Popover.Trigger>
|
||||
|
||||
<Popover.Content
|
||||
className="tl-popover-content"
|
||||
side="top"
|
||||
className="tl-popover-content p-1"
|
||||
side={popoverSide}
|
||||
sideOffset={15}
|
||||
collisionBoundary={collisionRef}
|
||||
>
|
||||
@@ -55,21 +59,23 @@ export function ColorInput({
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mx-1 my-2">
|
||||
<Slider.Root
|
||||
defaultValue={[opacity]}
|
||||
onValueCommit={value => setOpacity(value[0])}
|
||||
max={1}
|
||||
step={0.1}
|
||||
aria-label="Opacity"
|
||||
className="tl-slider-root"
|
||||
>
|
||||
<Slider.Track className="tl-slider-track">
|
||||
<Slider.Range className="tl-slider-range" />
|
||||
</Slider.Track>
|
||||
<Slider.Thumb className="tl-slider-thumb" />
|
||||
</Slider.Root>
|
||||
</div>
|
||||
{setOpacity && (
|
||||
<div className="mx-1 my-2">
|
||||
<Slider.Root
|
||||
defaultValue={[opacity]}
|
||||
onValueCommit={value => setOpacity(value[0])}
|
||||
max={1}
|
||||
step={0.1}
|
||||
aria-label="Opacity"
|
||||
className="tl-slider-root"
|
||||
>
|
||||
<Slider.Track className="tl-slider-track">
|
||||
<Slider.Range className="tl-slider-range" />
|
||||
</Slider.Track>
|
||||
<Slider.Thumb className="tl-slider-thumb" />
|
||||
</Slider.Root>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Popover.Arrow className="tl-popover-arrow" />
|
||||
</Popover.Content>
|
||||
|
||||
@@ -310,6 +310,8 @@ export function usePaste() {
|
||||
point: [point[0], point[1]],
|
||||
size: [400, 0], // use 0 here to enable auto-resize
|
||||
pageId: blockRef,
|
||||
fill: app.settings.color,
|
||||
stroke: app.settings.color,
|
||||
blockType: 'B' as 'B',
|
||||
},
|
||||
]
|
||||
@@ -324,6 +326,8 @@ export function usePaste() {
|
||||
point: [point[0], point[1]],
|
||||
size: [400, 0], // use 0 here to enable auto-resize
|
||||
pageId: pageName,
|
||||
fill: app.settings.color,
|
||||
stroke: app.settings.color,
|
||||
blockType: 'P' as 'P',
|
||||
},
|
||||
]
|
||||
@@ -339,6 +343,8 @@ export function usePaste() {
|
||||
size: [400, 0], // use 0 here to enable auto-resize
|
||||
point: [point[0], point[1]],
|
||||
pageId: uuid,
|
||||
fill: app.settings.color,
|
||||
stroke: app.settings.color,
|
||||
blockType: 'B' as 'B',
|
||||
compact: true,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { SVGContainer, TLComponentProps } from '@tldraw/react'
|
||||
import { SVGContainer, TLComponentProps, useApp } from '@tldraw/react'
|
||||
import { TLBoxShape, TLBoxShapeProps, getComputedColor } from '@tldraw/core'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { CustomStyleProps, withClampedStyles } from './style-props'
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import type { Shape } from '.'
|
||||
|
||||
export interface CustomStyleProps {
|
||||
stroke: string
|
||||
fill: string
|
||||
noFill: boolean
|
||||
strokeWidth: number
|
||||
strokeType: 'dashed' | 'line'
|
||||
|
||||
@@ -4,6 +4,6 @@ import { BoxShape, type Shape } from '../shapes'
|
||||
|
||||
export class BoxTool extends TLBoxTool<BoxShape, Shape, TLReactEventMap> {
|
||||
static id = 'box'
|
||||
static shortcut = ['8']
|
||||
static shortcut = ['7']
|
||||
Shape = BoxShape
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ import type { Shape } from '../shapes'
|
||||
|
||||
export class NuEraseTool extends TLEraseTool<Shape, TLReactEventMap> {
|
||||
static id = 'erase'
|
||||
static shortcut = ['5']
|
||||
static shortcut = ['4']
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { HighlighterShape, type Shape } from '../shapes'
|
||||
|
||||
export class HighlighterTool extends TLDrawTool<HighlighterShape, Shape, TLReactEventMap> {
|
||||
static id = 'highlighter'
|
||||
static shortcut = ['4']
|
||||
static shortcut = ['3']
|
||||
Shape = HighlighterShape
|
||||
simplify = true
|
||||
simplifyTolerance = 0.618
|
||||
|
||||
@@ -6,6 +6,6 @@ import { LineShape, type Shape } from '../shapes'
|
||||
export class LineTool extends TLLineTool<LineShape, Shape, TLReactEventMap> {
|
||||
static id = 'line'
|
||||
// not sure why "c" is not working in Logseq?
|
||||
static shortcut = ['6']
|
||||
static shortcut = ['5']
|
||||
Shape = LineShape
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export class LogseqPortalTool extends TLTool<
|
||||
TLApp<Shape, TLReactEventMap>
|
||||
> {
|
||||
static id = 'logseq-portal'
|
||||
static shortcut = ['9']
|
||||
static shortcut = ['1']
|
||||
static states = [IdleState, CreatingState]
|
||||
static initial = 'idle'
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ export class CreatingState extends TLToolState<
|
||||
parentId: this.app.currentPage.id,
|
||||
point: Vec.sub(this.app.inputs.originPoint, this.offset),
|
||||
size: LogseqPortalShape.defaultProps.size,
|
||||
fill: this.app.settings.color,
|
||||
stroke: this.app.settings.color,
|
||||
} as any)
|
||||
this.creatingShape = shape
|
||||
this.app.currentPage.addShapes(shape)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PencilShape, type Shape } from '../shapes'
|
||||
|
||||
export class PencilTool extends TLDrawTool<PencilShape, Shape, TLReactEventMap> {
|
||||
static id = 'pencil'
|
||||
static shortcut = ['3']
|
||||
static shortcut = ['2']
|
||||
Shape = PencilShape
|
||||
simplify = false
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ import { TextShape, type Shape } from '../shapes'
|
||||
|
||||
export class TextTool extends TLTextTool<TextShape, Shape, TLReactEventMap> {
|
||||
static id = 'text'
|
||||
static shortcut = ['7']
|
||||
static shortcut = ['6']
|
||||
Shape = TextShape
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
--color-text: var(--ls-primary-text-color);
|
||||
--color-text-inverted: var(--ls-tertiary-background-color);
|
||||
--color-hover: var(--ls-secondary-background-color);
|
||||
--color-selectedStroke: rgb(42, 123, 253);
|
||||
--color-selectedFill: #4285f4;
|
||||
--color-selectedStroke: var(--color-indigo-900);
|
||||
--color-selectedFill: var(--color-indigo-500);
|
||||
--color-selectedContrast: #fff;
|
||||
--shadow-small: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow-medium: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
@@ -331,20 +331,22 @@ button.tl-select-input-trigger {
|
||||
|
||||
.tl-geometry-tools-pane-anchor {
|
||||
@apply relative;
|
||||
|
||||
&[aria-expanded='true'] {
|
||||
.tl-popover-indicator {
|
||||
transform: rotate(180deg);
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tl-geometry-tools-pane {
|
||||
@apply absolute rounded-lg flex flex-col items-center justify-center;
|
||||
right: 100%;
|
||||
top: calc(50% - 54px);
|
||||
background-color: var(--ls-secondary-background-color);
|
||||
padding: 4px;
|
||||
gap: 8px;
|
||||
box-shadow: var(--shadow-small);
|
||||
.tl-popover-indicator {
|
||||
@apply absolute text-2xs;
|
||||
|
||||
.tl-button:not([data-selected='true']):hover {
|
||||
background-color: var(--ls-tertiary-background-color);
|
||||
}
|
||||
opacity: 0.8;
|
||||
pointer-events: none;
|
||||
left: 0;
|
||||
bottom: -3px;
|
||||
}
|
||||
|
||||
.floating-panel[data-tool-locked='true'] > .tl-button[data-selected='true']::after {
|
||||
@@ -874,6 +876,11 @@ html[data-theme='dark'] {
|
||||
background-color: var(--ls-border-color);
|
||||
width: 1px;
|
||||
opacity: 0.5;
|
||||
|
||||
&[data-orientation='horizontal'] {
|
||||
height: 1px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.tl-youtube-link,
|
||||
@@ -900,25 +907,28 @@ html[data-theme='dark'] {
|
||||
.tl-popover-content {
|
||||
@apply rounded-sm drop-shadow-md;
|
||||
|
||||
padding: 4px;
|
||||
width: 160px;
|
||||
background-color: var(--ls-secondary-background-color);
|
||||
z-index: 100000;
|
||||
}
|
||||
|
||||
.tl-geometry-toolbar {
|
||||
box-shadow: none;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.tl-popover-arrow {
|
||||
fill: var(--ls-secondary-background-color);
|
||||
}
|
||||
|
||||
.tl-color-palette {
|
||||
@apply flex flex-wrap;
|
||||
@apply grid grid-cols-4;
|
||||
}
|
||||
|
||||
.tl-color-drip {
|
||||
@apply rounded text-sm;
|
||||
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 3px;
|
||||
border: 1px solid var(--ls-secondary-border-color);
|
||||
color: var(--ls-secondary-text-color);
|
||||
|
||||
@@ -3,18 +3,26 @@
|
||||
"private": true,
|
||||
"version": "0.0.0-dev",
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-decorators": "^7.18.2",
|
||||
"@babel/plugin-proposal-decorators": "^7.20.2",
|
||||
"@swc/core": "^1.3.17",
|
||||
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
||||
"@typescript-eslint/parser": "^5.43.0",
|
||||
"@vitejs/plugin-basic-ssl": "^0.1.2",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"postcss": "^8.4.17",
|
||||
"tailwindcss": "^3.0.24",
|
||||
"vite": "^3.1.6"
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.27.0",
|
||||
"postcss": "^8.4.19",
|
||||
"prettier-plugin-jsdoc": "^0.4.2",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"unplugin-swc": "^1.3.2",
|
||||
"vite": "^3.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "^2.1.0",
|
||||
"@vitejs/plugin-react": "^2.2.0",
|
||||
"react": "^17",
|
||||
"react-dom": "^17"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import react from '@vitejs/plugin-react'
|
||||
import basicSsl from '@vitejs/plugin-basic-ssl'
|
||||
// import swc from 'unplugin-swc'
|
||||
// import basicSsl from '@vitejs/plugin-basic-ssl'
|
||||
import path from 'path'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
@@ -12,6 +13,23 @@ const bases = {
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
// swc.vite({
|
||||
// jsc: {
|
||||
// target: 'es2022',
|
||||
// parser: {
|
||||
// decorators: true,
|
||||
// tsx: true,
|
||||
// jsx: true,
|
||||
// },
|
||||
// transform: {
|
||||
// legacyDecorator: true,
|
||||
// react: {
|
||||
// refresh: true,
|
||||
// runtime: 'automatic',
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }),
|
||||
react({
|
||||
babel: {
|
||||
parserOpts: {
|
||||
|
||||
@@ -37,22 +37,22 @@
|
||||
"dependencies": {
|
||||
"@tldraw/intersect": "2.0.0-alpha.1",
|
||||
"@tldraw/vec": "2.0.0-alpha.1",
|
||||
"@use-gesture/react": "^10.2.20",
|
||||
"fast-copy": "^2.1.3",
|
||||
"@use-gesture/react": "^10.2.22",
|
||||
"fast-copy": "^3.0.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"hotkeys-js": "^3.10.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"mobx": "^6.6.2",
|
||||
"mobx": "^6.7.0",
|
||||
"mobx-react-lite": "^3.4.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"potpack": "^1.0.2",
|
||||
"potpack": "^2.0.0",
|
||||
"proxy-compare": "^2.3.0",
|
||||
"rbush": "^3.0.1",
|
||||
"uuid": "^8.0.0"
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/is-plain-object": "^2.0.4",
|
||||
"@types/mousetrap": "^1.6.8",
|
||||
"@types/mousetrap": "^1.6.11",
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/rbush": "^3.0.0",
|
||||
"@types/react": "^17.0.0",
|
||||
|
||||
@@ -166,6 +166,19 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
|
||||
return this
|
||||
}
|
||||
|
||||
setColor = (color: string): this => {
|
||||
const { settings } = this.app
|
||||
|
||||
settings.update({ color: color })
|
||||
|
||||
this.app.selectedShapesArray.forEach(s => {
|
||||
s.update({ fill: color, stroke: color })
|
||||
})
|
||||
this.app.persist()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
save = () => {
|
||||
this.app.save()
|
||||
return this
|
||||
|
||||
@@ -5,6 +5,7 @@ import { isSafari } from '../utils'
|
||||
export interface TLSettingsProps {
|
||||
mode: 'light' | 'dark'
|
||||
showGrid: boolean
|
||||
color: string
|
||||
}
|
||||
|
||||
export class TLSettings implements TLSettingsProps {
|
||||
@@ -14,6 +15,7 @@ export class TLSettings implements TLSettingsProps {
|
||||
|
||||
@observable mode: 'dark' | 'light' = 'light'
|
||||
@observable showGrid = true
|
||||
@observable color = ''
|
||||
|
||||
@action update(props: Partial<TLSettingsProps>): void {
|
||||
Object.assign(this, props)
|
||||
|
||||
@@ -28,6 +28,8 @@ export interface TLShapeProps {
|
||||
type: any
|
||||
parentId: string
|
||||
name?: string
|
||||
fill?: string
|
||||
stroke?: string
|
||||
point: number[]
|
||||
size?: number[]
|
||||
scale?: number[]
|
||||
|
||||
@@ -32,6 +32,8 @@ export class CreatingState<
|
||||
type: Shape.id,
|
||||
parentId: currentPage.id,
|
||||
point: [...originPoint],
|
||||
fill: this.app.settings.color,
|
||||
stroke: this.app.settings.color,
|
||||
size: Vec.abs(Vec.sub(currentPoint, originPoint)),
|
||||
})
|
||||
this.initialBounds = {
|
||||
|
||||
@@ -71,6 +71,8 @@ export class CreatingState<
|
||||
point: originPoint.slice(0, 2),
|
||||
points: this.points,
|
||||
isComplete: false,
|
||||
fill: this.app.settings.color,
|
||||
stroke: this.app.settings.color,
|
||||
})
|
||||
this.app.currentPage.addShapes(this.shape)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ export class CreatingState<
|
||||
type: Shape.id,
|
||||
parentId: this.app.currentPage.id,
|
||||
point: originPoint,
|
||||
fill: this.app.settings.color,
|
||||
stroke: this.app.settings.color,
|
||||
})
|
||||
this.initialShape = toJS(shape.props)
|
||||
this.currentShape = shape
|
||||
|
||||
@@ -10,7 +10,7 @@ export class TLMoveTool<
|
||||
R extends TLApp<S, K> = TLApp<S, K>
|
||||
> extends TLTool<S, K, R> {
|
||||
static id = 'move'
|
||||
static shortcut = ['2']
|
||||
static shortcut = ['9']
|
||||
|
||||
static states = [IdleState, IdleHoldState, PanningState, PinchingState]
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export class TLSelectTool<
|
||||
|
||||
static initial = 'idle'
|
||||
|
||||
static shortcut = ['1']
|
||||
static shortcut = ['8']
|
||||
|
||||
static states = [
|
||||
IdleState,
|
||||
|
||||
@@ -36,6 +36,8 @@ export class CreatingState<
|
||||
text: '',
|
||||
size: [16, 32],
|
||||
isSizeLocked: true,
|
||||
fill: this.app.settings.color,
|
||||
stroke: this.app.settings.color,
|
||||
})
|
||||
this.creatingShape = shape
|
||||
transaction(() => {
|
||||
|
||||
@@ -35,23 +35,23 @@
|
||||
"@tldraw/core": "2.0.0-alpha.1",
|
||||
"@tldraw/intersect": "2.0.0-alpha.1",
|
||||
"@tldraw/vec": "2.0.0-alpha.1",
|
||||
"@use-gesture/react": "^10.2.20",
|
||||
"@use-gesture/react": "^10.2.22",
|
||||
"hotkeys-js": "^3.10.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"mobx": "^6.6.2",
|
||||
"mobx": "^6.7.0",
|
||||
"mobx-react-lite": "^3.4.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"polished": "^4.2.2",
|
||||
"rbush": "^3.0.1",
|
||||
"uuid": "^8.0.0"
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8",
|
||||
"react": ">=16.8 || ^17.0",
|
||||
"react-dom": "^16.8 || ^17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/is-plain-object": "^2.0.4",
|
||||
"@types/mousetrap": "^1.6.8",
|
||||
"@types/mousetrap": "^1.6.11",
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/offscreencanvas": "^2019.6.4",
|
||||
"@types/rbush": "^3.0.0",
|
||||
|
||||
1194
tldraw/yarn.lock
1194
tldraw/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -1617,9 +1617,9 @@ caniuse-api@^3.0.0:
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001370:
|
||||
version "1.0.30001393"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001393.tgz#1aa161e24fe6af2e2ccda000fc2b94be0b0db356"
|
||||
integrity sha512-N/od11RX+Gsk+1qY/jbPa0R6zJupEa0lxeBG598EbrtblxVCTJsQwbRBm6+V+rxpc5lHKdsXb9RY83cZIPLseA==
|
||||
version "1.0.30001431"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz"
|
||||
integrity sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==
|
||||
|
||||
caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001426:
|
||||
version "1.0.30001431"
|
||||
|
||||
Reference in New Issue
Block a user