diff --git a/index.html b/index.html
index e6cfab4b..f1ada494 100644
--- a/index.html
+++ b/index.html
@@ -430,6 +430,7 @@
+
Features › Auto save:
@@ -443,6 +444,21 @@
+
+
+
+ Features:
+ URL Sync
+
+
+
+ Keeps the URL in sync with your current code for easy sharing. Disable to
+ prevent flooding your browser history.
+
+
diff --git a/src/aside.js b/src/aside.js
index 21e488d3..3b73007b 100644
--- a/src/aside.js
+++ b/src/aside.js
@@ -2,7 +2,6 @@ import { eventBus, EVENTS } from './events-controller.js'
import { $, $$ } from './utils/dom.js'
import * as Preview from './utils/WindowPreviewer'
import { BUTTON_ACTIONS } from './constants/button-actions.js'
-import { copyToClipboard } from './utils/string.js'
import { resetConsoleBadge } from './console.js'
const $aside = $('aside')
@@ -23,10 +22,8 @@ const SIMPLE_CLICK_ACTIONS = {
Preview.showPreviewerWindow()
},
- [BUTTON_ACTIONS.copyToClipboard]: async () => {
- const url = new URL(window.location.href)
- const urlToCopy = `https://codi.link${url.pathname}`
- copyToClipboard(urlToCopy)
+ [BUTTON_ACTIONS.copyToClipboard]: () => {
+ eventBus.emit(EVENTS.COPY_CURRENT_CODE_URL)
},
[BUTTON_ACTIONS.clearHistory]: () => {
diff --git a/src/components/codi-editor/extensions/editor-hotkeys.js b/src/components/codi-editor/extensions/editor-hotkeys.js
index 4a859c29..83b482f5 100644
--- a/src/components/codi-editor/extensions/editor-hotkeys.js
+++ b/src/components/codi-editor/extensions/editor-hotkeys.js
@@ -1,6 +1,6 @@
import * as monaco from 'monaco-editor'
import { $ } from '../../../utils/dom.js'
-import { copyToClipboard } from '../../../utils/string'
+import { eventBus, EVENTS } from '../../../events-controller.js'
export const initEditorHotKeys = (editor) => {
// Shortcut: Open/Close Settings
@@ -22,9 +22,7 @@ export const initEditorHotKeys = (editor) => {
editor.addCommand(
monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyC,
() => {
- const url = new URL(window.location.href)
- const urlToCopy = `https://codi.link${url.pathname}`
- copyToClipboard(urlToCopy)
+ eventBus.emit(EVENTS.COPY_CURRENT_CODE_URL)
}
)
}
diff --git a/src/constants/initial-settings.js b/src/constants/initial-settings.js
index c8a77306..c981c98a 100644
--- a/src/constants/initial-settings.js
+++ b/src/constants/initial-settings.js
@@ -17,6 +17,7 @@ export const DEFAULT_INITIAL_SETTINGS = {
zipFileName: 'codi.link',
zipInSingleFile: false,
saveLocalstorage: true,
+ urlSync: true,
layout: {
gutters: DEFAULT_LAYOUT,
style: DEFAULT_GRID_TEMPLATE,
diff --git a/src/events-controller.js b/src/events-controller.js
index 30bd0260..ca1bc872 100644
--- a/src/events-controller.js
+++ b/src/events-controller.js
@@ -1,5 +1,6 @@
import { decode } from 'js-base64'
-import { capitalize, searchByLine } from './utils/string.js'
+import { capitalize, copyToClipboard, searchByLine } from './utils/string.js'
+import { getEncodedPath } from './utils/url.js'
import { downloadUserCode } from './download.js'
import { getState } from './state.js'
import { getHistoryState } from './history.js'
@@ -42,6 +43,7 @@ export const EVENTS = {
DRAG_FILE: 'DRAG_FILE',
OPEN_EXISTING_INSTANCE: 'OPEN_EXISTING_INSTANCE',
OPEN_NEW_INSTANCE: 'OPEN_NEW_INSTANCE',
+ COPY_CURRENT_CODE_URL: 'COPY-CURRENT-CODE-URL',
CLEAR_HISTORY: 'CLEAR_HISTORY'
}
@@ -122,3 +124,15 @@ eventBus.on(EVENTS.CLEAR_HISTORY, () => {
const { clearHistory } = getHistoryState()
clearHistory()
})
+
+eventBus.on(EVENTS.COPY_CURRENT_CODE_URL, async () => {
+ const html = htmlEditor.getValue()
+ const css = cssEditor.getValue()
+ const js = jsEditor.getValue()
+
+ const encodedPath = getEncodedPath({ html, css, js })
+
+ const urlToCopy = `${window.location.origin}${encodedPath}`
+
+ await copyToClipboard(urlToCopy)
+})
diff --git a/src/language/en.js b/src/language/en.js
index 5c848144..1a5a1721 100644
--- a/src/language/en.js
+++ b/src/language/en.js
@@ -61,6 +61,10 @@ const en = {
localStorage: 'Local storage',
automaticallySaveUrl:
'Automatically save URL to local storage for fast content loading',
+ features: 'Features',
+ urlSync: 'URL Sync',
+ urlSyncCheckbox: 'Automatically update URL',
+ urlSyncDescription: 'Keeps the URL in sync with your current code for easy sharing. Disable to prevent flooding your browser history.',
searchDependency: 'Search and add a package...'
}
diff --git a/src/language/es.js b/src/language/es.js
index 4cf023d6..cc185f43 100644
--- a/src/language/es.js
+++ b/src/language/es.js
@@ -61,6 +61,11 @@ const es = {
localStorage: 'Almacenamiento local',
automaticallySaveUrl:
'Guardar automáticamente la URL en el almacenamiento local para una carga rápida del contenido',
+ features: 'Características',
+ urlSync: 'URL Sync',
+ urlSyncCheckbox: 'Actualizar URL automáticamente',
+ urlSyncDescription:
+ 'Actualiza automáticamente la URL con tu código actual. Desactívalo para evitar saturar el historial de navegación.',
searchDependency: 'Buscar y agregar un paquete...'
}
diff --git a/src/language/pt.js b/src/language/pt.js
index 14ff2473..56ed5815 100644
--- a/src/language/pt.js
+++ b/src/language/pt.js
@@ -61,6 +61,11 @@ const pt = {
localStorage: 'Armazenamento local',
automaticallySaveUrl:
'Salvar automaticamente a URL no armazenamento local para carregamento rápido de conteúdo',
+ features: 'Recursos',
+ urlSync: 'URL Sync',
+ urlSyncCheckbox: 'Atualizar automaticamente a URL',
+ urlSyncDescription:
+ 'Atualiza automaticamente a URL com o código atual. Desative para evitar sobrecarregar o histórico do navegador.',
searchDependency: 'Pesquisar e adicionar um pacote...'
}
diff --git a/src/main.js b/src/main.js
index e94fe142..869de5d7 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,4 +1,4 @@
-import { encode, decode } from 'js-base64'
+import { decode } from 'js-base64'
import { $, $$ } from './utils/dom.js'
import { createEditor } from './editor.js'
import debounce from './utils/debounce.js'
@@ -11,6 +11,8 @@ import setTheme from './theme.js'
import setLanguage from './language.js'
import { configurePrettierHotkeys } from './monaco-prettier/configurePrettier'
import { getHistoryState, subscribeHistory, setHistory } from './history.js'
+import { getEncodedString } from './utils/url.js'
+import { setUrlSync, handleUrlSyncOnType } from './url-sync.js'
import './aside.js'
import './skypack.js'
@@ -70,15 +72,11 @@ subscribe(state => {
setGridLayout(state.layout)
setTheme(state.theme)
setLanguage(state.language)
+ setUrlSync(state.urlSync, EDITORS)
})
const MS_UPDATE_DEBOUNCED_TIME = 200
-const MS_UPDATE_HASH_DEBOUNCED_TIME = 1000
const debouncedUpdate = debounce(update, MS_UPDATE_DEBOUNCED_TIME)
-const debouncedUpdateHash = debounce(
- updateHashedCode,
- MS_UPDATE_HASH_DEBOUNCED_TIME
-)
const { html: htmlEditor, css: cssEditor, javascript: jsEditor } = EDITORS
@@ -116,8 +114,9 @@ function update ({ notReload } = {}) {
Preview.updatePreview(values)
+ const { maxExecutionTime, urlSync } = getState()
+
if (!notReload) {
- const { maxExecutionTime } = getState()
runJs(values.js, parseInt(maxExecutionTime))
.then(() => {
iframe.setAttribute('src', Preview.getPreviewUrl())
@@ -129,10 +128,17 @@ function update ({ notReload } = {}) {
updateCss()
- debouncedUpdateHash(values)
if (saveLocalstorage) {
updateHistory(values)
}
+
+ if (urlSync) {
+ handleUrlSyncOnType(values)
+ }
+
+ // fixes url bugs when using history
+ setUrlSync(urlSync, EDITORS)
+
updateButtonAvailabilityIfContent(values)
}
@@ -144,14 +150,9 @@ function updateCss () {
}
}
-function updateHashedCode ({ html, css, js }) {
- const hashedCode = `${encode(html)}|${encode(css)}|${encode(js)}`
- window.history.replaceState(null, null, `/${hashedCode}`)
-}
-
function updateHistory ({ html, css, js }) {
const { history } = getHistoryState()
- const hashedCode = `${encode(html)}|${encode(css)}|${encode(js)}`
+ const hashedCode = getEncodedString({ html, css, js })
const isEmpty = !html.replace(/\n/g, '').trim() && !css.replace(/\n/g, '').trim() && !js.replace(/\n/g, '').trim()
if (isEmpty && !history.current) {
diff --git a/src/url-sync.js b/src/url-sync.js
new file mode 100644
index 00000000..24bbf7fe
--- /dev/null
+++ b/src/url-sync.js
@@ -0,0 +1,39 @@
+import { getCleanPath, getEncodedPath } from './utils/url.js'
+import debounce from './utils/debounce.js'
+
+const MS_UPDATE_HASH_DEBOUNCED_TIME = 1000
+
+function getCurrentCodeFromEditors (EDITORS) {
+ return {
+ html: EDITORS.html?.getValue() || '',
+ css: EDITORS.css?.getValue() || '',
+ js: EDITORS.javascript?.getValue() || ''
+ }
+}
+
+function updateHashedPath ({ html, css, js }) {
+ window.history.replaceState(null, null, getEncodedPath({ html, css, js }))
+}
+
+const debouncedUpdateHashedPath = debounce(updateHashedPath, MS_UPDATE_HASH_DEBOUNCED_TIME)
+
+export function handleUrlSyncOnType (codeValues) {
+ debouncedUpdateHashedPath(codeValues)
+}
+
+export function setUrlSync (isUrlSyncEnabled, EDITORS) {
+ const currentPathIsEmpty = window.location.pathname === '/'
+
+ const shouldEncodeUrl = isUrlSyncEnabled && currentPathIsEmpty
+ const shouldCleanUrl = !isUrlSyncEnabled && !currentPathIsEmpty
+
+ if (shouldEncodeUrl) {
+ const { html, css, js } = getCurrentCodeFromEditors(EDITORS)
+ window.history.replaceState(null, null, getEncodedPath({ html, css, js }))
+ return
+ }
+
+ if (shouldCleanUrl) {
+ window.history.replaceState(null, null, getCleanPath())
+ }
+}
diff --git a/src/utils/notification.js b/src/utils/notification.js
index 713a124f..67cfb9c4 100644
--- a/src/utils/notification.js
+++ b/src/utils/notification.js
@@ -7,7 +7,7 @@ const STATE_ICONS = {
}
const TRANSITION_DURATION = 400 // ms
-const NOTIFICATION_DURATION = 300000 // ms
+const NOTIFICATION_DURATION = 3000 // ms
export default {
/**
@@ -48,17 +48,23 @@ export default {
notification.setAttribute('aria-live', 'assertive')
notification.setAttribute('aria-atomic', 'true')
- setTimeout(() => {
+ const timerIdOut = setTimeout(() => {
notification.classList.remove('animation-in')
notification.classList.add('animation-out')
}, NOTIFICATION_DURATION - TRANSITION_DURATION / 2)
// Remove notification after NOTIFICATION_DURATION
- setTimeout(() => {
+ const timerIdRemove = setTimeout(() => {
notification.remove()
}, NOTIFICATION_DURATION)
+ const clearTimers = () => {
+ clearTimeout(timerIdOut)
+ clearTimeout(timerIdRemove)
+ }
+
notification.querySelector('.icon-close').addEventListener('click', () => {
+ clearTimers()
notification.classList.add('bounce-leave')
setTimeout(() => {
notification.remove()
diff --git a/src/utils/url.js b/src/utils/url.js
new file mode 100644
index 00000000..0dd0a645
--- /dev/null
+++ b/src/utils/url.js
@@ -0,0 +1,13 @@
+import { encode } from 'js-base64'
+
+export function getCleanPath () {
+ return '/'
+}
+
+export function getEncodedPath ({ html = '', css = '', js = '' }) {
+ return `/${encode(html)}%7C${encode(css)}%7C${encode(js)}`
+}
+
+export function getEncodedString ({ html = '', css = '', js = '' }) {
+ return `${encode(html)}|${encode(css)}|${encode(js)}`
+}