Merge branch 'main' of ssh://git.apoena.dev:22222/remanso-space/remanso
This commit is contained in:
8
.agents/.claude-plugin/plugin.json
Normal file
8
.agents/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "remanso-skills",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Local skills for the Remanso project",
|
||||||
|
"author": {
|
||||||
|
"name": "julien"
|
||||||
|
}
|
||||||
|
}
|
||||||
196
.agents/skills/migrate-oxlint/SKILL.md
Normal file
196
.agents/skills/migrate-oxlint/SKILL.md
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
---
|
||||||
|
name: migrate-oxlint
|
||||||
|
description: Guide for migrating a project from ESLint to Oxlint. Use when asked to migrate, convert, or switch a JavaScript/TypeScript project's linter from ESLint to Oxlint.
|
||||||
|
---
|
||||||
|
|
||||||
|
This skill guides you through migrating a JavaScript/TypeScript project from ESLint to [Oxlint](https://oxc.rs/docs/guide/usage/linter/).
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Oxlint is a high-performance linter that implements many popular ESLint rules natively in Rust. It can be used alongside ESLint or as a full replacement.
|
||||||
|
|
||||||
|
An official migration tool is available, and will be used by this skill: [`@oxlint/migrate`](https://github.com/oxc-project/oxlint-migrate)
|
||||||
|
|
||||||
|
## Step 1: Run Automated Migration
|
||||||
|
|
||||||
|
Run the migration tool in the project root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @oxlint/migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
This reads your ESLint flat config (`eslint.config.js` for example) and generates a `.oxlintrc.json` file from it. It will find your ESLint config file automatically in most cases.
|
||||||
|
|
||||||
|
See options below for more info.
|
||||||
|
|
||||||
|
### Key Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `--type-aware` | Include type-aware rules from `@typescript-eslint` (will require the `oxlint-tsgolint` package to be installed after migrating) |
|
||||||
|
| `--with-nursery` | Include experimental rules still under development, may not be fully stable or consistent with ESLint equivalents |
|
||||||
|
| `--js-plugins [bool]` | Enable/disable ESLint plugin migration via `jsPlugins` (default: enabled) |
|
||||||
|
| `--details` | List rules that could not be migrated |
|
||||||
|
| `--replace-eslint-comments` | Convert all `// eslint-disable` comments to `// oxlint-disable` |
|
||||||
|
| `--output-file <file>` | Specify a different output path (default: `.oxlintrc.json`) |
|
||||||
|
|
||||||
|
If your ESLint config is not at the default location, pass the path explicitly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @oxlint/migrate ./path/to/eslint.config.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2: Review Generated Config
|
||||||
|
|
||||||
|
After migration, review the generated `.oxlintrc.json`.
|
||||||
|
|
||||||
|
### Plugin Mapping
|
||||||
|
|
||||||
|
The migration tool automatically maps ESLint plugins to oxlint's built-in equivalents. The following table is for reference when reviewing the generated config:
|
||||||
|
|
||||||
|
| ESLint Plugin | Oxlint Plugin Name |
|
||||||
|
| --------------------------------------------------- | ------------------ |
|
||||||
|
| `@typescript-eslint/eslint-plugin` | `typescript` |
|
||||||
|
| `eslint-plugin-react` / `eslint-plugin-react-hooks` | `react` |
|
||||||
|
| `eslint-plugin-import` / `eslint-plugin-import-x` | `import` |
|
||||||
|
| `eslint-plugin-unicorn` | `unicorn` |
|
||||||
|
| `eslint-plugin-jsx-a11y` | `jsx-a11y` |
|
||||||
|
| `eslint-plugin-react-perf` | `react-perf` |
|
||||||
|
| `eslint-plugin-promise` | `promise` |
|
||||||
|
| `eslint-plugin-jest` | `jest` |
|
||||||
|
| `@vitest/eslint-plugin` | `vitest` |
|
||||||
|
| `eslint-plugin-jsdoc` | `jsdoc` |
|
||||||
|
| `eslint-plugin-next` | `nextjs` |
|
||||||
|
| `eslint-plugin-node` | `node` |
|
||||||
|
| `eslint-plugin-vue` | `vue` |
|
||||||
|
|
||||||
|
Default plugins (enabled when `plugins` field is omitted): `unicorn`, `typescript`, `oxc`.
|
||||||
|
Setting the `plugins` array explicitly overrides these defaults.
|
||||||
|
|
||||||
|
ESLint core rules are usable in oxlint without needing to configure a plugin in the config file.
|
||||||
|
|
||||||
|
### Rule Categories
|
||||||
|
|
||||||
|
Oxlint groups rules into categories for bulk configuration, though only `correctness` is enabled by default:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"categories": {
|
||||||
|
"correctness": "error",
|
||||||
|
"suspicious": "warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Available categories: `correctness` (default: enabled), `suspicious`, `pedantic`, `perf`, `style`, `restriction`, `nursery`.
|
||||||
|
|
||||||
|
Individual rule settings in `rules` override category settings.
|
||||||
|
|
||||||
|
`@oxlint/migrate` will turn `correctness` off to avoid enabling additional rules that weren't enabled by your ESLint config. You can choose to enable additional categories after migration if desired.
|
||||||
|
|
||||||
|
### Check Unmigrated Rules
|
||||||
|
|
||||||
|
Run with `--details` to see which ESLint rules could not be migrated:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @oxlint/migrate --details
|
||||||
|
```
|
||||||
|
|
||||||
|
Review the output and decide whether to keep ESLint for those rules or not. Some rules may be mentioned in the output from `--details` as having equivalents in oxlint that were not automatically mapped by the migration tool. In those cases, consider enabling the equivalent oxlint rule manually after migration.
|
||||||
|
|
||||||
|
## Step 3: Install Oxlint
|
||||||
|
|
||||||
|
Install the core oxlint package (use `yarn install`, `pnpm install`, `vp install`, `bun install`, etc. depending on your package manager):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -D oxlint
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to add the `oxlint-tsgolint` package, if you intend to use type-aware rules that require TypeScript type information:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -D oxlint-tsgolint
|
||||||
|
```
|
||||||
|
|
||||||
|
No other packages besides the above are needed by default, though you will need to keep/install any additional ESLint plugins that were migrated into `jsPlugins`. Do not add `@oxlint/migrate` to the package.json, it is meant for one-off usage.
|
||||||
|
|
||||||
|
## Step 4: Handle Unsupported Features
|
||||||
|
|
||||||
|
Some features require manual attention:
|
||||||
|
|
||||||
|
- Local plugins (relative path imports): Must be migrated manually to `jsPlugins`
|
||||||
|
- `eslint-plugin-prettier`: Supported, but very slow. It is recommended to use [oxfmt](https://oxc.rs/docs/guide/usage/formatter) instead, or switch to `prettier --check` as a separate step alongside oxlint.
|
||||||
|
- `settings` in override configs: Oxlint does not support `settings` inside `overrides` blocks.
|
||||||
|
- ESLint v9+ plugins: Not all work with oxlint's JS Plugins API, but the majority will.
|
||||||
|
|
||||||
|
### Local Plugins
|
||||||
|
|
||||||
|
If you have any custom ESLint rules in the project repo itself, you can migrate them manually after running the migration tool by adding them to the `jsPlugins` field in `.oxlintrc.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsPlugins": ["./path/to/my-plugin.js"],
|
||||||
|
"rules": {
|
||||||
|
"local-plugin/rule-name": "error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### External ESLint Plugins
|
||||||
|
|
||||||
|
For ESLint plugins without a built-in oxlint equivalent, use the `jsPlugins` field to load them:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsPlugins": ["eslint-plugin-custom"],
|
||||||
|
"rules": {
|
||||||
|
"custom/my-rule": "warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 5: Update CI and Scripts
|
||||||
|
|
||||||
|
Replace ESLint commands with oxlint. Path arguments are optional; oxlint defaults to the current working directory.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Before
|
||||||
|
npx eslint src/
|
||||||
|
npx eslint --fix src/
|
||||||
|
|
||||||
|
# After
|
||||||
|
npx oxlint src/
|
||||||
|
npx oxlint --fix src/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common CLI Options
|
||||||
|
|
||||||
|
| ESLint | oxlint equivalent |
|
||||||
|
| ------------------------- | ---------------------------------------------- |
|
||||||
|
| `eslint .` | `oxlint` (default: lints the cwd) |
|
||||||
|
| `eslint src/` | `oxlint src/` |
|
||||||
|
| `eslint --fix` | `oxlint --fix` |
|
||||||
|
| `eslint --max-warnings 0` | `oxlint --deny-warnings` or `--max-warnings 0` |
|
||||||
|
| `eslint --format json` | `oxlint --format json` |
|
||||||
|
|
||||||
|
Additional oxlint options:
|
||||||
|
|
||||||
|
- `--tsconfig <path>`: Specify tsconfig.json path, likely unnecessary unless you have a non-standard name for `tsconfig.json`.
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
- You can run alongside ESLint if necessary: Oxlint is designed to complement ESLint during migration, but with JS Plugins many projects can switch over fully without losing many rules.
|
||||||
|
- Disable comments work: `// eslint-disable` and `// eslint-disable-next-line` comments are supported by oxlint. Use `--replace-eslint-comments` when running @oxlint/migrate to convert them to `// oxlint-disable` equivalents if desired.
|
||||||
|
- List available rules: Run `npx oxlint --rules` to see all supported rules, or refer to the [rule documentation](https://oxc.rs/docs/guide/usage/linter/rules.html).
|
||||||
|
- Schema support: Add `"$schema": "./node_modules/oxlint/configuration_schema.json"` to `.oxlintrc.json` for editor autocompletion if the migration tool didn't do it automatically.
|
||||||
|
- Output formats: `default`, `stylish`, `json`, `github`, `gitlab`, `junit`, `checkstyle`, `unix`
|
||||||
|
- Ignore files: `.eslintignore` is supported by oxlint if you have it, but it's recommended to move any ignore patterns into the `ignorePatterns` field in `.oxlintrc.json` for consistency and simplicity. All files and paths ignored via a `.gitignore` file will be ignored by oxlint by default as well.
|
||||||
|
- If you ran the migration tool multiple times, remove the `.oxlintrc.json.bak` backup file created by the migration tool once you've finished migrating.
|
||||||
|
- If you are not using any JS Plugins and have replaced your ESLint configuration, you can remove all ESLint packages from your project dependencies.
|
||||||
|
- Ensure your editor is configured to use oxlint instead of ESLint for linting and error reporting. You may want to install the Oxc extension for your preferred editor. See https://oxc.rs/docs/guide/usage/linter/editors.html for more details.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [CLI Reference](https://oxc.rs/docs/guide/usage/linter/cli.html)
|
||||||
|
- [Config File Reference](https://oxc.rs/docs/guide/usage/linter/config-file-reference.html)
|
||||||
|
- [Complete Oxlint rule list and docs](https://oxc.rs/docs/guide/usage/linter/rules.html)
|
||||||
14
.claude-plugin/marketplace.json
Normal file
14
.claude-plugin/marketplace.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "remanso-local",
|
||||||
|
"description": "Local plugins for the Remanso project",
|
||||||
|
"owner": {
|
||||||
|
"name": "julien"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "remanso-skills",
|
||||||
|
"description": "Local skills for the Remanso project (migrate-oxlint, etc.)",
|
||||||
|
"source": "./.agents"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
.claude/settings.json
Normal file
13
.claude/settings.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extraKnownMarketplaces": {
|
||||||
|
"remanso-local": {
|
||||||
|
"source": {
|
||||||
|
"source": "directory",
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enabledPlugins": {
|
||||||
|
"remanso-skills@remanso-local": true
|
||||||
|
}
|
||||||
|
}
|
||||||
53
.eslintrc.js
53
.eslintrc.js
@@ -1,53 +0,0 @@
|
|||||||
require("@rushstack/eslint-patch/modern-module-resolution")
|
|
||||||
|
|
||||||
const DEV_TOOL_ACTIVATED =
|
|
||||||
process.env.NODE_ENV === "production" ? "warn" : "off"
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
es2022: true,
|
|
||||||
},
|
|
||||||
extends: ["plugin:vue/vue3-essential", "@vue/eslint-config-typescript"],
|
|
||||||
plugins: ["simple-import-sort", "unused-imports"],
|
|
||||||
rules: {
|
|
||||||
"no-console": DEV_TOOL_ACTIVATED,
|
|
||||||
"no-debugger": DEV_TOOL_ACTIVATED,
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
||||||
"@typescript-eslint/camelcase": "off",
|
|
||||||
"prettier-vue/prettier": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
semi: false,
|
|
||||||
singleQuote: true,
|
|
||||||
trailingComma: "none",
|
|
||||||
arrowParens: "always",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"vue/no-v-html": "off",
|
|
||||||
"no-restricted-imports": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
paths: [
|
|
||||||
{
|
|
||||||
name: "vue-demi",
|
|
||||||
importNames: ["computed"],
|
|
||||||
message: "Please use computed from vue instead.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"simple-import-sort/imports": "error",
|
|
||||||
"simple-import-sort/exports": "error",
|
|
||||||
"unused-imports/no-unused-imports": "error",
|
|
||||||
},
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: [
|
|
||||||
"**/__tests__/*.{j,t}s?(x)",
|
|
||||||
"**/tests/unit/**/*.spec.{j,t}s?(x)",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,7 +2,6 @@
|
|||||||
node_modules
|
node_modules
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env.local
|
.env.local
|
||||||
.env.*.local
|
.env.*.local
|
||||||
|
|||||||
9
.oxfmtrc.json
Normal file
9
.oxfmtrc.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 80,
|
||||||
|
"sortPackageJson": false,
|
||||||
|
"ignorePatterns": []
|
||||||
|
}
|
||||||
40
.oxlintrc.json
Normal file
40
.oxlintrc.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
|
"plugins": ["typescript"],
|
||||||
|
"jsPlugins": [
|
||||||
|
"eslint-plugin-prettier-vue",
|
||||||
|
"eslint-plugin-simple-import-sort",
|
||||||
|
"eslint-plugin-unused-imports"
|
||||||
|
],
|
||||||
|
"categories": {
|
||||||
|
"correctness": "off"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"builtin": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-restricted-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"name": "vue-demi",
|
||||||
|
"importNames": ["computed"],
|
||||||
|
"message": "Please use computed from vue instead."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"simple-import-sort/imports": "error",
|
||||||
|
"simple-import-sort/exports": "error",
|
||||||
|
"unused-imports/no-unused-imports": "error"
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["**/*.vue"],
|
||||||
|
"rules": {
|
||||||
|
"unused-imports/no-unused-imports": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"semi": false
|
|
||||||
}
|
|
||||||
@@ -28,8 +28,6 @@ RUN pnpm run build
|
|||||||
FROM nginx:alpine AS runner
|
FROM nginx:alpine AS runner
|
||||||
|
|
||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=3s \
|
|
||||||
CMD wget -qO- http://localhost:80/ || exit 1
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { readFileSync, writeFileSync } from "fs"
|
import { readFileSync, writeFileSync } from "fs"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
|
|
||||||
import { commitTheme } from "./change-theme"
|
import { commitTheme } from "./change-theme"
|
||||||
|
|
||||||
// Chemins vers les fichiers
|
// Chemins vers les fichiers
|
||||||
@@ -28,7 +29,7 @@ let themeConfigContent = readFileSync(themeConfigPath, "utf8")
|
|||||||
// Remplacer la valeur du thème sombre
|
// Remplacer la valeur du thème sombre
|
||||||
themeConfigContent = themeConfigContent.replace(
|
themeConfigContent = themeConfigContent.replace(
|
||||||
/dark:\s*['"][^'"]*['"],/,
|
/dark:\s*['"][^'"]*['"],/,
|
||||||
`dark: '${newTheme}',`,
|
`dark: '${newTheme}',`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Écrire le contenu mis à jour dans le fichier
|
// Écrire le contenu mis à jour dans le fichier
|
||||||
@@ -38,7 +39,7 @@ writeFileSync(themeConfigPath, themeConfigContent)
|
|||||||
let appCssContent = readFileSync(appCssPath, "utf8")
|
let appCssContent = readFileSync(appCssPath, "utf8")
|
||||||
appCssContent = appCssContent.replace(
|
appCssContent = appCssContent.replace(
|
||||||
/(\s+)([a-zA-Z0-9-]+)(\s+--prefersdark;)/,
|
/(\s+)([a-zA-Z0-9-]+)(\s+--prefersdark;)/,
|
||||||
`$1${newTheme}$3`,
|
`$1${newTheme}$3`
|
||||||
)
|
)
|
||||||
writeFileSync(appCssPath, appCssContent)
|
writeFileSync(appCssPath, appCssContent)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { readFileSync, writeFileSync } from "fs"
|
import { readFileSync, writeFileSync } from "fs"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
|
|
||||||
import { commitTheme } from "./change-theme"
|
import { commitTheme } from "./change-theme"
|
||||||
|
|
||||||
// Chemins vers les fichiers
|
// Chemins vers les fichiers
|
||||||
@@ -29,7 +30,7 @@ let themeConfigContent = readFileSync(themeConfigPath, "utf8")
|
|||||||
// Remplacer la valeur du thème clair
|
// Remplacer la valeur du thème clair
|
||||||
themeConfigContent = themeConfigContent.replace(
|
themeConfigContent = themeConfigContent.replace(
|
||||||
/light:\s*['"][^'"]*['"],/,
|
/light:\s*['"][^'"]*['"],/,
|
||||||
`light: '${newTheme}',`,
|
`light: '${newTheme}',`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Écrire le contenu mis à jour dans le fichier
|
// Écrire le contenu mis à jour dans le fichier
|
||||||
@@ -39,7 +40,7 @@ writeFileSync(themeConfigPath, themeConfigContent)
|
|||||||
let indexContent = readFileSync(indexPath, "utf8")
|
let indexContent = readFileSync(indexPath, "utf8")
|
||||||
indexContent = indexContent.replace(
|
indexContent = indexContent.replace(
|
||||||
/data-theme="[^"]*"/,
|
/data-theme="[^"]*"/,
|
||||||
`data-theme="${newTheme}"`,
|
`data-theme="${newTheme}"`
|
||||||
)
|
)
|
||||||
writeFileSync(indexPath, indexContent)
|
writeFileSync(indexPath, indexContent)
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ writeFileSync(indexPath, indexContent)
|
|||||||
let appCssContent = readFileSync(appCssPath, "utf8")
|
let appCssContent = readFileSync(appCssPath, "utf8")
|
||||||
appCssContent = appCssContent.replace(
|
appCssContent = appCssContent.replace(
|
||||||
/(\s+)([a-zA-Z0-9-]+)(\s+--default,)/,
|
/(\s+)([a-zA-Z0-9-]+)(\s+--default,)/,
|
||||||
`$1${newTheme}$3`,
|
`$1${newTheme}$3`
|
||||||
)
|
)
|
||||||
writeFileSync(appCssPath, appCssContent)
|
writeFileSync(appCssPath, appCssContent)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
presets: ['@vue/cli-plugin-babel/preset']
|
presets: ["@vue/cli-plugin-babel/preset"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ Rolldown's minifier drops the `while(`/`for(;` keyword when a `while (x in globa
|
|||||||
|
|
||||||
## Target repositories
|
## Target repositories
|
||||||
|
|
||||||
- https://github.com/rolldown/rolldown/issues
|
- <https://github.com/rolldown/rolldown/issues>
|
||||||
- https://github.com/oxc-project/oxc/issues (underlying minifier)
|
- <https://github.com/oxc-project/oxc/issues> (underlying minifier)
|
||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
| Package | Version |
|
| Package | Version |
|
||||||
|---|---|
|
| -------------------- | --------------------------------------- |
|
||||||
| `vite` | 8.0.1 |
|
| `vite` | 8.0.1 |
|
||||||
| `rolldown` | 1.0.0-rc.10 |
|
| `rolldown` | 1.0.0-rc.10 |
|
||||||
| `@oxc-project/types` | 0.120.0 |
|
| `@oxc-project/types` | 0.120.0 |
|
||||||
@@ -26,11 +26,10 @@ Rolldown's minifier drops the `while(`/`for(;` keyword when a `while (x in globa
|
|||||||
File: `node_modules/@ark/schema/out/shared/registry.js`
|
File: `node_modules/@ark/schema/out/shared/registry.js`
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let _registryName = "$ark";
|
let _registryName = "$ark"
|
||||||
let suffix = 2;
|
let suffix = 2
|
||||||
while (_registryName in globalThis)
|
while (_registryName in globalThis) _registryName = `$ark${suffix++}`
|
||||||
_registryName = `$ark${suffix++}`;
|
export const registryName = _registryName
|
||||||
export const registryName = _registryName;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Actual minified output
|
## Actual minified output
|
||||||
@@ -44,7 +43,10 @@ The `while(` keyword is missing. The orphaned `)` is a syntax error.
|
|||||||
## Expected output
|
## Expected output
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var dn=`$ark`,fn=2;for(;dn in globalThis;)dn=`$ark${fn++}`;var pn=dn;
|
var dn = `$ark`,
|
||||||
|
fn = 2
|
||||||
|
for (; dn in globalThis; ) dn = `$ark${fn++}`
|
||||||
|
var pn = dn
|
||||||
```
|
```
|
||||||
|
|
||||||
## Impact
|
## Impact
|
||||||
|
|||||||
9
nginx.conf
Normal file
9
nginx.conf
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
package.json
15
package.json
@@ -8,7 +8,10 @@
|
|||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"types": "tsc --noEmit",
|
"types": "tsc --noEmit",
|
||||||
"lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore --fix src",
|
"lint": "oxlint",
|
||||||
|
"lint:fix": "oxlint --fix",
|
||||||
|
"fmt": "oxfmt",
|
||||||
|
"fmt:check": "oxfmt --check",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"theme:light": "esno _scripts/change-theme-light.ts",
|
"theme:light": "esno _scripts/change-theme-light.ts",
|
||||||
"theme:dark": "esno _scripts/change-theme-dark.ts",
|
"theme:dark": "esno _scripts/change-theme-dark.ts",
|
||||||
@@ -60,31 +63,25 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.28.5",
|
"@babel/core": "^7.28.5",
|
||||||
"@rushstack/eslint-patch": "^1.14.1",
|
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@types/fontfaceobserver": "^2.1.3",
|
"@types/fontfaceobserver": "^2.1.3",
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
"@types/node": "^22.15.24",
|
"@types/node": "^22.15.24",
|
||||||
"@types/pouchdb-browser": "^6.1.5",
|
"@types/pouchdb-browser": "^6.1.5",
|
||||||
"@types/sanitize-html": "^2.16.0",
|
"@types/sanitize-html": "^2.16.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
|
||||||
"@typescript-eslint/parser": "^8.46.2",
|
|
||||||
"@vite-pwa/assets-generator": "^1.0.2",
|
"@vite-pwa/assets-generator": "^1.0.2",
|
||||||
"@vitejs/plugin-vue": "^5.2.4",
|
"@vitejs/plugin-vue": "^5.2.4",
|
||||||
"@vue/compiler-sfc": "^3.5.28",
|
"@vue/compiler-sfc": "^3.5.28",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
|
||||||
"@vue/eslint-config-typescript": "^14.6.0",
|
|
||||||
"autoprefixer": "^10.4.24",
|
"autoprefixer": "^10.4.24",
|
||||||
"daisyui": "^5.5.18",
|
"daisyui": "^5.5.18",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"eslint": "^8.57.1",
|
|
||||||
"eslint-config-prettier": "^9.1.0",
|
|
||||||
"eslint-plugin-prettier-vue": "^5.0.0",
|
"eslint-plugin-prettier-vue": "^5.0.0",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"eslint-plugin-unused-imports": "^4.4.1",
|
"eslint-plugin-unused-imports": "^4.4.1",
|
||||||
"eslint-plugin-vue": "^10.8.0",
|
|
||||||
"esno": "^4.8.0",
|
"esno": "^4.8.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
|
"oxfmt": "^0.42.0",
|
||||||
|
"oxlint": "^1.57.0",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"prettier-vue": "^1.1.2",
|
"prettier-vue": "^1.1.2",
|
||||||
"sass": "^1.98.0",
|
"sass": "^1.98.0",
|
||||||
|
|||||||
798
pnpm-lock.yaml
generated
798
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: { "@tailwindcss/postcss": {}, autoprefixer: {} },
|
plugins: { "@tailwindcss/postcss": {}, autoprefixer: {} }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
"client_id": "https://remanso.space/client-metadata.json",
|
"client_id": "https://remanso.space/client-metadata.json",
|
||||||
"client_name": "Remanso",
|
"client_name": "Remanso",
|
||||||
"client_uri": "https://remanso.space",
|
"client_uri": "https://remanso.space",
|
||||||
"redirect_uris": [
|
"redirect_uris": ["https://remanso.space/"],
|
||||||
"https://remanso.space/"
|
|
||||||
],
|
|
||||||
"scope": "atproto transition:generic",
|
"scope": "atproto transition:generic",
|
||||||
"grant_types": ["authorization_code", "refresh_token"],
|
"grant_types": ["authorization_code", "refresh_token"],
|
||||||
"response_types": ["code"],
|
"response_types": ["code"],
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
defineConfig,
|
defineConfig,
|
||||||
minimal2023Preset as preset,
|
minimal2023Preset as preset
|
||||||
} from "@vite-pwa/assets-generator/config"
|
} from "@vite-pwa/assets-generator/config"
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
preset,
|
preset,
|
||||||
images: ["public/favicon.png"],
|
images: ["public/favicon.png"]
|
||||||
})
|
})
|
||||||
|
|||||||
10
skills-lock.json
Normal file
10
skills-lock.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"skills": {
|
||||||
|
"migrate-oxlint": {
|
||||||
|
"source": "oxc-project/oxc",
|
||||||
|
"sourceType": "github",
|
||||||
|
"computedHash": "80ce5201b1ef52d6cabe553a4cacfd6e1db97bad99618216b9cf9318d11d7e64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ const { isATProtoReady } = useATProtoLogin()
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#main-app {
|
#main-app {
|
||||||
height: 100vh;
|
height: 100dvh;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ export const op = new OpenPanel({
|
|||||||
clientId: "038a6aac-19bb-4a7f-9aae-2d0201fead5b",
|
clientId: "038a6aac-19bb-4a7f-9aae-2d0201fead5b",
|
||||||
trackScreenViews: true,
|
trackScreenViews: true,
|
||||||
trackOutgoingLinks: true,
|
trackOutgoingLinks: true,
|
||||||
trackAttributes: true,
|
trackAttributes: true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createEventBus } from 'retrobus'
|
import { createEventBus } from "retrobus"
|
||||||
|
|
||||||
interface EventBusParams {
|
interface EventBusParams {
|
||||||
fileSha: string
|
fileSha: string
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createEventBus } from 'retrobus'
|
import { createEventBus } from "retrobus"
|
||||||
|
|
||||||
interface EventBusParams {
|
interface EventBusParams {
|
||||||
user: string
|
user: string
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onBeforeMount, ref } from 'vue'
|
import { onBeforeMount, ref } from "vue"
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from "vue-router"
|
||||||
|
|
||||||
import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
|
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
|
||||||
import { signIn } from '@/modules/user/service/signIn'
|
import { signIn } from "@/modules/user/service/signIn"
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -16,14 +16,14 @@ onBeforeMount(async () => {
|
|||||||
if (code) {
|
if (code) {
|
||||||
const token = await signIn(code.toString())
|
const token = await signIn(code.toString())
|
||||||
|
|
||||||
if ('error' in token) {
|
if ("error" in token) {
|
||||||
hasError.value = true
|
hasError.value = true
|
||||||
} else {
|
} else {
|
||||||
token.access_token
|
token.access_token
|
||||||
saveCredentials(token)
|
saveCredentials(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
router.replace({ name: 'Home' })
|
router.replace({ name: "Home" })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouter, type RouteLocationRaw } from "vue-router"
|
import { type RouteLocationRaw, useRouter } from "vue-router"
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{ fallback?: RouteLocationRaw; preferFallback?: boolean }>(),
|
defineProps<{ fallback?: RouteLocationRaw; preferFallback?: boolean }>(),
|
||||||
{ preferFallback: true },
|
{ preferFallback: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
defineAsyncComponent,
|
|
||||||
nextTick,
|
nextTick,
|
||||||
onMounted,
|
onMounted,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
toRefs,
|
toRefs,
|
||||||
watch,
|
watch
|
||||||
} from "vue"
|
} from "vue"
|
||||||
|
|
||||||
|
import HeaderNote from "@/components/HeaderNote.vue"
|
||||||
|
import SkeletonLoader from "@/components/SkeletonLoader.vue"
|
||||||
import StackedNote from "@/components/StackedNote.vue"
|
import StackedNote from "@/components/StackedNote.vue"
|
||||||
import { useLinks } from "@/hooks/useLinks.hook"
|
import { useLinks } from "@/hooks/useLinks.hook"
|
||||||
import { markdownBuilder } from "@/hooks/useMarkdown.hook"
|
import { markdownBuilder } from "@/hooks/useMarkdown.hook"
|
||||||
@@ -19,11 +20,6 @@ import { useVisitRepo } from "@/modules/history/hooks/useVisitRepo.hook"
|
|||||||
import CacheAllNotes from "@/modules/note/components/CacheAllNote.vue"
|
import CacheAllNotes from "@/modules/note/components/CacheAllNote.vue"
|
||||||
import { useUserRepoStore } from "@/modules/repo/store/userRepo.store"
|
import { useUserRepoStore } from "@/modules/repo/store/userRepo.store"
|
||||||
import { useUserSettings } from "@/modules/user/hooks/useUserSettings.hook"
|
import { useUserSettings } from "@/modules/user/hooks/useUserSettings.hook"
|
||||||
import SkeletonLoader from "@/components/SkeletonLoader.vue"
|
|
||||||
|
|
||||||
const HeaderNote = defineAsyncComponent(
|
|
||||||
() => import("@/components/HeaderNote.vue"),
|
|
||||||
)
|
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -38,8 +34,8 @@ const props = withDefaults(
|
|||||||
content: null,
|
content: null,
|
||||||
parseContent: true,
|
parseContent: true,
|
||||||
withContent: true,
|
withContent: true,
|
||||||
withHeader: true,
|
withHeader: true
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const user = computed(() => props.user)
|
const user = computed(() => props.user)
|
||||||
@@ -61,7 +57,7 @@ const renderedContent = computed(() =>
|
|||||||
? props.parseContent
|
? props.parseContent
|
||||||
? toHTML(props.content)
|
? toHTML(props.content)
|
||||||
: props.content
|
: props.content
|
||||||
: store.readme,
|
: store.readme
|
||||||
)
|
)
|
||||||
|
|
||||||
const isLoading = computed(() => renderedContent.value === undefined)
|
const isLoading = computed(() => renderedContent.value === undefined)
|
||||||
@@ -73,7 +69,7 @@ watch(
|
|||||||
await nextTick()
|
await nextTick()
|
||||||
listenToClick()
|
listenToClick()
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -81,7 +77,7 @@ watch(
|
|||||||
() => {
|
() => {
|
||||||
store.setUserRepo(props.user, props.repo)
|
store.setUserRepo(props.user, props.repo)
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
onMounted(() => visitRepo())
|
onMounted(() => visitRepo())
|
||||||
|
|||||||
@@ -9,26 +9,44 @@ const store = useUserRepoStore()
|
|||||||
|
|
||||||
const fontFamilies = computed(() => store.userSettings?.fontFamilies ?? [])
|
const fontFamilies = computed(() => store.userSettings?.fontFamilies ?? [])
|
||||||
const sortedFontFamilies = computed(() =>
|
const sortedFontFamilies = computed(() =>
|
||||||
[...fontFamilies.value].sort((a, b) => a.localeCompare(b)),
|
[...fontFamilies.value].sort((a, b) => a.localeCompare(b))
|
||||||
)
|
)
|
||||||
const fontSizes = Array.from({ length: 7 }, (_, i) => `${9 + i * 2}pt`)
|
const fontSizes = Array.from({ length: 7 }, (_, i) => `${9 + i * 2}pt`)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="font-change" v-if="sortedFontFamilies.length > 0">
|
<div class="font-change" v-if="sortedFontFamilies.length > 0">
|
||||||
<theme-swap />
|
<div>
|
||||||
|
<label for="title-font" class="font-label">t</label>
|
||||||
<select
|
<select
|
||||||
|
id="title-font"
|
||||||
class="select"
|
class="select"
|
||||||
:value="store.userSettings?.chosenFontFamily"
|
:value="store.userSettings?.chosenTitleFont"
|
||||||
@change="store.setFontFamily(($event.target as HTMLSelectElement).value)"
|
@change="store.setTitleFont(($event.target as HTMLSelectElement).value)"
|
||||||
>
|
>
|
||||||
<option v-for="font in sortedFontFamilies" :key="font" :value="font">
|
<option v-for="font in sortedFontFamilies" :key="font" :value="font">
|
||||||
{{ font }}
|
{{ font }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<label for="body-font" class="font-label">p</label>
|
||||||
<select
|
<select
|
||||||
|
id="body-font"
|
||||||
|
class="select"
|
||||||
|
:value="store.userSettings?.chosenBodyFont"
|
||||||
|
@change="store.setBodyFont(($event.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
|
<option v-for="font in sortedFontFamilies" :key="font" :value="font">
|
||||||
|
{{ font }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<theme-swap />
|
||||||
|
|
||||||
|
<label for="font-size" class="font-label">s</label>
|
||||||
|
<select
|
||||||
|
id="font-size"
|
||||||
class="select"
|
class="select"
|
||||||
:value="store.userSettings?.chosenFontSize"
|
:value="store.userSettings?.chosenFontSize"
|
||||||
@change="store.setFontSize(($event.target as HTMLSelectElement).value)"
|
@change="store.setFontSize(($event.target as HTMLSelectElement).value)"
|
||||||
@@ -38,19 +56,28 @@ const fontSizes = Array.from({ length: 7 }, (_, i) => `${9 + i * 2}pt`)
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.font-change {
|
.font-change {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-label {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from "vue-router"
|
||||||
|
|
||||||
const { push } = useRouter()
|
const { push } = useRouter()
|
||||||
|
|
||||||
const back = () =>
|
const back = () =>
|
||||||
push({
|
push({
|
||||||
name: 'Home'
|
name: "Home"
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,13 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import FontChange from "@/components/FontChange.vue"
|
import FontChange from "@/components/FontChange.vue"
|
||||||
|
import HomeButton from "@/components/HomeButton.vue"
|
||||||
|
|
||||||
defineProps<{ user: string; repo: string }>()
|
defineProps<{ user: string; repo: string }>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header class="header-note">
|
<header class="header-note">
|
||||||
<router-link
|
<home-button />
|
||||||
:to="{ name: 'Home' }"
|
|
||||||
class="button is-small is-white back-button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="icon icon-tabler icon-tabler-arrow-narrow-left"
|
|
||||||
width="28"
|
|
||||||
height="28"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<line x1="5" y1="12" x2="19" y2="12" />
|
|
||||||
<line x1="5" y1="12" x2="9" y2="16" />
|
|
||||||
<line x1="5" y1="12" x2="9" y2="8" />
|
|
||||||
</svg>
|
|
||||||
</router-link>
|
|
||||||
<!-- <router-link
|
<!-- <router-link
|
||||||
:to="{ name: 'SpacedRepetitionCard', params: { user, repo } }"
|
:to="{ name: 'SpacedRepetitionCard', params: { user, repo } }"
|
||||||
>
|
>
|
||||||
@@ -51,12 +32,12 @@ defineProps<{ user: string; repo: string }>()
|
|||||||
</svg>
|
</svg>
|
||||||
</router-link> -->
|
</router-link> -->
|
||||||
|
|
||||||
<button onclick="font_modal.showModal()">
|
<a class="btn btn-ghost btn-circle" onclick="font_modal.showModal()">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="icon icon-tabler icons-tabler-outline icon-tabler-typography"
|
class="icon icon-tabler icons-tabler-outline icon-tabler-typography"
|
||||||
width="36"
|
width="30"
|
||||||
height="36"
|
height="30"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -70,12 +51,15 @@ defineProps<{ user: string; repo: string }>()
|
|||||||
<path d="M10.2 6.3l5.8 13.7" />
|
<path d="M10.2 6.3l5.8 13.7" />
|
||||||
<path d="M5 20l6 -16l2 0l7 16" />
|
<path d="M5 20l6 -16l2 0l7 16" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</a>
|
||||||
<router-link :to="{ name: 'FluxNoteView', params: { user, repo } }">
|
<router-link
|
||||||
|
class="btn btn-ghost btn-circle"
|
||||||
|
:to="{ name: 'FluxNoteView', params: { user, repo } }"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="36"
|
width="30"
|
||||||
height="36"
|
height="30"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
fill="none"
|
fill="none"
|
||||||
@@ -88,12 +72,15 @@ defineProps<{ user: string; repo: string }>()
|
|||||||
<path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" />
|
<path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" />
|
||||||
</svg>
|
</svg>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link :to="{ name: 'DraftNotes', params: { user, repo } }">
|
<router-link
|
||||||
|
class="btn btn-ghost btn-circle"
|
||||||
|
:to="{ name: 'DraftNotes', params: { user, repo } }"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="icon icon-tabler icon-tabler-notes"
|
class="icon icon-tabler icon-tabler-notes"
|
||||||
width="36"
|
width="30"
|
||||||
height="36"
|
height="30"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -107,11 +94,14 @@ defineProps<{ user: string; repo: string }>()
|
|||||||
<line x1="9" y1="15" x2="13" y2="15" />
|
<line x1="9" y1="15" x2="13" y2="15" />
|
||||||
</svg>
|
</svg>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link :to="{ name: 'TodoNotes', params: { user, repo } }">
|
<router-link
|
||||||
|
class="btn btn-ghost btn-circle"
|
||||||
|
:to="{ name: 'TodoNotes', params: { user, repo } }"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="36"
|
width="30"
|
||||||
height="36"
|
height="30"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -129,12 +119,15 @@ defineProps<{ user: string; repo: string }>()
|
|||||||
<path d="M11 18l9 0" />
|
<path d="M11 18l9 0" />
|
||||||
</svg>
|
</svg>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link :to="{ name: 'FleetingNotes', params: { user, repo } }">
|
<router-link
|
||||||
|
class="btn btn-ghost btn-circle"
|
||||||
|
:to="{ name: 'FleetingNotes', params: { user, repo } }"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="icon icon-tabler icon-tabler-mailbox"
|
class="icon icon-tabler icon-tabler-mailbox"
|
||||||
width="36"
|
width="30"
|
||||||
height="36"
|
height="30"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -150,7 +143,7 @@ defineProps<{ user: string; repo: string }>()
|
|||||||
</svg>
|
</svg>
|
||||||
</router-link>
|
</router-link>
|
||||||
<dialog id="font_modal" class="modal">
|
<dialog id="font_modal" class="modal">
|
||||||
<div class="modal-box">
|
<div class="modal-box w-11/12 max-w-5xl">
|
||||||
<h3 class="text-lg font-bold">Style settings</h3>
|
<h3 class="text-lg font-bold">Style settings</h3>
|
||||||
<font-change />
|
<font-change />
|
||||||
</div>
|
</div>
|
||||||
@@ -167,15 +160,5 @@ defineProps<{ user: string; repo: string }>()
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
|
||||||
img {
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
color: var(--color-accent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const url = new URL('https://github.com/login/oauth/authorize')
|
const url = new URL("https://github.com/login/oauth/authorize")
|
||||||
url.searchParams.append('client_id', 'Iv1.87be14adcc912fa0')
|
url.searchParams.append("client_id", "Iv1.87be14adcc912fa0")
|
||||||
url.searchParams.append('redirect_uri', location.href)
|
url.searchParams.append("redirect_uri", location.href)
|
||||||
url.searchParams.append('scope', 'repo')
|
url.searchParams.append("scope", "repo")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PublicNoteListItem } from "@/modules/note/models/Note"
|
|
||||||
import { toShortDid } from "@/modules/atproto/shortDid"
|
|
||||||
import { slugify } from "@/utils/slugify"
|
|
||||||
import { vInfiniteScroll } from "@vueuse/components"
|
import { vInfiniteScroll } from "@vueuse/components"
|
||||||
|
|
||||||
|
import { toShortDid } from "@/modules/atproto/shortDid"
|
||||||
|
import { PublicNoteListItem } from "@/modules/note/models/Note"
|
||||||
|
import { slugify } from "@/utils/slugify"
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
notes: PublicNoteListItem[]
|
notes: PublicNoteListItem[]
|
||||||
canLoadMore: boolean
|
canLoadMore: boolean
|
||||||
@@ -28,8 +29,8 @@ defineSlots<{
|
|||||||
params: {
|
params: {
|
||||||
shortDid: toShortDid(note.did),
|
shortDid: toShortDid(note.did),
|
||||||
rkey: note.rkey,
|
rkey: note.rkey,
|
||||||
slug: slugify(note.title),
|
slug: slugify(note.title)
|
||||||
},
|
}
|
||||||
}"
|
}"
|
||||||
class="btn btn-link"
|
class="btn btn-link"
|
||||||
>{{ note.title }}</router-link
|
>{{ note.title }}</router-link
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ const getStyle = (seed: string) => {
|
|||||||
name: 'FluxNoteView',
|
name: 'FluxNoteView',
|
||||||
params: {
|
params: {
|
||||||
user: username,
|
user: username,
|
||||||
repo: favoriteRepo.name,
|
repo: favoriteRepo.name
|
||||||
},
|
}
|
||||||
}"
|
}"
|
||||||
class="btn"
|
class="btn"
|
||||||
:style="getStyle(`${favoriteRepo.name}-${username}`)"
|
:style="getStyle(`${favoriteRepo.name}-${username}`)"
|
||||||
@@ -40,6 +40,7 @@ const getStyle = (seed: string) => {
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.repo-list {
|
.repo-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ import { ref } from "vue"
|
|||||||
|
|
||||||
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
|
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
|
||||||
|
|
||||||
const { handle, isLoggedIn, isATProtoReady, signIn, signOut } = useATProtoLogin()
|
const { handle, isLoggedIn, isATProtoReady, signIn, signOut } =
|
||||||
|
useATProtoLogin()
|
||||||
|
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
withSignOut?: boolean
|
withSignOut?: boolean
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
withSignOut: true,
|
withSignOut: true
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const inputHandle = ref("")
|
const inputHandle = ref("")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
nextTick,
|
nextTick,
|
||||||
onMounted,
|
onMounted,
|
||||||
ref,
|
ref,
|
||||||
watch,
|
watch
|
||||||
} from "vue"
|
} from "vue"
|
||||||
|
|
||||||
import { useEditionMode } from "@/hooks/useEditionMode"
|
import { useEditionMode } from "@/hooks/useEditionMode"
|
||||||
@@ -13,20 +13,20 @@ import { useFile } from "@/hooks/useFile.hook"
|
|||||||
import { useGitHubContent } from "@/hooks/useGitHubContent.hook"
|
import { useGitHubContent } from "@/hooks/useGitHubContent.hook"
|
||||||
import { useImages } from "@/hooks/useImages.hook"
|
import { useImages } from "@/hooks/useImages.hook"
|
||||||
import { useLinks } from "@/hooks/useLinks.hook"
|
import { useLinks } from "@/hooks/useLinks.hook"
|
||||||
|
import { runMermaid, useShikiji } from "@/hooks/useMarkdown.hook"
|
||||||
import { useNoteOverlay } from "@/hooks/useNoteOverlay.hook"
|
import { useNoteOverlay } from "@/hooks/useNoteOverlay.hook"
|
||||||
import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook"
|
import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook"
|
||||||
import { useTitleNotes } from "@/hooks/useTitleNotes.hook"
|
import { useTitleNotes } from "@/hooks/useTitleNotes.hook"
|
||||||
import { useUserRepoStore } from "@/modules/repo/store/userRepo.store"
|
import { useUserRepoStore } from "@/modules/repo/store/userRepo.store"
|
||||||
import { encodeUTF8ToBase64 } from "@/utils/decodeBase64ToUTF8"
|
import { encodeUTF8ToBase64 } from "@/utils/decodeBase64ToUTF8"
|
||||||
import { filenameToNoteTitle } from "@/utils/noteTitle"
|
import { filenameToNoteTitle } from "@/utils/noteTitle"
|
||||||
import { runMermaid, useShikiji } from "@/hooks/useMarkdown.hook"
|
|
||||||
|
|
||||||
const LinkedNotes = defineAsyncComponent(
|
const LinkedNotes = defineAsyncComponent(
|
||||||
() => import("@/components/LinkedNotes.vue"),
|
() => import("@/components/LinkedNotes.vue")
|
||||||
)
|
)
|
||||||
|
|
||||||
const EditNote = defineAsyncComponent(
|
const EditNote = defineAsyncComponent(
|
||||||
() => import("@/modules/note/components/EditNote.vue"),
|
() => import("@/modules/note/components/EditNote.vue")
|
||||||
)
|
)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -50,7 +50,7 @@ const {
|
|||||||
rawContent,
|
rawContent,
|
||||||
getRawContent,
|
getRawContent,
|
||||||
saveCacheNote,
|
saveCacheNote,
|
||||||
getEditedSha,
|
getEditedSha
|
||||||
} = useFile(sha)
|
} = useFile(sha)
|
||||||
const initialRawContent = ref<string | null>(null)
|
const initialRawContent = ref<string | null>(null)
|
||||||
const className = computed(() => `stacked-note-${props.index}`)
|
const className = computed(() => `stacked-note-${props.index}`)
|
||||||
@@ -67,7 +67,7 @@ const breadcrumbs = computed(() => displayedTitle.value.split(" / "))
|
|||||||
|
|
||||||
const { updateFile } = useGitHubContent({
|
const { updateFile } = useGitHubContent({
|
||||||
user: user.value,
|
user: user.value,
|
||||||
repo: repo.value,
|
repo: repo.value
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -115,7 +115,7 @@ watch(mode, async (newMode) => {
|
|||||||
const newSha = await updateFile({
|
const newSha = await updateFile({
|
||||||
content: rawContent.value,
|
content: rawContent.value,
|
||||||
path: path.value,
|
path: path.value,
|
||||||
sha: editedSha,
|
sha: editedSha
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!newSha) {
|
if (!newSha) {
|
||||||
@@ -125,7 +125,7 @@ watch(mode, async (newMode) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await saveCacheNote(encodeUTF8ToBase64(rawContent.value), {
|
await saveCacheNote(encodeUTF8ToBase64(rawContent.value), {
|
||||||
editedSha: newSha,
|
editedSha: newSha
|
||||||
})
|
})
|
||||||
initialRawContent.value = rawContent.value
|
initialRawContent.value = rawContent.value
|
||||||
})
|
})
|
||||||
@@ -137,7 +137,7 @@ watch(mode, async (newMode) => {
|
|||||||
:class="{
|
:class="{
|
||||||
[className]: true,
|
[className]: true,
|
||||||
overlay: displayNoteOverlay,
|
overlay: displayNoteOverlay,
|
||||||
[`note-${sha}`]: true,
|
[`note-${sha}`]: true
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@@ -198,7 +198,9 @@ watch(mode, async (newMode) => {
|
|||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M6 4h10l4 4v10a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2" />
|
<path
|
||||||
|
d="M6 4h10l4 4v10a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2"
|
||||||
|
/>
|
||||||
<path d="M12 14m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
<path d="M12 14m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||||
<path d="M14 4l0 4l-6 0l0 -4" />
|
<path d="M14 4l0 4l-6 0l0 -4" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { computedAsync } from "@vueuse/core"
|
||||||
import { computed, nextTick, ref, watch } from "vue"
|
import { computed, nextTick, ref, watch } from "vue"
|
||||||
import { useRoute } from "vue-router"
|
import { useRoute } from "vue-router"
|
||||||
import { errorMessage } from "@/utils/notif"
|
|
||||||
|
import SkeletonLoader from "@/components/SkeletonLoader.vue"
|
||||||
import { useATProtoLinks } from "@/hooks/useATProtoLinks.hook"
|
import { useATProtoLinks } from "@/hooks/useATProtoLinks.hook"
|
||||||
|
import { markdownBuilder } from "@/hooks/useMarkdown.hook"
|
||||||
import { useNoteOverlay } from "@/hooks/useNoteOverlay.hook"
|
import { useNoteOverlay } from "@/hooks/useNoteOverlay.hook"
|
||||||
import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook"
|
import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook"
|
||||||
import { markdownBuilder } from "@/hooks/useMarkdown.hook"
|
|
||||||
import { computedAsync } from "@vueuse/core"
|
|
||||||
import { getUrl } from "@/modules/atproto/getUrl"
|
|
||||||
import { withATProtoImages } from "@/modules/atproto/withATProtoImages"
|
|
||||||
import { getAuthor } from "@/modules/atproto/getAuthor"
|
import { getAuthor } from "@/modules/atproto/getAuthor"
|
||||||
import { fromShortDid } from "@/modules/atproto/shortDid"
|
import { getUrl } from "@/modules/atproto/getUrl"
|
||||||
import { PublicNoteRecord } from "@/modules/atproto/publicNote.types"
|
import { PublicNoteRecord } from "@/modules/atproto/publicNote.types"
|
||||||
import SkeletonLoader from "@/components/SkeletonLoader.vue"
|
import { fromShortDid } from "@/modules/atproto/shortDid"
|
||||||
|
import { withATProtoImages } from "@/modules/atproto/withATProtoImages"
|
||||||
|
import { errorMessage } from "@/utils/notif"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
didrkey: string
|
didrkey: string
|
||||||
@@ -28,7 +29,7 @@ const index = computed(() => props.index)
|
|||||||
|
|
||||||
const author = computedAsync(async () => getAuthor(did.value))
|
const author = computedAsync(async () => getAuthor(did.value))
|
||||||
const url = computedAsync(async () =>
|
const url = computedAsync(async () =>
|
||||||
getUrl({ did: did.value, rkey: rkey.value }),
|
getUrl({ did: did.value, rkey: rkey.value })
|
||||||
)
|
)
|
||||||
|
|
||||||
const className = computed(() => `stacked-note-${props.index}`)
|
const className = computed(() => `stacked-note-${props.index}`)
|
||||||
@@ -36,13 +37,13 @@ const titleClassName = computed(() => `title-${className.value}`)
|
|||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const mainNoteId = computed(
|
const mainNoteId = computed(
|
||||||
() => `${route.params.shortDid}-${route.params.rkey}`,
|
() => `${route.params.shortDid}-${route.params.rkey}`
|
||||||
)
|
)
|
||||||
|
|
||||||
const { scrollToFocusedNote } = useRouteQueryStackedNotes()
|
const { scrollToFocusedNote } = useRouteQueryStackedNotes()
|
||||||
const { listenToClick } = useATProtoLinks(className.value, {
|
const { listenToClick } = useATProtoLinks(className.value, {
|
||||||
currentAtUri: didrkey,
|
currentAtUri: didrkey,
|
||||||
mainNoteId,
|
mainNoteId
|
||||||
})
|
})
|
||||||
const { displayNoteOverlay } = useNoteOverlay(className.value, index)
|
const { displayNoteOverlay } = useNoteOverlay(className.value, index)
|
||||||
|
|
||||||
@@ -69,10 +70,10 @@ const content = computed(() =>
|
|||||||
? toHTML(
|
? toHTML(
|
||||||
withATProtoImages(noteRecord.value.value.content, {
|
withATProtoImages(noteRecord.value.value.content, {
|
||||||
pds: author.value.pds,
|
pds: author.value.pds,
|
||||||
did: did.value,
|
did: did.value
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
: "",
|
: ""
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -81,7 +82,7 @@ watch(
|
|||||||
await nextTick()
|
await nextTick()
|
||||||
listenToClick()
|
listenToClick()
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -91,7 +92,7 @@ watch(
|
|||||||
:class="{
|
:class="{
|
||||||
[className]: true,
|
[className]: true,
|
||||||
overlay: displayNoteOverlay,
|
overlay: displayNoteOverlay,
|
||||||
[`note-${classNameId}`]: true,
|
[`note-${classNameId}`]: true
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -3,35 +3,28 @@ import RepoList from "@/components/RepoList.vue"
|
|||||||
import SignInAtproto from "@/components/SignInAtproto.vue"
|
import SignInAtproto from "@/components/SignInAtproto.vue"
|
||||||
import SignInGithub from "@/components/SignInGithub.vue"
|
import SignInGithub from "@/components/SignInGithub.vue"
|
||||||
import ThemeSwap from "@/components/ThemeSwap.vue"
|
import ThemeSwap from "@/components/ThemeSwap.vue"
|
||||||
|
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
|
||||||
import { useForm } from "@/hooks/useForm.hook"
|
import { useForm } from "@/hooks/useForm.hook"
|
||||||
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
|
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
|
||||||
import LastVisited from "@/modules/history/components/LastVisited.vue"
|
import LastVisited from "@/modules/history/components/LastVisited.vue"
|
||||||
|
|
||||||
const { isLogged } = useGitHubLogin()
|
const { isLogged } = useGitHubLogin()
|
||||||
|
const { isLoggedIn: isATProtoLoggedIn, avatarUrl } = useATProtoLogin()
|
||||||
const { userInput, repoInput, submit } = useForm()
|
const { userInput, repoInput, submit } = useForm()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="welcome-world">
|
<div class="welcome-world">
|
||||||
|
<div class="welcome-content">
|
||||||
<h1 class="title is-1">
|
<h1 class="title is-1">
|
||||||
<img src="/favicon.png" alt="Remanso icon" class="remanso-logo" />
|
<img src="/favicon.png" alt="Remanso icon" class="remanso-logo" />
|
||||||
Remanso
|
Remanso
|
||||||
</h1>
|
</h1>
|
||||||
<p>
|
|
||||||
<sign-in-atproto :with-sign-out="false" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<repo-list />
|
<repo-list />
|
||||||
|
|
||||||
<last-visited />
|
<last-visited />
|
||||||
|
|
||||||
<div class="get-started">
|
|
||||||
<sign-in-github />
|
|
||||||
<router-link v-if="isLogged" :to="{ name: 'RepoList' }" class="btn btn-sm"
|
|
||||||
>Manage your repos</router-link
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form class="github-form" @submit.prevent>
|
<form class="github-form" @submit.prevent>
|
||||||
<div>github/</div>
|
<div>github/</div>
|
||||||
<input
|
<input
|
||||||
@@ -47,12 +40,15 @@ const { userInput, repoInput, submit } = useForm()
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="repo"
|
placeholder="repo"
|
||||||
/>
|
/>
|
||||||
<button type="submit" class="btn btn-primary" @click="submit">go</button>
|
<button type="submit" class="btn btn-sm btn-primary" @click="submit">
|
||||||
|
go
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<theme-swap />
|
<theme-swap />
|
||||||
Made with
|
made with
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="icon icon-tabler icon-tabler-heart"
|
class="icon icon-tabler icon-tabler-heart"
|
||||||
@@ -74,15 +70,63 @@ const { userInput, repoInput, submit } = useForm()
|
|||||||
<a href="https://apoena.dev" target="_blank" rel="noopener noreferrer"
|
<a href="https://apoena.dev" target="_blank" rel="noopener noreferrer"
|
||||||
>apoena</a
|
>apoena</a
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-ghost btn-circle btn-sm profile-btn"
|
||||||
|
onclick="profile_modal.showModal()"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="isATProtoLoggedIn && avatarUrl"
|
||||||
|
:src="avatarUrl"
|
||||||
|
class="profile-avatar"
|
||||||
|
alt="Profile"
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
v-else
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="28"
|
||||||
|
height="28"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" />
|
||||||
|
<path d="M6 20c0 -2.21 2.686 -4 6 -4s6 1.79 6 4" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
name: 'FluxNoteView',
|
name: 'FluxNoteView',
|
||||||
params: { user: 'remanso-space', repo: 'getting-started' },
|
params: { user: 'remanso-space', repo: 'getting-started' }
|
||||||
}"
|
}"
|
||||||
class="btn btn-sm"
|
class="btn btn-sm"
|
||||||
>Get started</router-link
|
>Get started</router-link
|
||||||
>
|
>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<dialog id="profile_modal" class="modal">
|
||||||
|
<div class="modal-box profile-modal-box">
|
||||||
|
<h3 class="text-lg font-bold">Profile</h3>
|
||||||
|
<div class="profile-section">
|
||||||
|
<sign-in-atproto :with-sign-out="true" />
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="profile-section">
|
||||||
|
<sign-in-github />
|
||||||
|
<router-link
|
||||||
|
v-if="isLogged"
|
||||||
|
:to="{ name: 'RepoList' }"
|
||||||
|
class="btn btn-sm"
|
||||||
|
>Manage your repos</router-link
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button></button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -100,26 +144,26 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.welcome-world {
|
.welcome-world {
|
||||||
padding: 1rem;
|
|
||||||
margin: auto;
|
margin: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
align-self: stretch;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.get-started {
|
|
||||||
margin: center;
|
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.welcome-content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.github-form {
|
.github-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -129,15 +173,33 @@ h1 {
|
|||||||
max-width: 140px;
|
max-width: 140px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: 0.2rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
img {
|
padding: 0.5rem;
|
||||||
vertical-align: middle;
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-avatar {
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-modal-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-section {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.to-user-repo {
|
.to-user-repo {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ export const getNoteWidth = () => {
|
|||||||
if (cached === undefined) {
|
if (cached === undefined) {
|
||||||
cached = parseInt(
|
cached = parseInt(
|
||||||
getComputedStyle(document.documentElement).getPropertyValue(
|
getComputedStyle(document.documentElement).getPropertyValue(
|
||||||
"--note-width",
|
"--note-width"
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return cached
|
return cached
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
export enum DataType {
|
export enum DataType {
|
||||||
GithubAccessToken = 'GithubAccessToken',
|
GithubAccessToken = "GithubAccessToken",
|
||||||
FavoriteRepo = 'FavoriteRepo',
|
FavoriteRepo = "FavoriteRepo",
|
||||||
SavedRepo = 'SavedRepo',
|
SavedRepo = "SavedRepo",
|
||||||
Note = 'Note',
|
Note = "Note",
|
||||||
BacklinkNote = 'BacklinkNote',
|
BacklinkNote = "BacklinkNote",
|
||||||
RepetitionCard = 'RepetitionCard',
|
RepetitionCard = "RepetitionCard",
|
||||||
History = 'History',
|
History = "History",
|
||||||
UserSettings = 'UserSettings',
|
UserSettings = "UserSettings",
|
||||||
AtprotoSession = 'AtprotoSession'
|
AtprotoSession = "AtprotoSession"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ interface GetAllParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Data {
|
class Data {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// oxlint-disable-next-line typescript/ban-types
|
||||||
private readonly locale: PouchDB.Database<{}> | null = null
|
private readonly locale: PouchDB.Database<{}> | null = null
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
try {
|
try {
|
||||||
this.locale = new PouchDb("remanso", {
|
this.locale = new PouchDb("remanso", {
|
||||||
adapter: "indexeddb",
|
adapter: "indexeddb"
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("data error", error)
|
console.warn("data error", error)
|
||||||
@@ -40,7 +40,7 @@ class Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async update<DT extends DataType, T extends Model<DT>>(
|
public async update<DT extends DataType, T extends Model<DT>>(
|
||||||
model: T,
|
model: T
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
if (!model._id) {
|
if (!model._id) {
|
||||||
@@ -71,7 +71,7 @@ class Data {
|
|||||||
}
|
}
|
||||||
const result = await this.locale?.put({
|
const result = await this.locale?.put({
|
||||||
...doc,
|
...doc,
|
||||||
_deleted: true,
|
_deleted: true
|
||||||
})
|
})
|
||||||
return result?.ok ?? false
|
return result?.ok ?? false
|
||||||
} catch {
|
} catch {
|
||||||
@@ -80,7 +80,7 @@ class Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async get<DT extends DataType, T extends Model<DT>>(
|
public async get<DT extends DataType, T extends Model<DT>>(
|
||||||
id: string,
|
id: string
|
||||||
): Promise<T | null> {
|
): Promise<T | null> {
|
||||||
try {
|
try {
|
||||||
return ((await this.locale?.get(id)) as T) || null
|
return ((await this.locale?.get(id)) as T) || null
|
||||||
@@ -91,7 +91,7 @@ class Data {
|
|||||||
|
|
||||||
public async getOrCreate<DT extends DataType, T extends Model<DT>>(
|
public async getOrCreate<DT extends DataType, T extends Model<DT>>(
|
||||||
id: string,
|
id: string,
|
||||||
initialValue: T,
|
initialValue: T
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const element = await this.get<DT, T>(id)
|
const element = await this.get<DT, T>(id)
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ class Data {
|
|||||||
prefix,
|
prefix,
|
||||||
includeDocs = true,
|
includeDocs = true,
|
||||||
includeAttachments = false,
|
includeAttachments = false,
|
||||||
keys = [],
|
keys = []
|
||||||
}: GetAllParams): Promise<T[]> {
|
}: GetAllParams): Promise<T[]> {
|
||||||
if (!this.locale) {
|
if (!this.locale) {
|
||||||
return []
|
return []
|
||||||
@@ -118,7 +118,7 @@ class Data {
|
|||||||
const response = await this.locale.allDocs({
|
const response = await this.locale.allDocs({
|
||||||
include_docs: includeDocs,
|
include_docs: includeDocs,
|
||||||
attachments: includeAttachments,
|
attachments: includeAttachments,
|
||||||
keys: keys.map((key) => this.generateId(prefix, key)),
|
keys: keys.map((key) => this.generateId(prefix, key))
|
||||||
})
|
})
|
||||||
|
|
||||||
if (includeDocs) {
|
if (includeDocs) {
|
||||||
@@ -148,7 +148,7 @@ class Data {
|
|||||||
include_docs: includeDocs,
|
include_docs: includeDocs,
|
||||||
attachments: includeAttachments,
|
attachments: includeAttachments,
|
||||||
startkey: prefix ? prefix : undefined,
|
startkey: prefix ? prefix : undefined,
|
||||||
endkey: prefix ? `${prefix}\ufff0` : undefined,
|
endkey: prefix ? `${prefix}\ufff0` : undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
return response.rows.map((row) => row.doc) as T[]
|
return response.rows.map((row) => row.doc) as T[]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { Model } from '@/data/models/Model'
|
import { Model } from "@/data/models/Model"
|
||||||
|
|
||||||
export interface AtprotoSession extends Model<DataType.AtprotoSession> {
|
export interface AtprotoSession extends Model<DataType.AtprotoSession> {
|
||||||
did: string
|
did: string
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { Model } from '@/data/models/Model'
|
import { Model } from "@/data/models/Model"
|
||||||
|
|
||||||
export interface GithubAccessToken extends Model<DataType.GithubAccessToken> {
|
export interface GithubAccessToken extends Model<DataType.GithubAccessToken> {
|
||||||
username: string
|
username: string
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { Model } from '@/data/models/Model'
|
import { Model } from "@/data/models/Model"
|
||||||
|
|
||||||
export interface History extends Model<DataType.History> {
|
export interface History extends Model<DataType.History> {
|
||||||
repos: ReadonlyArray<{
|
repos: ReadonlyArray<{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DataType } from '../DataType.enum'
|
import { DataType } from "../DataType.enum"
|
||||||
|
|
||||||
export interface Model<DT extends DataType> {
|
export interface Model<DT extends DataType> {
|
||||||
_id?: string
|
_id?: string
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { ComputedRef, onUnmounted, Ref, toValue } from "vue"
|
import { ComputedRef, onUnmounted, Ref, toValue } from "vue"
|
||||||
|
|
||||||
import { isExternalLink } from "@/utils/link"
|
|
||||||
import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook"
|
import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook"
|
||||||
import { parseAtUri } from "@/modules/atproto/parseAtUri"
|
import { parseAtUri } from "@/modules/atproto/parseAtUri"
|
||||||
import { toShortDid } from "@/modules/atproto/shortDid"
|
import { toShortDid } from "@/modules/atproto/shortDid"
|
||||||
import { router } from "@/router/router"
|
import { router } from "@/router/router"
|
||||||
|
import { isExternalLink } from "@/utils/link"
|
||||||
|
|
||||||
export const useATProtoLinks = (
|
export const useATProtoLinks = (
|
||||||
className: ComputedRef<string> | string,
|
className: ComputedRef<string> | string,
|
||||||
options: {
|
options: {
|
||||||
currentAtUri?: Ref<string> | string | ComputedRef<string>
|
currentAtUri?: Ref<string> | string | ComputedRef<string>
|
||||||
mainNoteId: Ref<string> | string | ComputedRef<string>
|
mainNoteId: Ref<string> | string | ComputedRef<string>
|
||||||
},
|
}
|
||||||
) => {
|
) => {
|
||||||
const { addStackedNote, scrollToFocusedNote } = useRouteQueryStackedNotes()
|
const { addStackedNote, scrollToFocusedNote } = useRouteQueryStackedNotes()
|
||||||
const { currentAtUri, mainNoteId } = options
|
const { currentAtUri, mainNoteId } = options
|
||||||
@@ -38,7 +38,7 @@ export const useATProtoLinks = (
|
|||||||
|
|
||||||
if (href.startsWith(window.location.origin)) {
|
if (href.startsWith(window.location.origin)) {
|
||||||
const { params } = router.resolve(
|
const { params } = router.resolve(
|
||||||
href.replace(window.location.origin, ""),
|
href.replace(window.location.origin, "")
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!params.shortDid || !params.rkey) {
|
if (!params.shortDid || !params.rkey) {
|
||||||
@@ -57,7 +57,7 @@ export const useATProtoLinks = (
|
|||||||
addStackedNote(
|
addStackedNote(
|
||||||
toValue(currentAtUri) ?? "",
|
toValue(currentAtUri) ?? "",
|
||||||
noteId,
|
noteId,
|
||||||
`${params.shortDid}-${params.rkey}`,
|
`${params.shortDid}-${params.rkey}`
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -111,6 +111,6 @@ export const useATProtoLinks = (
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listenToClick,
|
listenToClick
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,62 @@
|
|||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from "vue"
|
||||||
|
|
||||||
import { getAuthor } from '@/modules/atproto/getAuthor'
|
import { getAuthor } from "@/modules/atproto/getAuthor"
|
||||||
import { restoreSession, sdkSignOut, signInWithHandle } from '@/modules/atproto/service/atprotoOAuth'
|
import {
|
||||||
import { clearSession, loadSession, saveSession } from '@/modules/atproto/service/atprotoSession'
|
restoreSession,
|
||||||
|
sdkSignOut,
|
||||||
|
signInWithHandle
|
||||||
|
} from "@/modules/atproto/service/atprotoOAuth"
|
||||||
|
import {
|
||||||
|
clearSession,
|
||||||
|
loadSession,
|
||||||
|
saveSession
|
||||||
|
} from "@/modules/atproto/service/atprotoSession"
|
||||||
|
|
||||||
const did = ref<string | null>(null)
|
const did = ref<string | null>(null)
|
||||||
const handle = ref<string | null>(null)
|
const handle = ref<string | null>(null)
|
||||||
|
const avatarUrl = ref<string | null>(null)
|
||||||
|
|
||||||
let init = true
|
let init = true
|
||||||
|
|
||||||
|
const fetchAvatar = async (actorDid: string) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(actorDid)}`
|
||||||
|
)
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json()
|
||||||
|
avatarUrl.value = data.avatar ?? null
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
avatarUrl.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const initializeAuth = async () => {
|
const initializeAuth = async () => {
|
||||||
// Load cached session from IndexedDB first (fast, local) so the UI can render immediately
|
// Load cached session from IndexedDB first (fast, local) so the UI can render immediately
|
||||||
const stored = await loadSession()
|
const stored = await loadSession()
|
||||||
did.value = stored?.did ?? ''
|
did.value = stored?.did ?? ""
|
||||||
handle.value = stored?.handle ?? ''
|
handle.value = stored?.handle ?? ""
|
||||||
|
if (stored?.did) {
|
||||||
|
fetchAvatar(stored.did)
|
||||||
|
}
|
||||||
|
|
||||||
// Then restore OAuth session in the background (may involve network)
|
// Then restore OAuth session in the background (may involve network)
|
||||||
const session = await restoreSession()
|
const session = await restoreSession()
|
||||||
if (session) {
|
if (session) {
|
||||||
const author = await getAuthor(session.did)
|
const author = await getAuthor(session.did)
|
||||||
const resolvedHandle = author?.handle ?? ''
|
const resolvedHandle = author?.handle ?? ""
|
||||||
|
|
||||||
did.value = session.did
|
did.value = session.did
|
||||||
handle.value = resolvedHandle
|
handle.value = resolvedHandle
|
||||||
await saveSession(session.did, resolvedHandle)
|
await saveSession(session.did, resolvedHandle)
|
||||||
|
fetchAvatar(session.did)
|
||||||
|
|
||||||
window.history.replaceState(null, '', window.location.pathname + window.location.search)
|
window.history.replaceState(
|
||||||
|
null,
|
||||||
|
"",
|
||||||
|
window.location.pathname + window.location.search
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,16 +78,18 @@ export const useATProtoLogin = () => {
|
|||||||
await sdkSignOut(did.value)
|
await sdkSignOut(did.value)
|
||||||
}
|
}
|
||||||
await clearSession()
|
await clearSession()
|
||||||
did.value = ''
|
did.value = ""
|
||||||
handle.value = ''
|
handle.value = ""
|
||||||
|
avatarUrl.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
did,
|
did,
|
||||||
handle,
|
handle,
|
||||||
|
avatarUrl,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
isATProtoReady,
|
isATProtoReady,
|
||||||
signIn,
|
signIn,
|
||||||
signOut,
|
signOut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useAsyncState } from '@vueuse/core'
|
import { useAsyncState } from "@vueuse/core"
|
||||||
import { ComputedRef, onUnmounted, toValue } from 'vue'
|
import { ComputedRef, onUnmounted, toValue } from "vue"
|
||||||
|
|
||||||
import { backlinkEventBus } from '@/bus/backlinkEventBus'
|
import { backlinkEventBus } from "@/bus/backlinkEventBus"
|
||||||
import { data } from '@/data/data'
|
import { data } from "@/data/data"
|
||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { BacklinkNote } from '@/modules/note/models/BacklinkNote'
|
import { BacklinkNote } from "@/modules/note/models/BacklinkNote"
|
||||||
|
|
||||||
export const useBacklinks = (sha: string | ComputedRef<string>) => {
|
export const useBacklinks = (sha: string | ComputedRef<string>) => {
|
||||||
sha = toValue(sha)
|
sha = toValue(sha)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ref, Ref, toValue, onUnmounted } from "vue"
|
|
||||||
import { useDebounceFn } from "@vueuse/core"
|
import { useDebounceFn } from "@vueuse/core"
|
||||||
|
import { onUnmounted, Ref, ref, toValue } from "vue"
|
||||||
|
|
||||||
import { useGitHubContent } from "@/hooks/useGitHubContent.hook"
|
import { useGitHubContent } from "@/hooks/useGitHubContent.hook"
|
||||||
|
|
||||||
const CHECKBOX_PATTERN = /\[([ xX])\]/g
|
const CHECKBOX_PATTERN = /\[([ xX])\]/g
|
||||||
@@ -7,7 +8,7 @@ const CHECKBOX_PATTERN = /\[([ xX])\]/g
|
|||||||
const setCheckboxInMarkdown = (
|
const setCheckboxInMarkdown = (
|
||||||
markdown: string,
|
markdown: string,
|
||||||
index: number,
|
index: number,
|
||||||
checked: boolean,
|
checked: boolean
|
||||||
): string => {
|
): string => {
|
||||||
let currentIndex = 0
|
let currentIndex = 0
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ const setCheckboxInMarkdown = (
|
|||||||
|
|
||||||
const findCheckboxIndex = (
|
const findCheckboxIndex = (
|
||||||
container: Element,
|
container: Element,
|
||||||
checkbox: HTMLInputElement,
|
checkbox: HTMLInputElement
|
||||||
): number => {
|
): number => {
|
||||||
const allCheckboxes = container.querySelectorAll('input[type="checkbox"]')
|
const allCheckboxes = container.querySelectorAll('input[type="checkbox"]')
|
||||||
return Array.from(allCheckboxes).indexOf(checkbox)
|
return Array.from(allCheckboxes).indexOf(checkbox)
|
||||||
@@ -34,7 +35,7 @@ export const useCheckboxCommit = ({
|
|||||||
initialContent,
|
initialContent,
|
||||||
initialSha,
|
initialSha,
|
||||||
containerSelector,
|
containerSelector,
|
||||||
debounceMs = 1000,
|
debounceMs = 1000
|
||||||
}: {
|
}: {
|
||||||
user: string
|
user: string
|
||||||
repo: string
|
repo: string
|
||||||
@@ -76,7 +77,7 @@ export const useCheckboxCommit = ({
|
|||||||
const newSha = await updateFile({
|
const newSha = await updateFile({
|
||||||
content: pendingContent.value,
|
content: pendingContent.value,
|
||||||
path: pathValue,
|
path: pathValue,
|
||||||
sha: currentSha.value,
|
sha: currentSha.value
|
||||||
})
|
})
|
||||||
|
|
||||||
if (newSha) {
|
if (newSha) {
|
||||||
@@ -109,7 +110,7 @@ export const useCheckboxCommit = ({
|
|||||||
pendingContent.value = setCheckboxInMarkdown(
|
pendingContent.value = setCheckboxInMarkdown(
|
||||||
pendingContent.value,
|
pendingContent.value,
|
||||||
index,
|
index,
|
||||||
target.checked,
|
target.checked
|
||||||
)
|
)
|
||||||
hasPendingChanges.value = true
|
hasPendingChanges.value = true
|
||||||
|
|
||||||
@@ -142,6 +143,6 @@ export const useCheckboxCommit = ({
|
|||||||
isCommitting,
|
isCommitting,
|
||||||
hasPendingChanges,
|
hasPendingChanges,
|
||||||
syncContent,
|
syncContent,
|
||||||
listenToCheckboxes,
|
listenToCheckboxes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { watch } from 'vue'
|
import { watch } from "vue"
|
||||||
|
|
||||||
import { backlinkEventBus } from '@/bus/backlinkEventBus'
|
import { backlinkEventBus } from "@/bus/backlinkEventBus"
|
||||||
import { data } from '@/data/data'
|
import { data } from "@/data/data"
|
||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { useFile } from '@/hooks/useFile.hook'
|
import { useFile } from "@/hooks/useFile.hook"
|
||||||
import { Backlink } from '@/modules/note/models/Backlink'
|
import { Backlink } from "@/modules/note/models/Backlink"
|
||||||
import { BacklinkNote } from '@/modules/note/models/BacklinkNote'
|
import { BacklinkNote } from "@/modules/note/models/BacklinkNote"
|
||||||
import { resolvePath } from '@/modules/repo/services/resolvePath'
|
import { resolvePath } from "@/modules/repo/services/resolvePath"
|
||||||
import { useUserRepoStore } from '@/modules/repo/store/userRepo.store'
|
import { useUserRepoStore } from "@/modules/repo/store/userRepo.store"
|
||||||
import { isExternalLink } from '@/utils/link'
|
import { isExternalLink } from "@/utils/link"
|
||||||
import { filenameToNoteTitle } from '@/utils/noteTitle'
|
import { filenameToNoteTitle } from "@/utils/noteTitle"
|
||||||
import { confirmMessage } from '@/utils/notif'
|
import { confirmMessage } from "@/utils/notif"
|
||||||
|
|
||||||
const isMarkdown = (filename?: string) => filename?.endsWith('.md') ?? false
|
const isMarkdown = (filename?: string) => filename?.endsWith(".md") ?? false
|
||||||
|
|
||||||
export const useComputeBacklinks = () => {
|
export const useComputeBacklinks = () => {
|
||||||
const store = useUserRepoStore()
|
const store = useUserRepoStore()
|
||||||
@@ -51,18 +51,18 @@ export const useComputeBacklinks = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parser = new DOMParser()
|
const parser = new DOMParser()
|
||||||
const htmlDoc = parser.parseFromString(note, 'text/html')
|
const htmlDoc = parser.parseFromString(note, "text/html")
|
||||||
|
|
||||||
const links = htmlDoc.querySelectorAll('a')
|
const links = htmlDoc.querySelectorAll("a")
|
||||||
|
|
||||||
for (const link of links) {
|
for (const link of links) {
|
||||||
const href = link.getAttribute('href') ?? ''
|
const href = link.getAttribute("href") ?? ""
|
||||||
|
|
||||||
if (isExternalLink(href) || !isMarkdown(href)) {
|
if (isExternalLink(href) || !isMarkdown(href)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = resolvePath(file.path ?? '', href)
|
const path = resolvePath(file.path ?? "", href)
|
||||||
const backlinkFile = store.files.find((file) => file.path === path)
|
const backlinkFile = store.files.find((file) => file.path === path)
|
||||||
|
|
||||||
if (!backlinkFile?.sha || !backlinkFile?.path) {
|
if (!backlinkFile?.sha || !backlinkFile?.path) {
|
||||||
@@ -77,14 +77,14 @@ export const useComputeBacklinks = () => {
|
|||||||
|
|
||||||
if (!notifiedForComputation) {
|
if (!notifiedForComputation) {
|
||||||
notifiedForComputation = true
|
notifiedForComputation = true
|
||||||
confirmMessage('Updating backlinks...')
|
confirmMessage("Updating backlinks...")
|
||||||
}
|
}
|
||||||
|
|
||||||
backlinks.set(backlinkFile.sha, [
|
backlinks.set(backlinkFile.sha, [
|
||||||
...previousBacklinks,
|
...previousBacklinks,
|
||||||
{
|
{
|
||||||
sha: file.sha,
|
sha: file.sha,
|
||||||
title: filenameToNoteTitle(file.path ?? '')
|
title: filenameToNoteTitle(file.path ?? "")
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { useMagicKeys } from '@vueuse/core'
|
import { useMagicKeys } from "@vueuse/core"
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from "vue"
|
||||||
|
|
||||||
export const useEditionMode = () => {
|
export const useEditionMode = () => {
|
||||||
const mode = ref<'read' | 'edit'>('read')
|
const mode = ref<"read" | "edit">("read")
|
||||||
const toggleMode = () => {
|
const toggleMode = () => {
|
||||||
mode.value = mode.value === 'read' ? 'edit' : 'read'
|
mode.value = mode.value === "read" ? "edit" : "read"
|
||||||
}
|
}
|
||||||
|
|
||||||
const { escape } = useMagicKeys()
|
const { escape } = useMagicKeys()
|
||||||
|
|
||||||
watch(escape, () => {
|
watch(escape, () => {
|
||||||
if (mode.value === 'edit') {
|
if (mode.value === "edit") {
|
||||||
toggleMode()
|
toggleMode()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,18 +17,18 @@ export const useFile = (sha: Ref<string> | string, retrieveContent = true) => {
|
|||||||
const {
|
const {
|
||||||
render,
|
render,
|
||||||
renderFromUTF8,
|
renderFromUTF8,
|
||||||
getRawContent: getRawContentFromFile,
|
getRawContent: getRawContentFromFile
|
||||||
} = markdownBuilder(shaValue)
|
} = markdownBuilder(shaValue)
|
||||||
|
|
||||||
const { getCachedNote, saveCacheNote } = prepareNoteCache(
|
const { getCachedNote, saveCacheNote } = prepareNoteCache(
|
||||||
shaValue,
|
shaValue,
|
||||||
toValue(path),
|
toValue(path)
|
||||||
)
|
)
|
||||||
|
|
||||||
const fromCache = ref(false)
|
const fromCache = ref(false)
|
||||||
const rawContent = ref("")
|
const rawContent = ref("")
|
||||||
const content = computed(() =>
|
const content = computed(() =>
|
||||||
rawContent.value ? renderFromUTF8(rawContent.value) : "",
|
rawContent.value ? renderFromUTF8(rawContent.value) : ""
|
||||||
)
|
)
|
||||||
|
|
||||||
const getEditedSha = async () => {
|
const getEditedSha = async () => {
|
||||||
@@ -55,7 +55,7 @@ export const useFile = (sha: Ref<string> | string, retrieveContent = true) => {
|
|||||||
}
|
}
|
||||||
saveCacheNote(fileContent)
|
saveCacheNote(fileContent)
|
||||||
rawContent.value = getRawContentFromFile(fileContent)
|
rawContent.value = getRawContentFromFile(fileContent)
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +111,6 @@ export const useFile = (sha: Ref<string> | string, retrieveContent = true) => {
|
|||||||
getCachedFileContent,
|
getCachedFileContent,
|
||||||
getEditedSha,
|
getEditedSha,
|
||||||
fromCache,
|
fromCache,
|
||||||
saveCacheNote,
|
saveCacheNote
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
|
import { computedAsync } from "@vueuse/core"
|
||||||
|
import { computed, Ref, ref, watch } from "vue"
|
||||||
|
|
||||||
import { Author, getAuthors } from "@/modules/atproto/getAuthor"
|
import { Author, getAuthors } from "@/modules/atproto/getAuthor"
|
||||||
import { PublicNoteListItem } from "@/modules/note/models/Note"
|
import { PublicNoteListItem } from "@/modules/note/models/Note"
|
||||||
import { computedAsync } from "@vueuse/core"
|
|
||||||
import { computed, ref, Ref, watch } from "vue"
|
|
||||||
|
|
||||||
export function useFollowingNoteList(
|
export function useFollowingNoteList(
|
||||||
dids: Ref<Set<string>>,
|
dids: Ref<Set<string>>,
|
||||||
enabled: Ref<boolean>,
|
enabled: Ref<boolean>
|
||||||
) {
|
) {
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const notes = ref<PublicNoteListItem[]>([])
|
const notes = ref<PublicNoteListItem[]>([])
|
||||||
const cursor = ref<string | null | undefined>(null)
|
const cursor = ref<string | null | undefined>(null)
|
||||||
const canLoadMore = computed(
|
const canLoadMore = computed(
|
||||||
() => dids.value.size > 0 && cursor.value !== undefined,
|
() => dids.value.size > 0 && cursor.value !== undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
const onLoadMore = async () => {
|
const onLoadMore = async () => {
|
||||||
@@ -22,7 +23,7 @@ export function useFollowingNoteList(
|
|||||||
|
|
||||||
const body: { dids: string[]; limit: number; cursor?: string } = {
|
const body: { dids: string[]; limit: number; cursor?: string } = {
|
||||||
dids: [...dids.value],
|
dids: [...dids.value],
|
||||||
limit: 20,
|
limit: 20
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursor.value) {
|
if (cursor.value) {
|
||||||
@@ -32,7 +33,7 @@ export function useFollowingNoteList(
|
|||||||
const response = await fetch("https://api.remanso.space/notes/feed", {
|
const response = await fetch("https://api.remanso.space/notes/feed", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body)
|
||||||
})
|
})
|
||||||
|
|
||||||
const data: { notes: PublicNoteListItem[]; cursor?: string } =
|
const data: { notes: PublicNoteListItem[]; cursor?: string } =
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Ref, ref, watch } from 'vue'
|
import { Ref, ref, watch } from "vue"
|
||||||
|
|
||||||
import { getFollows } from '@/modules/atproto/service/getFollows'
|
import { getFollows } from "@/modules/atproto/service/getFollows"
|
||||||
|
|
||||||
export const useFollows = (did: Ref<string | null>) => {
|
export const useFollows = (did: Ref<string | null>) => {
|
||||||
const follows = ref<Set<string>>(new Set())
|
const follows = ref<Set<string>>(new Set())
|
||||||
@@ -14,7 +14,7 @@ export const useFollows = (did: Ref<string | null>) => {
|
|||||||
follows.value = new Set()
|
follows.value = new Set()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
return { follows }
|
return { follows }
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from "vue"
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from "vue-router"
|
||||||
|
|
||||||
export const useForm = () => {
|
export const useForm = () => {
|
||||||
const userInput = ref('')
|
const userInput = ref("")
|
||||||
const repoInput = ref('')
|
const repoInput = ref("")
|
||||||
const { push } = useRouter()
|
const { push } = useRouter()
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
@@ -12,7 +12,7 @@ export const useForm = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
push({
|
push({
|
||||||
name: 'FluxNoteView',
|
name: "FluxNoteView",
|
||||||
params: {
|
params: {
|
||||||
user: userInput.value,
|
user: userInput.value,
|
||||||
repo: repoInput.value
|
repo: repoInput.value
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { confirmMessage, errorMessage } from "@/utils/notif"
|
|||||||
|
|
||||||
export const useGitHubContent = ({
|
export const useGitHubContent = ({
|
||||||
user,
|
user,
|
||||||
repo,
|
repo
|
||||||
}: {
|
}: {
|
||||||
user: string
|
user: string
|
||||||
repo: string
|
repo: string
|
||||||
@@ -12,7 +12,7 @@ export const useGitHubContent = ({
|
|||||||
const putFile = async ({
|
const putFile = async ({
|
||||||
content,
|
content,
|
||||||
path,
|
path,
|
||||||
sha,
|
sha
|
||||||
}: {
|
}: {
|
||||||
content: string
|
content: string
|
||||||
path: string
|
path: string
|
||||||
@@ -29,8 +29,8 @@ export const useGitHubContent = ({
|
|||||||
path,
|
path,
|
||||||
message: `Updating ${path} from Remanso`,
|
message: `Updating ${path} from Remanso`,
|
||||||
content: encodeUTF8ToBase64(content),
|
content: encodeUTF8ToBase64(content),
|
||||||
sha,
|
sha
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
confirmMessage("✅ Note saved")
|
confirmMessage("✅ Note saved")
|
||||||
@@ -48,6 +48,6 @@ export const useGitHubContent = ({
|
|||||||
updateFile: async (props: { content: string; path: string; sha: string }) =>
|
updateFile: async (props: { content: string; path: string; sha: string }) =>
|
||||||
putFile(props),
|
putFile(props),
|
||||||
createFile: async (props: { content: string; path: string }) =>
|
createFile: async (props: { content: string; path: string }) =>
|
||||||
putFile(props),
|
putFile(props)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from "vue"
|
||||||
|
|
||||||
import { GithubToken } from '@/modules/user/interfaces/GithubToken'
|
import { GithubToken } from "@/modules/user/interfaces/GithubToken"
|
||||||
import { getAccessToken, saveAccessToken } from '@/modules/user/service/signIn'
|
import { getAccessToken, saveAccessToken } from "@/modules/user/service/signIn"
|
||||||
import { confirmMessage } from '@/utils/notif'
|
import { confirmMessage } from "@/utils/notif"
|
||||||
|
|
||||||
const username = ref<string | null>(null)
|
const username = ref<string | null>(null)
|
||||||
const accessToken = ref<string | null>(null)
|
const accessToken = ref<string | null>(null)
|
||||||
@@ -11,8 +11,8 @@ let init = true
|
|||||||
|
|
||||||
const saveAccessTokenToLocal = async () => {
|
const saveAccessTokenToLocal = async () => {
|
||||||
const response = await getAccessToken()
|
const response = await getAccessToken()
|
||||||
username.value = response?.username || ''
|
username.value = response?.username || ""
|
||||||
accessToken.value = response?.token || ''
|
accessToken.value = response?.token || ""
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveCredentials = async (token: GithubToken): Promise<void> => {
|
const saveCredentials = async (token: GithubToken): Promise<void> => {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { computed, watch } from 'vue'
|
import { computed, watch } from "vue"
|
||||||
|
|
||||||
import { useFile } from '@/hooks/useFile.hook'
|
import { useFile } from "@/hooks/useFile.hook"
|
||||||
import { resolvePath } from '@/modules/repo/services/resolvePath'
|
import { resolvePath } from "@/modules/repo/services/resolvePath"
|
||||||
import { useUserRepoStore } from '@/modules/repo/store/userRepo.store'
|
import { useUserRepoStore } from "@/modules/repo/store/userRepo.store"
|
||||||
|
|
||||||
const SRC_PREFIX = 'data:image/jpeg;charset=utf-8;base64,'
|
const SRC_PREFIX = "data:image/jpeg;charset=utf-8;base64,"
|
||||||
|
|
||||||
export const useImages = (sha: string) => {
|
export const useImages = (sha: string) => {
|
||||||
const store = useUserRepoStore()
|
const store = useUserRepoStore()
|
||||||
@@ -23,14 +23,14 @@ export const useImages = (sha: string) => {
|
|||||||
const images = document.querySelectorAll(`.note-${sha} img`)
|
const images = document.querySelectorAll(`.note-${sha} img`)
|
||||||
|
|
||||||
images.forEach(async (image) => {
|
images.forEach(async (image) => {
|
||||||
const src = image.getAttribute('src')
|
const src = image.getAttribute("src")
|
||||||
if (!src || src.startsWith(SRC_PREFIX)) {
|
if (!src || src.startsWith(SRC_PREFIX)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageFilePath = resolvePath(
|
const imageFilePath = resolvePath(
|
||||||
filePath,
|
filePath,
|
||||||
image.getAttribute('src') ?? ''
|
image.getAttribute("src") ?? ""
|
||||||
)
|
)
|
||||||
|
|
||||||
const imageFile = store.files.find(
|
const imageFile = store.files.find(
|
||||||
@@ -43,7 +43,7 @@ export const useImages = (sha: string) => {
|
|||||||
const { getCachedFileContent } = useFile(imageFile.sha, false)
|
const { getCachedFileContent } = useFile(imageFile.sha, false)
|
||||||
|
|
||||||
const fileContent = await getCachedFileContent()
|
const fileContent = await getCachedFileContent()
|
||||||
image.setAttribute('src', `${SRC_PREFIX} ${fileContent}`)
|
image.setAttribute("src", `${SRC_PREFIX} ${fileContent}`)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { isExternalLink } from "@/utils/link"
|
|||||||
|
|
||||||
export const useLinks = (
|
export const useLinks = (
|
||||||
className: ComputedRef<string> | string,
|
className: ComputedRef<string> | string,
|
||||||
sha?: Ref<string> | string,
|
sha?: Ref<string> | string
|
||||||
) => {
|
) => {
|
||||||
const store = useUserRepoStore()
|
const store = useUserRepoStore()
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ export const useLinks = (
|
|||||||
path: href,
|
path: href,
|
||||||
currentNoteSHA: toValue(sha),
|
currentNoteSHA: toValue(sha),
|
||||||
user: store.user,
|
user: store.user,
|
||||||
repo: store.repo,
|
repo: store.repo
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +74,6 @@ export const useLinks = (
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listenToClick,
|
listenToClick
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import markdownItKatex from "@vscode/markdown-it-katex"
|
import markdownItKatex from "@vscode/markdown-it-katex"
|
||||||
import MarkdownIt, { Options } from "markdown-it"
|
import MarkdownIt, { Options } from "markdown-it"
|
||||||
|
import Renderer, { type RenderRuleRecord } from "markdown-it/lib/renderer.mjs"
|
||||||
|
import type Token from "markdown-it/lib/token.mjs"
|
||||||
import blockEmbedPlugin from "markdown-it-block-embed"
|
import blockEmbedPlugin from "markdown-it-block-embed"
|
||||||
import markdownItCheckbox from "markdown-it-checkbox"
|
import markdownItCheckbox from "markdown-it-checkbox"
|
||||||
import MarkdownItGitHubAlerts from "markdown-it-github-alerts"
|
import MarkdownItGitHubAlerts from "markdown-it-github-alerts"
|
||||||
import markdownItIframe from "markdown-it-iframe"
|
import markdownItIframe from "markdown-it-iframe"
|
||||||
import Shikiji from "markdown-it-shikiji"
|
import Shikiji from "markdown-it-shikiji"
|
||||||
|
import mermaid from "mermaid"
|
||||||
import { Ref, toValue } from "vue"
|
import { Ref, toValue } from "vue"
|
||||||
|
|
||||||
import { decodeBase64ToUTF8 } from "@/utils/decodeBase64ToUTF8"
|
import { decodeBase64ToUTF8 } from "@/utils/decodeBase64ToUTF8"
|
||||||
import { html5Media } from "@/utils/markdown/markdown-html5-media"
|
import { html5Media } from "@/utils/markdown/markdown-html5-media"
|
||||||
import { markdownItTablerIcons } from "@/utils/markdown/markdown-it-tabler-icons"
|
import { markdownItTablerIcons } from "@/utils/markdown/markdown-it-tabler-icons"
|
||||||
import mermaid from "mermaid"
|
|
||||||
import type Token from "markdown-it/lib/token.mjs"
|
|
||||||
import Renderer, { type RenderRuleRecord } from "markdown-it/lib/renderer.mjs"
|
|
||||||
|
|
||||||
const markdownItMermaidExtractor = (md: MarkdownIt) => {
|
const markdownItMermaidExtractor = (md: MarkdownIt) => {
|
||||||
const defaultFence =
|
const defaultFence =
|
||||||
@@ -22,7 +22,7 @@ const markdownItMermaidExtractor = (md: MarkdownIt) => {
|
|||||||
index: number,
|
index: number,
|
||||||
options: Options,
|
options: Options,
|
||||||
_: unknown,
|
_: unknown,
|
||||||
self: Renderer,
|
self: Renderer
|
||||||
) {
|
) {
|
||||||
return self.renderToken(tokens, index, options)
|
return self.renderToken(tokens, index, options)
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ const markdownItMermaidExtractor = (md: MarkdownIt) => {
|
|||||||
index: number,
|
index: number,
|
||||||
options: Options,
|
options: Options,
|
||||||
env: unknown,
|
env: unknown,
|
||||||
self: Renderer,
|
self: Renderer
|
||||||
) {
|
) {
|
||||||
const token = tokens[index]
|
const token = tokens[index]
|
||||||
|
|
||||||
@@ -47,20 +47,20 @@ const markdownItMermaidExtractor = (md: MarkdownIt) => {
|
|||||||
|
|
||||||
const md = new MarkdownIt({
|
const md = new MarkdownIt({
|
||||||
typographer: true,
|
typographer: true,
|
||||||
quotes: ["«\xA0", "\xA0»", "‹\xA0", "\xA0›"],
|
quotes: ["«\xA0", "\xA0»", "‹\xA0", "\xA0›"]
|
||||||
})
|
})
|
||||||
.use(markdownItMermaidExtractor)
|
.use(markdownItMermaidExtractor)
|
||||||
.use(html5Media)
|
.use(html5Media)
|
||||||
.use(blockEmbedPlugin, {
|
.use(blockEmbedPlugin, {
|
||||||
youtube: {
|
youtube: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: 300,
|
height: 300
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
.use(markdownItCheckbox)
|
.use(markdownItCheckbox)
|
||||||
.use(markdownItKatex)
|
.use(markdownItKatex)
|
||||||
.use(markdownItIframe, {
|
.use(markdownItIframe, {
|
||||||
width: "100%",
|
width: "100%"
|
||||||
})
|
})
|
||||||
.use(MarkdownItGitHubAlerts)
|
.use(MarkdownItGitHubAlerts)
|
||||||
.use(markdownItTablerIcons)
|
.use(markdownItTablerIcons)
|
||||||
@@ -77,7 +77,7 @@ export const useShikiji = async () => {
|
|||||||
await Shikiji({
|
await Shikiji({
|
||||||
themes: {
|
themes: {
|
||||||
light: "vitesse-light",
|
light: "vitesse-light",
|
||||||
dark: "vitesse-black",
|
dark: "vitesse-black"
|
||||||
},
|
},
|
||||||
langs: [
|
langs: [
|
||||||
"bash",
|
"bash",
|
||||||
@@ -87,9 +87,9 @@ export const useShikiji = async () => {
|
|||||||
"mermaid",
|
"mermaid",
|
||||||
"html",
|
"html",
|
||||||
"css",
|
"css",
|
||||||
"json",
|
"json"
|
||||||
],
|
]
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,19 +101,19 @@ export const runMermaid = (querySelector: string) => {
|
|||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
theme: "dark",
|
theme: "dark",
|
||||||
startOnLoad: false,
|
startOnLoad: false,
|
||||||
flowchart: { curve: "natural" },
|
flowchart: { curve: "natural" }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
mermaid.run({
|
mermaid.run({
|
||||||
querySelector,
|
querySelector
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const rules: RenderRuleRecord = {
|
const rules: RenderRuleRecord = {
|
||||||
table_open: () =>
|
table_open: () =>
|
||||||
'<div class="overflow-x-auto"><table class="table table-zebra">',
|
'<div class="overflow-x-auto"><table class="table table-zebra">',
|
||||||
table_close: () => "</table></div>",
|
table_close: () => "</table></div>"
|
||||||
}
|
}
|
||||||
|
|
||||||
md.renderer.rules = { ...md.renderer.rules, ...rules }
|
md.renderer.rules = { ...md.renderer.rules, ...rules }
|
||||||
@@ -128,7 +128,7 @@ export const markdownBuilder = (defaultPrefix?: Ref<string> | string) => {
|
|||||||
const renderFromUTF8 = (content: string, prefix?: string) => {
|
const renderFromUTF8 = (content: string, prefix?: string) => {
|
||||||
return content
|
return content
|
||||||
? md.render(stripFrontmatter(content), {
|
? md.render(stripFrontmatter(content), {
|
||||||
docId: defaultPrefix ? toValue(defaultPrefix) : (prefix ?? ""),
|
docId: defaultPrefix ? toValue(defaultPrefix) : (prefix ?? "")
|
||||||
})
|
})
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
@@ -139,6 +139,6 @@ export const markdownBuilder = (defaultPrefix?: Ref<string> | string) => {
|
|||||||
render: (content: string, prefix?: string) =>
|
render: (content: string, prefix?: string) =>
|
||||||
renderFromUTF8(decodeBase64ToUTF8(content), prefix),
|
renderFromUTF8(decodeBase64ToUTF8(content), prefix),
|
||||||
renderFromUTF8,
|
renderFromUTF8,
|
||||||
getRawContent,
|
getRawContent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import { computed, onMounted, Ref, ref, toValue } from "vue"
|
import { computed, onMounted, Ref, ref, toValue } from "vue"
|
||||||
|
|
||||||
import { BOOKMARK_WIDTH_REM, getBookmarkWidthPx } from "@/constants/bookmark-width"
|
import {
|
||||||
|
BOOKMARK_WIDTH_REM,
|
||||||
|
getBookmarkWidthPx
|
||||||
|
} from "@/constants/bookmark-width"
|
||||||
import { getNoteWidth } from "@/constants/note-width"
|
import { getNoteWidth } from "@/constants/note-width"
|
||||||
import { useOverlay } from "@/hooks/useOverlay.hook"
|
import { useOverlay } from "@/hooks/useOverlay.hook"
|
||||||
import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook"
|
import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook"
|
||||||
|
|
||||||
export const useNoteOverlay = (
|
export const useNoteOverlay = (
|
||||||
className: string,
|
className: string,
|
||||||
index: Ref<number> | number,
|
index: Ref<number> | number
|
||||||
) => {
|
) => {
|
||||||
const { x, y, isMobile } = useOverlay()
|
const { x, y, isMobile } = useOverlay()
|
||||||
const noteHeight = ref(0)
|
const noteHeight = ref(0)
|
||||||
@@ -18,14 +21,17 @@ export const useNoteOverlay = (
|
|||||||
if (isMobile.value) {
|
if (isMobile.value) {
|
||||||
return y.value > valueIndex * noteHeight.value
|
return y.value > valueIndex * noteHeight.value
|
||||||
} else {
|
} else {
|
||||||
return x.value > valueIndex * getNoteWidth() - valueIndex * getBookmarkWidthPx()
|
return (
|
||||||
|
x.value >
|
||||||
|
valueIndex * getNoteWidth() - valueIndex * getBookmarkWidthPx()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const { stackedNotes } = useRouteQueryStackedNotes()
|
const { stackedNotes } = useRouteQueryStackedNotes()
|
||||||
const noteElement = document.querySelector(
|
const noteElement = document.querySelector(
|
||||||
`.${className}`,
|
`.${className}`
|
||||||
) satisfies HTMLElement | null
|
) satisfies HTMLElement | null
|
||||||
|
|
||||||
if (!noteElement) {
|
if (!noteElement) {
|
||||||
@@ -40,7 +46,7 @@ export const useNoteOverlay = (
|
|||||||
noteElement.style.left = `${(toValue(index) + 1) * BOOKMARK_WIDTH_REM}rem`
|
noteElement.style.left = `${(toValue(index) + 1) * BOOKMARK_WIDTH_REM}rem`
|
||||||
|
|
||||||
const stackedNoteContainers = document.querySelectorAll(
|
const stackedNoteContainers = document.querySelectorAll(
|
||||||
".stacked-note",
|
".stacked-note"
|
||||||
) satisfies NodeListOf<HTMLElement>
|
) satisfies NodeListOf<HTMLElement>
|
||||||
|
|
||||||
stackedNoteContainers.forEach((stackedNote, ind) => {
|
stackedNoteContainers.forEach((stackedNote, ind) => {
|
||||||
@@ -52,6 +58,6 @@ export const useNoteOverlay = (
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
displayNoteOverlay,
|
displayNoteOverlay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ export const useNoteView = () => {
|
|||||||
obj[note] = pathToNotePathTitle(filePath)
|
obj[note] = pathToNotePathTitle(filePath)
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
}, {}),
|
}, {})
|
||||||
)
|
)
|
||||||
|
|
||||||
const unsubscribeLink = noteEventBus.addEventBusListener(
|
const unsubscribeLink = noteEventBus.addEventBusListener(
|
||||||
({ path, currentNoteSHA }) => {
|
({ path, currentNoteSHA }) => {
|
||||||
const currentFile = store.files.find(
|
const currentFile = store.files.find(
|
||||||
(file) => file.sha === currentNoteSHA,
|
(file) => file.sha === currentNoteSHA
|
||||||
)
|
)
|
||||||
|
|
||||||
const absolutePath = resolvePath(currentFile?.path ?? "", path)
|
const absolutePath = resolvePath(currentFile?.path ?? "", path)
|
||||||
@@ -39,7 +39,7 @@ export const useNoteView = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addStackedNote(currentNoteSHA ?? "", file.sha)
|
addStackedNote(currentNoteSHA ?? "", file.sha)
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -47,6 +47,6 @@ export const useNoteView = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
titles,
|
titles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useAsyncState } from '@vueuse/core'
|
import { useAsyncState } from "@vueuse/core"
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from "vue"
|
||||||
|
|
||||||
import { data } from '@/data/data'
|
import { data } from "@/data/data"
|
||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { prepareNoteCache } from '@/modules/note/cache/prepareNoteCache'
|
import { prepareNoteCache } from "@/modules/note/cache/prepareNoteCache"
|
||||||
import { Note } from '@/modules/note/models/Note'
|
import { Note } from "@/modules/note/models/Note"
|
||||||
import { queryFileContent } from '@/modules/repo/services/repo'
|
import { queryFileContent } from "@/modules/repo/services/repo"
|
||||||
import { useUserRepoStore } from '@/modules/repo/store/userRepo.store'
|
import { useUserRepoStore } from "@/modules/repo/store/userRepo.store"
|
||||||
|
|
||||||
export const useOfflineNotes = () => {
|
export const useOfflineNotes = () => {
|
||||||
const store = useUserRepoStore()
|
const store = useUserRepoStore()
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ export const useOverlay = (listen = true) => {
|
|||||||
}
|
}
|
||||||
useEventListener(window, "scroll", updateScroll, {
|
useEventListener(window, "scroll", updateScroll, {
|
||||||
passive: true,
|
passive: true,
|
||||||
capture: false,
|
capture: false
|
||||||
})
|
})
|
||||||
useEventListener(document.body, "scroll", updateScroll, {
|
useEventListener(document.body, "scroll", updateScroll, {
|
||||||
passive: true,
|
passive: true,
|
||||||
capture: false,
|
capture: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +47,6 @@ export const useOverlay = (listen = true) => {
|
|||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
isMobile,
|
isMobile,
|
||||||
scrollToNote,
|
scrollToNote
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import { computedAsync } from "@vueuse/core"
|
||||||
|
import { computed, Ref, ref } from "vue"
|
||||||
|
|
||||||
import { Author, getAuthors } from "@/modules/atproto/getAuthor"
|
import { Author, getAuthors } from "@/modules/atproto/getAuthor"
|
||||||
import { PublicNoteListItem } from "@/modules/note/models/Note"
|
import { PublicNoteListItem } from "@/modules/note/models/Note"
|
||||||
import { computedAsync } from "@vueuse/core"
|
|
||||||
import { computed, ref, Ref } from "vue"
|
|
||||||
|
|
||||||
interface UsePublicNoteListOptions {
|
interface UsePublicNoteListOptions {
|
||||||
did?: Ref<string | undefined>
|
did?: Ref<string | undefined>
|
||||||
@@ -50,6 +51,6 @@ export function usePublicNoteList(options?: UsePublicNoteListOptions) {
|
|||||||
canLoadMore,
|
canLoadMore,
|
||||||
onLoadMore,
|
onLoadMore,
|
||||||
authors,
|
authors,
|
||||||
getAuthor,
|
getAuthor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useAsyncState } from '@vueuse/core'
|
import { useAsyncState } from "@vueuse/core"
|
||||||
|
|
||||||
import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
|
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
|
||||||
import { RepoBase } from '@/modules/repo/interfaces/RepoBase'
|
import { RepoBase } from "@/modules/repo/interfaces/RepoBase"
|
||||||
import { getOctokit } from '@/modules/repo/services/octo'
|
import { getOctokit } from "@/modules/repo/services/octo"
|
||||||
|
|
||||||
export const useRepos = () => {
|
export const useRepos = () => {
|
||||||
const { username, accessToken } = useGitHubLogin()
|
const { username, accessToken } = useGitHubLogin()
|
||||||
@@ -13,7 +13,7 @@ export const useRepos = () => {
|
|||||||
|
|
||||||
const octokit = await getOctokit()
|
const octokit = await getOctokit()
|
||||||
|
|
||||||
const repoList = await octokit.request('GET /search/repositories', {
|
const repoList = await octokit.request("GET /search/repositories", {
|
||||||
q: `user:${username.value}`,
|
q: `user:${username.value}`,
|
||||||
per_page: 100
|
per_page: 100
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { onMounted, watch, type Ref } from "vue"
|
import { onMounted, type Ref, watch } from "vue"
|
||||||
|
|
||||||
import { getNoteWidth } from "@/constants/note-width"
|
import { getNoteWidth } from "@/constants/note-width"
|
||||||
import { useOverlay } from "@/hooks/useOverlay.hook"
|
import { useOverlay } from "@/hooks/useOverlay.hook"
|
||||||
|
|
||||||
export const useResizeContainer = (
|
export const useResizeContainer = (
|
||||||
containerClass: string,
|
containerClass: string,
|
||||||
stackedNotes: Readonly<Ref<readonly string[]>>,
|
stackedNotes: Readonly<Ref<readonly string[]>>
|
||||||
) => {
|
) => {
|
||||||
const { isMobile } = useOverlay(false)
|
const { isMobile } = useOverlay(false)
|
||||||
|
|
||||||
const resizeContainer = () => {
|
const resizeContainer = () => {
|
||||||
const container = document.querySelector(
|
const container = document.querySelector(
|
||||||
`.${containerClass}`,
|
`.${containerClass}`
|
||||||
) as HTMLElement | null
|
) as HTMLElement | null
|
||||||
|
|
||||||
if (!container) {
|
if (!container) {
|
||||||
@@ -32,6 +32,6 @@ export const useResizeContainer = (
|
|||||||
})
|
})
|
||||||
|
|
||||||
watch(stackedNotes, resizeContainer, {
|
watch(stackedNotes, resizeContainer, {
|
||||||
immediate: true,
|
immediate: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const useRouteQueryStackedNotes = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Array.isArray(value) ? value : [value]
|
return Array.isArray(value) ? value : [value]
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
const { height } = useWindowSize()
|
const { height } = useWindowSize()
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ export const useRouteQueryStackedNotes = () => {
|
|||||||
|
|
||||||
const scrollToFocusedNote = (
|
const scrollToFocusedNote = (
|
||||||
noteId: string | null = null,
|
noteId: string | null = null,
|
||||||
notes: string[] = stackedNotes.value,
|
notes: string[] = stackedNotes.value
|
||||||
) => {
|
) => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const index = noteId ? notes.findIndex((nid) => nid === noteId) : 0
|
const index = noteId ? notes.findIndex((nid) => nid === noteId) : 0
|
||||||
@@ -31,7 +31,7 @@ export const useRouteQueryStackedNotes = () => {
|
|||||||
if (noteId) {
|
if (noteId) {
|
||||||
const cleanNoteId = noteId.replaceAll(":", "-")
|
const cleanNoteId = noteId.replaceAll(":", "-")
|
||||||
const element = document.querySelector(
|
const element = document.querySelector(
|
||||||
`.note-${cleanNoteId}`,
|
`.note-${cleanNoteId}`
|
||||||
) as HTMLElement
|
) as HTMLElement
|
||||||
|
|
||||||
const top = (index + 1) * (element?.clientHeight ?? height.value)
|
const top = (index + 1) * (element?.clientHeight ?? height.value)
|
||||||
@@ -53,7 +53,7 @@ export const useRouteQueryStackedNotes = () => {
|
|||||||
const addStackedNote = (
|
const addStackedNote = (
|
||||||
currentSha: string,
|
currentSha: string,
|
||||||
sha: string,
|
sha: string,
|
||||||
selector?: string,
|
selector?: string
|
||||||
) => {
|
) => {
|
||||||
if (stackedNotes.value.includes(sha)) {
|
if (stackedNotes.value.includes(sha)) {
|
||||||
scrollToFocusedNote(selector ?? sha)
|
scrollToFocusedNote(selector ?? sha)
|
||||||
@@ -70,7 +70,7 @@ export const useRouteQueryStackedNotes = () => {
|
|||||||
const newStackedNotes = [
|
const newStackedNotes = [
|
||||||
...splittedStackedNotes.replaceAll(";;", ";").split(";"),
|
...splittedStackedNotes.replaceAll(";;", ";").split(";"),
|
||||||
currentSha,
|
currentSha,
|
||||||
sha,
|
sha
|
||||||
].filter((sha) => !!sha)
|
].filter((sha) => !!sha)
|
||||||
|
|
||||||
stackedNotes.value = newStackedNotes
|
stackedNotes.value = newStackedNotes
|
||||||
@@ -82,6 +82,6 @@ export const useRouteQueryStackedNotes = () => {
|
|||||||
return {
|
return {
|
||||||
stackedNotes: readonly(stackedNotes),
|
stackedNotes: readonly(stackedNotes),
|
||||||
addStackedNote,
|
addStackedNote,
|
||||||
scrollToFocusedNote,
|
scrollToFocusedNote
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { useTitle } from '@vueuse/core'
|
import { useTitle } from "@vueuse/core"
|
||||||
import { computed, Ref, toValue, watch } from 'vue'
|
import { computed, Ref, toValue, watch } from "vue"
|
||||||
|
|
||||||
import { useRouteQueryStackedNotes } from '@/hooks/useRouteQueryStackedNotes.hook'
|
import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook"
|
||||||
import { useNotes } from '@/modules/note/hooks/useNotes'
|
import { useNotes } from "@/modules/note/hooks/useNotes"
|
||||||
import { pathToNoteTitle } from '@/utils/noteTitle'
|
import { pathToNoteTitle } from "@/utils/noteTitle"
|
||||||
|
|
||||||
export const generateTitle = (titles: string[]) => titles.join(' | ')
|
export const generateTitle = (titles: string[]) => titles.join(" | ")
|
||||||
|
|
||||||
export const useTitleNotes = (prefix: Ref<string> | string) => {
|
export const useTitleNotes = (prefix: Ref<string> | string) => {
|
||||||
const { stackedNotes } = useRouteQueryStackedNotes()
|
const { stackedNotes } = useRouteQueryStackedNotes()
|
||||||
const { notes } = useNotes()
|
const { notes } = useNotes()
|
||||||
const titleNotes = computed(() =>
|
const titleNotes = computed(() =>
|
||||||
notes.value
|
notes.value
|
||||||
.filter((note) => stackedNotes.value.includes(note.sha ?? ''))
|
.filter((note) => stackedNotes.value.includes(note.sha ?? ""))
|
||||||
.map((note) => pathToNoteTitle(note.path ?? ''))
|
.map((note) => pathToNoteTitle(note.path ?? ""))
|
||||||
)
|
)
|
||||||
|
|
||||||
const title = useTitle(generateTitle([toValue(prefix), ...titleNotes.value]))
|
const title = useTitle(generateTitle([toValue(prefix), ...titleNotes.value]))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import en from './en.json'
|
import en from "./en.json"
|
||||||
import fr from './fr.json'
|
import fr from "./fr.json"
|
||||||
|
|
||||||
export const messages = {
|
export const messages = {
|
||||||
en,
|
en,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import "notyf/notyf.min.css"
|
import "notyf/notyf.min.css"
|
||||||
import "./styles/app.css"
|
import "./styles/app.css"
|
||||||
|
import "@/analytics/openpanel"
|
||||||
|
|
||||||
import { VueQueryPlugin } from "@tanstack/vue-query"
|
import { VueQueryPlugin } from "@tanstack/vue-query"
|
||||||
import { createPinia } from "pinia"
|
import { createPinia } from "pinia"
|
||||||
@@ -10,11 +11,10 @@ import { messages } from "@/locales/message"
|
|||||||
import { router } from "@/router/router"
|
import { router } from "@/router/router"
|
||||||
|
|
||||||
import App from "./App.vue"
|
import App from "./App.vue"
|
||||||
import "@/analytics/openpanel"
|
|
||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
locale: "en",
|
locale: "en",
|
||||||
messages,
|
messages
|
||||||
})
|
})
|
||||||
|
|
||||||
createApp(App)
|
createApp(App)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createSchema, createFetch } from "@better-fetch/fetch"
|
import { createFetch, createSchema } from "@better-fetch/fetch"
|
||||||
import { type } from "arktype"
|
import { type } from "arktype"
|
||||||
|
|
||||||
export type Author = { handle: string; pds: string }
|
export type Author = { handle: string; pds: string }
|
||||||
@@ -12,20 +12,20 @@ const schema = createSchema(
|
|||||||
did: "string",
|
did: "string",
|
||||||
handle: "string",
|
handle: "string",
|
||||||
pds: "string",
|
pds: "string",
|
||||||
signing_key: "string",
|
signing_key: "string"
|
||||||
}),
|
}),
|
||||||
query: type({
|
query: type({
|
||||||
identifier: "string",
|
identifier: "string"
|
||||||
}),
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
{ strict: true }
|
||||||
{ strict: true },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const microcosmSlingshot = createFetch({
|
const microcosmSlingshot = createFetch({
|
||||||
baseURL: "https://slingshot.microcosm.blue",
|
baseURL: "https://slingshot.microcosm.blue",
|
||||||
// plugins: [logger()],
|
// plugins: [logger()],
|
||||||
schema,
|
schema
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getAuthor = async (did: string): Promise<Author | null> => {
|
export const getAuthor = async (did: string): Promise<Author | null> => {
|
||||||
@@ -36,7 +36,7 @@ export const getAuthor = async (did: string): Promise<Author | null> => {
|
|||||||
try {
|
try {
|
||||||
const { data: author } = await microcosmSlingshot(
|
const { data: author } = await microcosmSlingshot(
|
||||||
"/xrpc/blue.microcosm.identity.resolveMiniDoc",
|
"/xrpc/blue.microcosm.identity.resolveMiniDoc",
|
||||||
{ query: { identifier: did } },
|
{ query: { identifier: did } }
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!author) {
|
if (!author) {
|
||||||
@@ -62,7 +62,7 @@ export const getAuthors = async (dids: Set<string>) => {
|
|||||||
|
|
||||||
const { data: author } = await microcosmSlingshot(
|
const { data: author } = await microcosmSlingshot(
|
||||||
"/xrpc/blue.microcosm.identity.resolveMiniDoc",
|
"/xrpc/blue.microcosm.identity.resolveMiniDoc",
|
||||||
{ query: { identifier: did } },
|
{ query: { identifier: did } }
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!author) {
|
if (!author) {
|
||||||
@@ -72,7 +72,7 @@ export const getAuthors = async (dids: Set<string>) => {
|
|||||||
correspondanceCache.set(did, author)
|
correspondanceCache.set(did, author)
|
||||||
|
|
||||||
return [did, author] as [string, Author | null]
|
return [did, author] as [string, Author | null]
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
return new Map(correspondance)
|
return new Map(correspondance)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
BrowserOAuthClient,
|
BrowserOAuthClient,
|
||||||
buildLoopbackClientId,
|
buildLoopbackClientId
|
||||||
} from "@atproto/oauth-client-browser"
|
} from "@atproto/oauth-client-browser"
|
||||||
|
|
||||||
const getClientId = () =>
|
const getClientId = () =>
|
||||||
@@ -14,7 +14,7 @@ export const getOAuthClient = (): Promise<BrowserOAuthClient> => {
|
|||||||
if (!clientPromise) {
|
if (!clientPromise) {
|
||||||
clientPromise = BrowserOAuthClient.load({
|
clientPromise = BrowserOAuthClient.load({
|
||||||
clientId: getClientId(),
|
clientId: getClientId(),
|
||||||
handleResolver: "https://bsky.social",
|
handleResolver: "https://bsky.social"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return clientPromise
|
return clientPromise
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { data } from '@/data/data'
|
import { data } from "@/data/data"
|
||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { AtprotoSession } from '@/data/models/AtprotoSession'
|
import { AtprotoSession } from "@/data/models/AtprotoSession"
|
||||||
|
|
||||||
const SESSION_ID = `${DataType.AtprotoSession}-current`
|
const SESSION_ID = `${DataType.AtprotoSession}-current`
|
||||||
|
|
||||||
@@ -8,12 +8,15 @@ export const loadSession = (): Promise<AtprotoSession | null> => {
|
|||||||
return data.get<DataType.AtprotoSession, AtprotoSession>(SESSION_ID)
|
return data.get<DataType.AtprotoSession, AtprotoSession>(SESSION_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const saveSession = async (did: string, handle: string): Promise<void> => {
|
export const saveSession = async (
|
||||||
|
did: string,
|
||||||
|
handle: string
|
||||||
|
): Promise<void> => {
|
||||||
const session: AtprotoSession = {
|
const session: AtprotoSession = {
|
||||||
_id: SESSION_ID,
|
_id: SESSION_ID,
|
||||||
$type: DataType.AtprotoSession,
|
$type: DataType.AtprotoSession,
|
||||||
did,
|
did,
|
||||||
handle,
|
handle
|
||||||
}
|
}
|
||||||
await data.update<DataType.AtprotoSession, AtprotoSession>(session)
|
await data.update<DataType.AtprotoSession, AtprotoSession>(session)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,18 @@ export const getFollows = async (did: string): Promise<Set<string>> => {
|
|||||||
let cursor: string | undefined
|
let cursor: string | undefined
|
||||||
|
|
||||||
do {
|
do {
|
||||||
const url = new URL('https://public.api.bsky.app/xrpc/app.bsky.graph.getFollows')
|
const url = new URL(
|
||||||
url.searchParams.set('actor', did)
|
"https://public.api.bsky.app/xrpc/app.bsky.graph.getFollows"
|
||||||
url.searchParams.set('limit', '100')
|
)
|
||||||
|
url.searchParams.set("actor", did)
|
||||||
|
url.searchParams.set("limit", "100")
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
url.searchParams.set('cursor', cursor)
|
url.searchParams.set("cursor", cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
const result: { follows: { did: string }[]; cursor?: string } = await response.json()
|
const result: { follows: { did: string }[]; cursor?: string } =
|
||||||
|
await response.json()
|
||||||
|
|
||||||
for (const follow of result.follows) {
|
for (const follow of result.follows) {
|
||||||
follows.add(follow.did)
|
follows.add(follow.did)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export const withATProtoImages = (
|
export const withATProtoImages = (
|
||||||
markdown: string,
|
markdown: string,
|
||||||
{ pds, did }: { pds: string; did: string },
|
{ pds, did }: { pds: string; did: string }
|
||||||
): string => {
|
): string => {
|
||||||
const imageLinkPattern = /!\[([^\]]*)\]\((bafkrei[a-z0-9]+)\)/g
|
const imageLinkPattern = /!\[([^\]]*)\]\((bafkrei[a-z0-9]+)\)/g
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from "vue"
|
||||||
|
|
||||||
import FlipCard from '@/modules/card/components/FlipCard.vue'
|
import FlipCard from "@/modules/card/components/FlipCard.vue"
|
||||||
import { Repetition } from '@/modules/card/hooks/useSpacedRepetitionCards'
|
import { Repetition } from "@/modules/card/hooks/useSpacedRepetitionCards"
|
||||||
|
|
||||||
const props = defineProps<{ cards: Repetition[] }>()
|
const props = defineProps<{ cards: Repetition[] }>()
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
@@ -22,24 +22,24 @@ const sortedCards = ref(
|
|||||||
const currentIndex = ref(0)
|
const currentIndex = ref(0)
|
||||||
|
|
||||||
const goToNextCard = (success: boolean) => {
|
const goToNextCard = (success: boolean) => {
|
||||||
const id = sortedCards.value[currentIndex.value].repetition._id ?? ''
|
const id = sortedCards.value[currentIndex.value].repetition._id ?? ""
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
emits('success', id)
|
emits("success", id)
|
||||||
} else {
|
} else {
|
||||||
const failedCard = sortedCards.value.at(currentIndex.value)
|
const failedCard = sortedCards.value.at(currentIndex.value)
|
||||||
if (failedCard) {
|
if (failedCard) {
|
||||||
sortedCards.value.push(failedCard)
|
sortedCards.value.push(failedCard)
|
||||||
}
|
}
|
||||||
emits('fail', id)
|
emits("fail", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentIndex.value++
|
currentIndex.value++
|
||||||
}
|
}
|
||||||
|
|
||||||
const needsReview = () => {
|
const needsReview = () => {
|
||||||
const id = sortedCards.value[currentIndex.value].repetition._id ?? ''
|
const id = sortedCards.value[currentIndex.value].repetition._id ?? ""
|
||||||
emits('needsReview', id)
|
emits("needsReview", id)
|
||||||
currentIndex.value++
|
currentIndex.value++
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useAsyncState } from '@vueuse/core'
|
import { useAsyncState } from "@vueuse/core"
|
||||||
|
|
||||||
import { data } from '@/data/data'
|
import { data } from "@/data/data"
|
||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { RepetitionCard } from '@/modules/card/models/RepetitionCard'
|
import { RepetitionCard } from "@/modules/card/models/RepetitionCard"
|
||||||
|
|
||||||
export const useNeedReviewCards = () => {
|
export const useNeedReviewCards = () => {
|
||||||
const { state: cardsToReview, isReady } = useAsyncState(async () => {
|
const { state: cardsToReview, isReady } = useAsyncState(async () => {
|
||||||
|
|||||||
@@ -31,14 +31,14 @@ export const useSpacedRepetitionCards = () => {
|
|||||||
(file) =>
|
(file) =>
|
||||||
file.path !== undefined &&
|
file.path !== undefined &&
|
||||||
file.path.startsWith("_cards") &&
|
file.path.startsWith("_cards") &&
|
||||||
file.path.endsWith(".md"),
|
file.path.endsWith(".md")
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
state: cards,
|
state: cards,
|
||||||
isReady,
|
isReady,
|
||||||
execute,
|
execute
|
||||||
} = useAsyncState(
|
} = useAsyncState(
|
||||||
async () => {
|
async () => {
|
||||||
const cards: Repetition[] = []
|
const cards: Repetition[] = []
|
||||||
@@ -55,7 +55,7 @@ export const useSpacedRepetitionCards = () => {
|
|||||||
$type: DataType.RepetitionCard,
|
$type: DataType.RepetitionCard,
|
||||||
level: 1,
|
level: 1,
|
||||||
repeatDate: new Date(),
|
repeatDate: new Date(),
|
||||||
needsReview: false,
|
needsReview: false
|
||||||
})
|
})
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -77,20 +77,20 @@ export const useSpacedRepetitionCards = () => {
|
|||||||
card: {
|
card: {
|
||||||
front: toHTML(front),
|
front: toHTML(front),
|
||||||
back: toHTML(back),
|
back: toHTML(back),
|
||||||
references: toHTML(references),
|
references: toHTML(references)
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return cards
|
return cards
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
{ immediate: false },
|
{ immediate: false }
|
||||||
)
|
)
|
||||||
|
|
||||||
const successRepetition = async (cardId: string) => {
|
const successRepetition = async (cardId: string) => {
|
||||||
const repetition = await data.get<DataType.RepetitionCard, RepetitionCard>(
|
const repetition = await data.get<DataType.RepetitionCard, RepetitionCard>(
|
||||||
cardId,
|
cardId
|
||||||
)
|
)
|
||||||
if (!repetition) {
|
if (!repetition) {
|
||||||
return
|
return
|
||||||
@@ -100,13 +100,13 @@ export const useSpacedRepetitionCards = () => {
|
|||||||
...repetition,
|
...repetition,
|
||||||
needsReview: false,
|
needsReview: false,
|
||||||
level: Math.min(repetition.level + 1, MAX_LEVEL),
|
level: Math.min(repetition.level + 1, MAX_LEVEL),
|
||||||
repeatDate: addDays(new Date(), 2 ** repetition.level),
|
repeatDate: addDays(new Date(), 2 ** repetition.level)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const failRepetition = async (cardId: string) => {
|
const failRepetition = async (cardId: string) => {
|
||||||
const repetition = await data.get<DataType.RepetitionCard, RepetitionCard>(
|
const repetition = await data.get<DataType.RepetitionCard, RepetitionCard>(
|
||||||
cardId,
|
cardId
|
||||||
)
|
)
|
||||||
if (!repetition) {
|
if (!repetition) {
|
||||||
return
|
return
|
||||||
@@ -118,13 +118,13 @@ export const useSpacedRepetitionCards = () => {
|
|||||||
...repetition,
|
...repetition,
|
||||||
level,
|
level,
|
||||||
needsReview: false,
|
needsReview: false,
|
||||||
repeatDate: addDays(new Date(), level),
|
repeatDate: addDays(new Date(), level)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const needsReview = async (cardId: string) => {
|
const needsReview = async (cardId: string) => {
|
||||||
const repetition = await data.get<DataType.RepetitionCard, RepetitionCard>(
|
const repetition = await data.get<DataType.RepetitionCard, RepetitionCard>(
|
||||||
cardId,
|
cardId
|
||||||
)
|
)
|
||||||
if (!repetition) {
|
if (!repetition) {
|
||||||
return
|
return
|
||||||
@@ -132,7 +132,7 @@ export const useSpacedRepetitionCards = () => {
|
|||||||
|
|
||||||
await data.update<DataType.RepetitionCard, RepetitionCard>({
|
await data.update<DataType.RepetitionCard, RepetitionCard>({
|
||||||
...repetition,
|
...repetition,
|
||||||
needsReview: true,
|
needsReview: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ export const useSpacedRepetitionCards = () => {
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
listenToClick()
|
listenToClick()
|
||||||
}),
|
}),
|
||||||
{ immediate: true },
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(cardFiles, () => execute())
|
watch(cardFiles, () => execute())
|
||||||
@@ -152,6 +152,6 @@ export const useSpacedRepetitionCards = () => {
|
|||||||
successRepetition,
|
successRepetition,
|
||||||
failRepetition,
|
failRepetition,
|
||||||
needsReview,
|
needsReview,
|
||||||
isLoading: !isReady,
|
isLoading: !isReady
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { Model } from '@/data/models/Model'
|
import { Model } from "@/data/models/Model"
|
||||||
|
|
||||||
export interface RepetitionCard extends Model<DataType.RepetitionCard> {
|
export interface RepetitionCard extends Model<DataType.RepetitionCard> {
|
||||||
level: number
|
level: number
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useLastVisitedRepos } from '@/modules/history/hooks/useLastVisitedRepos.hook'
|
import { useLastVisitedRepos } from "@/modules/history/hooks/useLastVisitedRepos.hook"
|
||||||
|
|
||||||
const { lastVisitedRepos } = useLastVisitedRepos()
|
const { lastVisitedRepos } = useLastVisitedRepos()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { useAsyncState } from '@vueuse/core'
|
import { useAsyncState } from "@vueuse/core"
|
||||||
import { computed } from 'vue'
|
import { computed } from "vue"
|
||||||
|
|
||||||
import { data } from '@/data/data'
|
import { data } from "@/data/data"
|
||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { History } from '@/data/models/History'
|
import { History } from "@/data/models/History"
|
||||||
|
|
||||||
const HISTORY_ID = data.generateId(DataType.History, 'history')
|
const HISTORY_ID = data.generateId(DataType.History, "history")
|
||||||
|
|
||||||
export const useLastVisitedRepos = () => {
|
export const useLastVisitedRepos = () => {
|
||||||
const history = useAsyncState(
|
const history = useAsyncState(
|
||||||
() =>
|
() =>
|
||||||
data.get<DataType.History, History>(
|
data.get<DataType.History, History>(
|
||||||
data.generateId(DataType.History, 'history')
|
data.generateId(DataType.History, "history")
|
||||||
),
|
),
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Ref, toValue } from 'vue'
|
import { Ref, toValue } from "vue"
|
||||||
|
|
||||||
import { data } from '@/data/data'
|
import { data } from "@/data/data"
|
||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { History } from '@/data/models/History'
|
import { History } from "@/data/models/History"
|
||||||
|
|
||||||
const HISTORY_ID = data.generateId(DataType.History, 'history')
|
const HISTORY_ID = data.generateId(DataType.History, "history")
|
||||||
const MAX_REPO_HISTORY = 10
|
const MAX_REPO_HISTORY = 10
|
||||||
|
|
||||||
export const useVisitRepo = (newRepo: {
|
export const useVisitRepo = (newRepo: {
|
||||||
|
|||||||
16
src/modules/note/cache/prepareNoteCache.ts
vendored
16
src/modules/note/cache/prepareNoteCache.ts
vendored
@@ -1,14 +1,14 @@
|
|||||||
import { data } from '@/data/data'
|
import { data } from "@/data/data"
|
||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { Note } from '@/modules/note/models/Note'
|
import { Note } from "@/modules/note/models/Note"
|
||||||
import { useUserRepoStore } from '@/modules/repo/store/userRepo.store'
|
import { useUserRepoStore } from "@/modules/repo/store/userRepo.store"
|
||||||
|
|
||||||
type NoteCacheResult =
|
type NoteCacheResult =
|
||||||
| {
|
| {
|
||||||
note: Note
|
note: Note
|
||||||
from: 'sha'
|
from: "sha"
|
||||||
}
|
}
|
||||||
| { note: Note; from: 'path' }
|
| { note: Note; from: "path" }
|
||||||
| { note: null; from: null }
|
| { note: null; from: null }
|
||||||
|
|
||||||
export const prepareNoteCache = (sha: string, path?: string) => {
|
export const prepareNoteCache = (sha: string, path?: string) => {
|
||||||
@@ -20,7 +20,7 @@ export const prepareNoteCache = (sha: string, path?: string) => {
|
|||||||
const note = await data.get<DataType.Note, Note>(noteId)
|
const note = await data.get<DataType.Note, Note>(noteId)
|
||||||
|
|
||||||
if (note) {
|
if (note) {
|
||||||
return { note, from: 'sha' }
|
return { note, from: "sha" }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notePath) {
|
if (notePath) {
|
||||||
@@ -33,7 +33,7 @@ export const prepareNoteCache = (sha: string, path?: string) => {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
note,
|
note,
|
||||||
from: 'path'
|
from: "path"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
|
|
||||||
import { useUserRepoStore } from "@/modules/repo/store/userRepo.store"
|
import { useUserRepoStore } from "@/modules/repo/store/userRepo.store"
|
||||||
|
|
||||||
export const useFolderNotes = (folders: string[]) => {
|
export const useFolderNotes = (folders: string[]) => {
|
||||||
@@ -8,8 +9,8 @@ export const useFolderNotes = (folders: string[]) => {
|
|||||||
store.files.filter(
|
store.files.filter(
|
||||||
(file) =>
|
(file) =>
|
||||||
folders.some((folder) => file.path?.startsWith(folder)) &&
|
folders.some((folder) => file.path?.startsWith(folder)) &&
|
||||||
file.path?.endsWith(".md"),
|
file.path?.endsWith(".md")
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const content = computed(() =>
|
const content = computed(() =>
|
||||||
@@ -23,10 +24,10 @@ export const useFolderNotes = (folders: string[]) => {
|
|||||||
})`
|
})`
|
||||||
})
|
})
|
||||||
.join("\n")
|
.join("\n")
|
||||||
: "",
|
: ""
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content,
|
content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ export const useNotes = () => {
|
|||||||
const store = useUserRepoStore()
|
const store = useUserRepoStore()
|
||||||
|
|
||||||
const notes = computed(() =>
|
const notes = computed(() =>
|
||||||
store.files.filter((file) => file.path?.endsWith(".md")),
|
store.files.filter((file) => file.path?.endsWith(".md"))
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
notes,
|
notes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { Model } from '@/data/models/Model'
|
import { Model } from "@/data/models/Model"
|
||||||
import { Backlink } from '@/modules/note/models/Backlink'
|
import { Backlink } from "@/modules/note/models/Backlink"
|
||||||
|
|
||||||
export interface BacklinkNote extends Model<DataType.BacklinkNote> {
|
export interface BacklinkNote extends Model<DataType.BacklinkNote> {
|
||||||
sha: string
|
sha: string
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { initContract } from "@ts-rest/core"
|
import { initContract } from "@ts-rest/core"
|
||||||
import { type } from "arktype"
|
|
||||||
import { initQueryClient } from "@ts-rest/vue-query"
|
import { initQueryClient } from "@ts-rest/vue-query"
|
||||||
|
import { type } from "arktype"
|
||||||
|
|
||||||
const PublicNoteListItem = type({
|
const PublicNoteListItem = type({
|
||||||
did: "string",
|
did: "string",
|
||||||
rkey: "string",
|
rkey: "string",
|
||||||
title: "string",
|
title: "string",
|
||||||
publishedAt: "string",
|
publishedAt: "string",
|
||||||
createdAt: "string",
|
createdAt: "string"
|
||||||
})
|
})
|
||||||
|
|
||||||
export type PublicNoteListItem = typeof PublicNoteListItem.infer
|
export type PublicNoteListItem = typeof PublicNoteListItem.infer
|
||||||
@@ -18,7 +18,7 @@ const PublicNote = type({
|
|||||||
title: "string",
|
title: "string",
|
||||||
content: "string",
|
content: "string",
|
||||||
publishedAt: "string",
|
publishedAt: "string",
|
||||||
createdAt: "string",
|
createdAt: "string"
|
||||||
})
|
})
|
||||||
|
|
||||||
export type PublicNote = typeof PublicNote.infer
|
export type PublicNote = typeof PublicNote.infer
|
||||||
@@ -31,34 +31,34 @@ export const noteRouter = contract.router({
|
|||||||
path: "/notes",
|
path: "/notes",
|
||||||
query: type({
|
query: type({
|
||||||
cursor: "string | undefined",
|
cursor: "string | undefined",
|
||||||
limit: "number | undefined",
|
limit: "number | undefined"
|
||||||
}),
|
}),
|
||||||
responses: {
|
responses: {
|
||||||
200: type({
|
200: type({
|
||||||
notes: PublicNoteListItem.array(),
|
notes: PublicNoteListItem.array()
|
||||||
}),
|
})
|
||||||
},
|
},
|
||||||
summary: "List all notes",
|
summary: "List all notes"
|
||||||
},
|
},
|
||||||
noteListsByDid: {
|
noteListsByDid: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: "/:did/notes",
|
path: "/:did/notes",
|
||||||
pathParams: type({
|
pathParams: type({
|
||||||
did: "string",
|
did: "string"
|
||||||
}),
|
}),
|
||||||
query: type({
|
query: type({
|
||||||
cursor: "string | undefined",
|
cursor: "string | undefined",
|
||||||
limit: "number | undefined",
|
limit: "number | undefined"
|
||||||
}),
|
}),
|
||||||
responses: {
|
responses: {
|
||||||
200: type({
|
200: type({
|
||||||
notes: PublicNoteListItem.array(),
|
notes: PublicNoteListItem.array()
|
||||||
}),
|
})
|
||||||
},
|
|
||||||
summary: "List all notes",
|
|
||||||
},
|
},
|
||||||
|
summary: "List all notes"
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const client = initQueryClient(noteRouter, {
|
export const client = initQueryClient(noteRouter, {
|
||||||
baseUrl: "https://api.remanso.space",
|
baseUrl: "https://api.remanso.space"
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from "vue"
|
||||||
|
|
||||||
import { data } from '@/data/data'
|
import { data } from "@/data/data"
|
||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { useRepos } from '@/hooks/useRepos.hook'
|
import { useRepos } from "@/hooks/useRepos.hook"
|
||||||
import { RepoBase } from '@/modules/repo/interfaces/RepoBase'
|
import { RepoBase } from "@/modules/repo/interfaces/RepoBase"
|
||||||
import { FavoriteRepo } from '@/modules/repo/models/FavoriteRepo'
|
import { FavoriteRepo } from "@/modules/repo/models/FavoriteRepo"
|
||||||
|
|
||||||
export const useFavoriteRepos = () => {
|
export const useFavoriteRepos = () => {
|
||||||
const { repos } = useRepos()
|
const { repos } = useRepos()
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from "vue"
|
||||||
|
|
||||||
import { useRepos } from '@/hooks/useRepos.hook'
|
import { useRepos } from "@/hooks/useRepos.hook"
|
||||||
import { useFavoriteRepos } from '@/modules/repo/hooks/useFavoriteRepos.hook'
|
import { useFavoriteRepos } from "@/modules/repo/hooks/useFavoriteRepos.hook"
|
||||||
import { RepoBase } from '@/modules/repo/interfaces/RepoBase'
|
import { RepoBase } from "@/modules/repo/interfaces/RepoBase"
|
||||||
|
|
||||||
export const useRepoList = () => {
|
export const useRepoList = () => {
|
||||||
const { savedFavoriteRepos, addFavorite, removeFavorite } = useFavoriteRepos()
|
const { savedFavoriteRepos, addFavorite, removeFavorite } = useFavoriteRepos()
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ export interface UserSettings extends Model<DataType.UserSettings> {
|
|||||||
fontSize?: string
|
fontSize?: string
|
||||||
chosenFontSize?: string
|
chosenFontSize?: string
|
||||||
backlink?: boolean
|
backlink?: boolean
|
||||||
|
chosenTitleFont?: string
|
||||||
|
chosenBodyFont?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { Model } from '@/data/models/Model'
|
import { Model } from "@/data/models/Model"
|
||||||
|
|
||||||
export interface FavoriteRepo extends Model<DataType.FavoriteRepo> {
|
export interface FavoriteRepo extends Model<DataType.FavoriteRepo> {
|
||||||
isFavorite: boolean
|
isFavorite: boolean
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DataType } from '@/data/DataType.enum'
|
import { DataType } from "@/data/DataType.enum"
|
||||||
import { Model } from '@/data/models/Model'
|
import { Model } from "@/data/models/Model"
|
||||||
import { RepoFile } from '@/modules/repo/interfaces/RepoFile'
|
import { RepoFile } from "@/modules/repo/interfaces/RepoFile"
|
||||||
|
|
||||||
export interface SavedRepo extends Model<DataType.SavedRepo> {
|
export interface SavedRepo extends Model<DataType.SavedRepo> {
|
||||||
user: string
|
user: string
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Octokit } from '@octokit/rest'
|
import { Octokit } from "@octokit/rest"
|
||||||
|
|
||||||
import { getAccessToken } from '@/modules/user/service/signIn'
|
import { getAccessToken } from "@/modules/user/service/signIn"
|
||||||
|
|
||||||
export const getOctokit = async (): Promise<Octokit> => {
|
export const getOctokit = async (): Promise<Octokit> => {
|
||||||
const response = await getAccessToken()
|
const response = await getAccessToken()
|
||||||
|
|
||||||
return new Octokit({
|
return new Octokit({
|
||||||
auth: response?.token ?? ''
|
auth: response?.token ?? ""
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { getOctokit } from "@/modules/repo/services/octo"
|
|||||||
|
|
||||||
export const getFiles = async (
|
export const getFiles = async (
|
||||||
owner: string,
|
owner: string,
|
||||||
repo: string,
|
repo: string
|
||||||
): Promise<RepoFile[]> => {
|
): Promise<RepoFile[]> => {
|
||||||
if (!owner || !repo) {
|
if (!owner || !repo) {
|
||||||
return []
|
return []
|
||||||
@@ -15,7 +15,7 @@ export const getFiles = async (
|
|||||||
|
|
||||||
const commits = await octokit.request("GET /repos/{owner}/{repo}/commits", {
|
const commits = await octokit.request("GET /repos/{owner}/{repo}/commits", {
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo
|
||||||
})
|
})
|
||||||
|
|
||||||
const lastCommit = commits.data.shift()
|
const lastCommit = commits.data.shift()
|
||||||
@@ -30,8 +30,8 @@ export const getFiles = async (
|
|||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
tree_sha: lastCommit.commit.tree.sha,
|
tree_sha: lastCommit.commit.tree.sha,
|
||||||
recursive: "true",
|
recursive: "true"
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return treeResponse?.data.tree.filter((t) => t.type === "blob") ?? []
|
return treeResponse?.data.tree.filter((t) => t.type === "blob") ?? []
|
||||||
@@ -60,7 +60,7 @@ export const getMainReadme = async (owner: string, repo: string) => {
|
|||||||
|
|
||||||
const { render } = markdownBuilder()
|
const { render } = markdownBuilder()
|
||||||
const { getCachedNote, saveCacheNote } = prepareNoteCache(
|
const { getCachedNote, saveCacheNote } = prepareNoteCache(
|
||||||
`${owner}-${repo}-README`,
|
`${owner}-${repo}-README`
|
||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -68,7 +68,7 @@ export const getMainReadme = async (owner: string, repo: string) => {
|
|||||||
|
|
||||||
const README = await octokit.repos.getReadme({
|
const README = await octokit.repos.getReadme({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo
|
||||||
})
|
})
|
||||||
|
|
||||||
if (README) {
|
if (README) {
|
||||||
@@ -90,7 +90,7 @@ export const getMainReadme = async (owner: string, repo: string) => {
|
|||||||
export const getUserSettingsContent = async (
|
export const getUserSettingsContent = async (
|
||||||
user: string,
|
user: string,
|
||||||
repo: string,
|
repo: string,
|
||||||
files: RepoFile[],
|
files: RepoFile[]
|
||||||
): Promise<Omit<UserSettings, "chosenFontFamily"> | null> => {
|
): Promise<Omit<UserSettings, "chosenFontFamily"> | null> => {
|
||||||
const configFile = files.find((file) => file.path === ".remanso.json")
|
const configFile = files.find((file) => file.path === ".remanso.json")
|
||||||
|
|
||||||
@@ -104,13 +104,22 @@ export const getUserSettingsContent = async (
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSON.parse(atob(content)) as UserSettings
|
const raw = JSON.parse(atob(content)) as UserSettings & {
|
||||||
|
t?: string
|
||||||
|
p?: string
|
||||||
|
}
|
||||||
|
const { t, p, ...rest } = raw
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
chosenTitleFont: t,
|
||||||
|
chosenBodyFont: p
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const queryFileContent = async (
|
export const queryFileContent = async (
|
||||||
user: string,
|
user: string,
|
||||||
repo: string,
|
repo: string,
|
||||||
sha: string,
|
sha: string
|
||||||
) => {
|
) => {
|
||||||
const octokit = await getOctokit()
|
const octokit = await getOctokit()
|
||||||
|
|
||||||
@@ -123,8 +132,8 @@ export const queryFileContent = async (
|
|||||||
{
|
{
|
||||||
owner: user,
|
owner: user,
|
||||||
repo: repo,
|
repo: repo,
|
||||||
file_sha: sha,
|
file_sha: sha
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return file?.data.content ?? null
|
return file?.data.content ?? null
|
||||||
|
|||||||
@@ -1,43 +1,43 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from "vitest"
|
||||||
|
|
||||||
import { resolvePath } from './resolvePath'
|
import { resolvePath } from "./resolvePath"
|
||||||
|
|
||||||
describe('resolve path service', () => {
|
describe("resolve path service", () => {
|
||||||
it('set the absolute path if path to resolve is empty', () => {
|
it("set the absolute path if path to resolve is empty", () => {
|
||||||
expect(resolvePath('standard/README.md', '')).toEqual('standard/')
|
expect(resolvePath("standard/README.md", "")).toEqual("standard/")
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns the path sanitized if there is no absolute path', () => {
|
it("returns the path sanitized if there is no absolute path", () => {
|
||||||
expect(resolvePath('', './here/note.md')).toEqual('here/note.md')
|
expect(resolvePath("", "./here/note.md")).toEqual("here/note.md")
|
||||||
})
|
})
|
||||||
|
|
||||||
it('set the absolute path from the current path', () => {
|
it("set the absolute path from the current path", () => {
|
||||||
expect(resolvePath('standard/README.md', './other-note.md')).toEqual(
|
expect(resolvePath("standard/README.md", "./other-note.md")).toEqual(
|
||||||
'standard/other-note.md'
|
"standard/other-note.md"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('set the absolute path from the current path with multiple level', () => {
|
it("set the absolute path from the current path with multiple level", () => {
|
||||||
expect(
|
expect(
|
||||||
resolvePath('standard/you/are/here/README.md', './other-note.md')
|
resolvePath("standard/you/are/here/README.md", "./other-note.md")
|
||||||
).toEqual('standard/you/are/here/other-note.md')
|
).toEqual("standard/you/are/here/other-note.md")
|
||||||
})
|
})
|
||||||
|
|
||||||
it('set the absolute path from the current path with a go back in the relative path', () => {
|
it("set the absolute path from the current path with a go back in the relative path", () => {
|
||||||
expect(
|
expect(
|
||||||
resolvePath('standard/you/are/here/README.md', '../other-note.md')
|
resolvePath("standard/you/are/here/README.md", "../other-note.md")
|
||||||
).toEqual('standard/you/are/other-note.md')
|
).toEqual("standard/you/are/other-note.md")
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
resolvePath('standard/you/are/here/README.md', '../../other-note.md')
|
resolvePath("standard/you/are/here/README.md", "../../other-note.md")
|
||||||
).toEqual('standard/you/other-note.md')
|
).toEqual("standard/you/other-note.md")
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
resolvePath('standard/you/are/here/README.md', './../../other-note.md')
|
resolvePath("standard/you/are/here/README.md", "./../../other-note.md")
|
||||||
).toEqual('standard/you/other-note.md')
|
).toEqual("standard/you/other-note.md")
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
resolvePath('standard/you/are/here/README.md', './../../../other-note.md')
|
resolvePath("standard/you/are/here/README.md", "./../../../other-note.md")
|
||||||
).toEqual('standard/other-note.md')
|
).toEqual("standard/other-note.md")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
const sanitizePath = (path: string) => {
|
const sanitizePath = (path: string) => {
|
||||||
if (path.startsWith('./')) {
|
if (path.startsWith("./")) {
|
||||||
return decodeURIComponent(path.replace('./', ''))
|
return decodeURIComponent(path.replace("./", ""))
|
||||||
}
|
}
|
||||||
return decodeURIComponent(path)
|
return decodeURIComponent(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeNoteFilename = (pathNote: string) => {
|
const removeNoteFilename = (pathNote: string) => {
|
||||||
const path = pathNote.split('/')
|
const path = pathNote.split("/")
|
||||||
path.pop()
|
path.pop()
|
||||||
|
|
||||||
return sanitizePath(path.join('/'))
|
return sanitizePath(path.join("/"))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const resolvePath = (
|
export const resolvePath = (
|
||||||
@@ -19,11 +19,11 @@ export const resolvePath = (
|
|||||||
let currentAbsolutePath = removeNoteFilename(currentAbsolutePathNote)
|
let currentAbsolutePath = removeNoteFilename(currentAbsolutePathNote)
|
||||||
pathToResolve = sanitizePath(pathToResolve)
|
pathToResolve = sanitizePath(pathToResolve)
|
||||||
|
|
||||||
while (pathToResolve.startsWith('../')) {
|
while (pathToResolve.startsWith("../")) {
|
||||||
const adjustedAbsolutePath = currentAbsolutePath.split('/')
|
const adjustedAbsolutePath = currentAbsolutePath.split("/")
|
||||||
adjustedAbsolutePath.pop()
|
adjustedAbsolutePath.pop()
|
||||||
currentAbsolutePath = adjustedAbsolutePath.join('/')
|
currentAbsolutePath = adjustedAbsolutePath.join("/")
|
||||||
pathToResolve = pathToResolve.replace('../', '')
|
pathToResolve = pathToResolve.replace("../", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentAbsolutePath
|
return currentAbsolutePath
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
getCachedMainReadme,
|
getCachedMainReadme,
|
||||||
getFiles,
|
getFiles,
|
||||||
getMainReadme,
|
getMainReadme,
|
||||||
getUserSettingsContent,
|
getUserSettingsContent
|
||||||
} from "@/modules/repo/services/repo"
|
} from "@/modules/repo/services/repo"
|
||||||
import { refreshToken } from "@/modules/user/service/signIn"
|
import { refreshToken } from "@/modules/user/service/signIn"
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
|
|||||||
files: [],
|
files: [],
|
||||||
readme: undefined,
|
readme: undefined,
|
||||||
userSettings: undefined,
|
userSettings: undefined,
|
||||||
needToLogin: false,
|
needToLogin: false
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async setUserRepo(user: string, repo: string) {
|
async setUserRepo(user: string, repo: string) {
|
||||||
@@ -38,7 +38,7 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
|
|||||||
|
|
||||||
const savedRepoId = data.generateId(DataType.SavedRepo, `${user}-${repo}`)
|
const savedRepoId = data.generateId(DataType.SavedRepo, `${user}-${repo}`)
|
||||||
const cachedSavedRepo = await data.get<DataType.SavedRepo, SavedRepo>(
|
const cachedSavedRepo = await data.get<DataType.SavedRepo, SavedRepo>(
|
||||||
savedRepoId,
|
savedRepoId
|
||||||
)
|
)
|
||||||
|
|
||||||
if (cachedSavedRepo) {
|
if (cachedSavedRepo) {
|
||||||
@@ -68,19 +68,27 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
|
|||||||
$type: DataType.SavedRepo,
|
$type: DataType.SavedRepo,
|
||||||
repo,
|
repo,
|
||||||
user,
|
user,
|
||||||
files,
|
files
|
||||||
})
|
})
|
||||||
this.files = files
|
this.files = files
|
||||||
return getUserSettingsContent(user, repo, files)
|
return getUserSettingsContent(user, repo, files)
|
||||||
})
|
})
|
||||||
.then((userSettings) => {
|
.then((userSettings) => {
|
||||||
const chosenFontFamily = userSettings?.fontFamilies?.find(
|
const chosenFontFamily = userSettings?.fontFamilies?.find(
|
||||||
(font) => font === this.userSettings?.chosenFontFamily,
|
(font) => font === this.userSettings?.chosenFontFamily
|
||||||
)
|
)
|
||||||
? this.userSettings?.chosenFontFamily
|
? this.userSettings?.chosenFontFamily
|
||||||
: userSettings?.fontFamily
|
: userSettings?.fontFamily
|
||||||
const chosenFontSize =
|
const chosenFontSize =
|
||||||
this.userSettings?.chosenFontSize ?? userSettings?.fontSize
|
this.userSettings?.chosenFontSize ?? userSettings?.fontSize
|
||||||
|
const chosenTitleFont =
|
||||||
|
this.userSettings?.chosenTitleFont ??
|
||||||
|
userSettings?.chosenTitleFont ??
|
||||||
|
chosenFontFamily
|
||||||
|
const chosenBodyFont =
|
||||||
|
this.userSettings?.chosenBodyFont ??
|
||||||
|
userSettings?.chosenBodyFont ??
|
||||||
|
chosenFontFamily
|
||||||
this.userSettings = userSettings
|
this.userSettings = userSettings
|
||||||
|
|
||||||
if (!this.userSettings) {
|
if (!this.userSettings) {
|
||||||
@@ -91,10 +99,12 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
|
|||||||
chosenFontFamily ?? this.userSettings.fontFamily
|
chosenFontFamily ?? this.userSettings.fontFamily
|
||||||
this.userSettings.chosenFontSize =
|
this.userSettings.chosenFontSize =
|
||||||
chosenFontSize ?? this.userSettings.fontSize
|
chosenFontSize ?? this.userSettings.fontSize
|
||||||
|
this.userSettings.chosenTitleFont = chosenTitleFont
|
||||||
|
this.userSettings.chosenBodyFont = chosenBodyFont
|
||||||
|
|
||||||
data.update<DataType.UserSettings, UserSettings>({
|
data.update<DataType.UserSettings, UserSettings>({
|
||||||
...this.userSettings,
|
...this.userSettings,
|
||||||
_id: userSettingsId,
|
_id: userSettingsId
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -116,7 +126,7 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
|
|||||||
|
|
||||||
const savedRepoId = data.generateId(
|
const savedRepoId = data.generateId(
|
||||||
DataType.SavedRepo,
|
DataType.SavedRepo,
|
||||||
`${this.user}-${this.repo}`,
|
`${this.user}-${this.repo}`
|
||||||
)
|
)
|
||||||
const newFiles = [...this.files.filter((f) => f.sha !== file.sha), file]
|
const newFiles = [...this.files.filter((f) => f.sha !== file.sha), file]
|
||||||
data.update<DataType.SavedRepo, SavedRepo>({
|
data.update<DataType.SavedRepo, SavedRepo>({
|
||||||
@@ -124,7 +134,7 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
|
|||||||
$type: DataType.SavedRepo,
|
$type: DataType.SavedRepo,
|
||||||
repo: this.repo,
|
repo: this.repo,
|
||||||
user: this.user,
|
user: this.user,
|
||||||
files: newFiles,
|
files: newFiles
|
||||||
})
|
})
|
||||||
this.files = newFiles
|
this.files = newFiles
|
||||||
},
|
},
|
||||||
@@ -147,7 +157,7 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
|
|||||||
const userSettingsId = `UserSetting-${this.user}-${this.repo}`
|
const userSettingsId = `UserSetting-${this.user}-${this.repo}`
|
||||||
data.update<DataType.UserSettings, UserSettings>({
|
data.update<DataType.UserSettings, UserSettings>({
|
||||||
...this.userSettings,
|
...this.userSettings,
|
||||||
_id: userSettingsId,
|
_id: userSettingsId
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setFontSize(fontSize: string) {
|
setFontSize(fontSize: string) {
|
||||||
@@ -159,8 +169,32 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
|
|||||||
const userSettingsId = `UserSetting-${this.user}-${this.repo}`
|
const userSettingsId = `UserSetting-${this.user}-${this.repo}`
|
||||||
data.update<DataType.UserSettings, UserSettings>({
|
data.update<DataType.UserSettings, UserSettings>({
|
||||||
...this.userSettings,
|
...this.userSettings,
|
||||||
_id: userSettingsId,
|
_id: userSettingsId
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
setTitleFont(font: string) {
|
||||||
|
if (!this.userSettings) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.userSettings.chosenTitleFont = font
|
||||||
|
|
||||||
|
const userSettingsId = `UserSetting-${this.user}-${this.repo}`
|
||||||
|
data.update<DataType.UserSettings, UserSettings>({
|
||||||
|
...this.userSettings,
|
||||||
|
_id: userSettingsId
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setBodyFont(font: string) {
|
||||||
|
if (!this.userSettings) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.userSettings.chosenBodyFont = font
|
||||||
|
|
||||||
|
const userSettingsId = `UserSetting-${this.user}-${this.repo}`
|
||||||
|
data.update<DataType.UserSettings, UserSettings>({
|
||||||
|
...this.userSettings,
|
||||||
|
_id: userSettingsId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,10 +12,15 @@ export const useUserSettings = () => {
|
|||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
const root = document.documentElement
|
const root = document.documentElement
|
||||||
|
|
||||||
const fontFamily = store.userSettings?.chosenFontFamily
|
|
||||||
const fontSize = store.userSettings?.chosenFontSize
|
const fontSize = store.userSettings?.chosenFontSize
|
||||||
|
const bodyFont = store.userSettings?.chosenBodyFont
|
||||||
|
const titleFont = store.userSettings?.chosenTitleFont
|
||||||
|
|
||||||
downloadFont(fontFamily || DEFAULT_FONT_POLICY)
|
downloadFont(bodyFont || DEFAULT_FONT_POLICY, "--font-family")
|
||||||
|
downloadFont(
|
||||||
|
titleFont || bodyFont || DEFAULT_FONT_POLICY,
|
||||||
|
"--title-font-family"
|
||||||
|
)
|
||||||
root.style.setProperty("--font-size", fontSize || DEFAULT_FONT_SIZE)
|
root.style.setProperty("--font-size", fontSize || DEFAULT_FONT_SIZE)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ export interface GithubToken {
|
|||||||
refresh_token: string
|
refresh_token: string
|
||||||
refresh_token_expires_in: number
|
refresh_token_expires_in: number
|
||||||
scope: string
|
scope: string
|
||||||
token_type: 'bearer'
|
token_type: "bearer"
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user