Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"css.lint.unknownAtRules": "ignore",
"scss.lint.unknownAtRules": "ignore",
"less.lint.unknownAtRules": "ignore"
}
131 changes: 97 additions & 34 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { copyFileSync, mkdirSync, readFileSync, readdirSync, statSync } from "fs";
/** @format */

import {
copyFileSync,
mkdirSync,
readFileSync,
readdirSync,
statSync,
} from "fs";
import { dirname, join, relative, resolve } from "path";
import { defineConfig, type HeadConfig } from "vitepress";
import { extendConfig } from "@voidzero-dev/vitepress-theme/config";
import { tabsMarkdownPlugin } from "vitepress-plugin-tabs";

function loadEnvVar(key: string): string | undefined {
Expand All @@ -21,8 +30,9 @@ const algoliaAppId = loadEnvVar("VITE_ALGOLIA_APP_ID");
const algoliaApiKey = loadEnvVar("VITE_ALGOLIA_API_KEY");
const algoliaIndexName = loadEnvVar("VITE_ALGOLIA_INDEX_NAME");

const posthogHead: HeadConfig[] = posthogKey
? [
const posthogHead: HeadConfig[] =
posthogKey ?
[
[
"script",
{},
Expand All @@ -33,19 +43,19 @@ const posthogHead: HeadConfig[] = posthogKey
: [];

const searchConfig =
algoliaAppId && algoliaApiKey && algoliaIndexName
? {
provider: "algolia" as const,
options: {
appId: algoliaAppId,
apiKey: algoliaApiKey,
indexName: algoliaIndexName,
insights: true,
},
}
: { provider: "local" as const };
algoliaAppId && algoliaApiKey && algoliaIndexName ?
{
provider: "algolia" as const,
options: {
appId: algoliaAppId,
apiKey: algoliaApiKey,
indexName: algoliaIndexName,
insights: true,
},
}
: { provider: "local" as const };

export default defineConfig({
const config = defineConfig({
title: "Plane",
description: "Modern project management software",
cleanUrls: true,
Expand All @@ -57,7 +67,12 @@ export default defineConfig({

function walk(dir: string): void {
for (const entry of readdirSync(dir)) {
if (entry === ".vitepress" || entry === "public" || entry === "node_modules") continue;
if (
entry === ".vitepress" ||
entry === "public" ||
entry === "node_modules"
)
continue;
const abs = join(dir, entry);
const stat = statSync(abs);
if (stat.isDirectory()) {
Expand Down Expand Up @@ -102,13 +117,6 @@ export default defineConfig({
crossorigin: "anonymous",
},
],
[
"link",
{
rel: "stylesheet",
href: "https://fonts.googleapis.com/css2?family=Instrument+Sans:ital,wght@0,400..700;1,400..700&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=National+Park:wght@200..800&family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&family=PT+Sans:ital,wght@0,400;0,700;1,400;1,700&family=Raleway:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100..900;1,100..900&family=Work+Sans:ital,wght@0,100..900;1,100..900&display=swap",
},
],
[
"script",
{
Expand Down Expand Up @@ -192,14 +200,42 @@ export default defineConfig({
"project management, issue tracking, sprint management, agile, scrum, create projects, track sprints",
},
],
/**
* SSG inlines OSSHeader with data-theme from server isDark. Tailwind `dark:…`
* keys off [data-theme*="dark"] on that wrapper, so the bar can stay dark
* until Vue hydrates. The built-in "check-dark-mode" also only add()s
* html.dark. Run in setTimeout(0) so it executes after that script, then
* clear stale data-theme as soon as the bar exists.
*/
[
"script",
{},
`!function(){setTimeout(function(){
var k="vitepress-theme-appearance";
var p=localStorage.getItem(k)||"dark";
var m=matchMedia("(prefers-color-scheme: dark)").matches;
var d=!p||p==="auto"?m:p==="dark";
document.documentElement.classList.toggle("dark",d);
function bar(n){
var h=document.querySelector(".docs-layout header");
if(h&&h.parentElement){
var w=h.parentElement;
if(d)w.setAttribute("data-theme","dark");else w.removeAttribute("data-theme");
return;
}
if(n<200&&document.readyState==="loading")requestAnimationFrame(function(){bar(n+1)});
}bar(0);
},0);}();`,
],
],

themeConfig: {
variant: "voidzero",
logo: {
light: "https://media.docs.plane.so/logo/new-logo-white.png",
dark: "https://media.docs.plane.so/logo/new-logo-dark.png",
},
siteTitle: "",
siteTitle: "Plane",

Comment on lines 232 to 239
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

@voidzero-dev/vitepress-theme themeConfig.variant supported values list

💡 Result:

The supported values for themeConfig.variant in @voidzero-dev/vitepress-theme are strings corresponding to VoidZero projects, such as "viteplus", "vite", "vitest". Additional likely values based on project context include "rolldown" and "oxc", as the theme is designed for Vite+, Vite, Vitest, Rolldown, and Oxc. The variant controls branding colors and styling via CSS selectors like :root[data-variant="vite"]. Users import specific variants like '@voidzero-dev/vitepress-theme/src/vite' in the theme entry.

Citations:


Change variant: "voidzero" to a supported value.

The @voidzero-dev/vitepress-theme package only supports variants: "vite", "viteplus", "vitest", "rolldown", and "oxc". The variant "voidzero" is not recognized and will cause incorrect theme rendering or build failures. Use one of the supported variants that matches your target project.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/.vitepress/config.ts` around lines 225 - 232, The themeConfig currently
sets variant: "voidzero", which is unsupported by `@voidzero-dev/vitepress-theme`;
update the variant property inside themeConfig to one of the supported values
("vite", "viteplus", "vitest", "rolldown", or "oxc") that best matches your
project (e.g., change variant in themeConfig from "voidzero" to "vite" or
another supported option) so the theme renders and builds correctly.

outline: {
level: [2, 3],
Expand All @@ -209,21 +245,25 @@ export default defineConfig({
search: searchConfig,

socialLinks: [
{ icon: "github", link: "https://github.com/makeplane/plane" },
{
icon: {
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2z"/><path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"/></svg>',
svg: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 15L6.92474 18.1137C6.49579 18.548 6.28131 18.7652 6.09695 18.7805C5.93701 18.7938 5.78042 18.7295 5.67596 18.6076C5.55556 18.4672 5.55556 18.162 5.55556 17.5515V15.9916C5.55556 15.444 5.10707 15.0477 4.5652 14.9683V14.9683C3.25374 14.7762 2.22378 13.7463 2.03168 12.4348C2 12.2186 2 11.9605 2 11.4444V6.8C2 5.11984 2 4.27976 2.32698 3.63803C2.6146 3.07354 3.07354 2.6146 3.63803 2.32698C4.27976 2 5.11984 2 6.8 2H14.2C15.8802 2 16.7202 2 17.362 2.32698C17.9265 2.6146 18.3854 3.07354 18.673 3.63803C19 4.27976 19 5.11984 19 6.8V11M19 22L16.8236 20.4869C16.5177 20.2742 16.3647 20.1678 16.1982 20.0924C16.0504 20.0255 15.8951 19.9768 15.7356 19.9474C15.5558 19.9143 15.3695 19.9143 14.9969 19.9143H13.2C12.0799 19.9143 11.5198 19.9143 11.092 19.6963C10.7157 19.5046 10.4097 19.1986 10.218 18.8223C10 18.3944 10 17.8344 10 16.7143V14.2C10 13.0799 10 12.5198 10.218 12.092C10.4097 11.7157 10.7157 11.4097 11.092 11.218C11.5198 11 12.0799 11 13.2 11H18.8C19.9201 11 20.4802 11 20.908 11.218C21.2843 11.4097 21.5903 11.7157 21.782 12.092C22 12.5198 22 13.0799 22 14.2V16.9143C22 17.8462 22 18.3121 21.8478 18.6797C21.6448 19.1697 21.2554 19.5591 20.7654 19.762C20.3978 19.9143 19.9319 19.9143 19 19.9143V22Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
},
link: "https://forum.plane.so",
},
{ icon: "github", link: "https://github.com/makeplane/plane" },
{ icon: "twitter", link: "https://twitter.com/planepowers" },
{ icon: "linkedin", link: "https://www.linkedin.com/company/planepowers/" },
{
icon: "linkedin",
link: "https://www.linkedin.com/company/planepowers/",
},
],

nav: [
{
text: "Sign in",
link: "https://app.plane.so/sign-in",
noIcon: true,
},
],

Expand Down Expand Up @@ -302,7 +342,10 @@ export default defineConfig({
text: "Search workspace",
link: "/workspaces-and-users/search-workspace",
},
{ text: "Personalize homepage", link: "/core-concepts/account/overview" },
{
text: "Personalize homepage",
link: "/core-concepts/account/overview",
},
{ text: "Power K", link: "/core-concepts/power-k" },
{
text: "Customize navigation",
Expand Down Expand Up @@ -377,7 +420,10 @@ export default defineConfig({
text: "Projects",
collapsed: true,
items: [
{ text: "Manage projects", link: "/core-concepts/projects/overview" },
{
text: "Manage projects",
link: "/core-concepts/projects/overview",
},
{
text: "Manage members",
link: "/core-concepts/projects/manage-project-members",
Expand Down Expand Up @@ -410,7 +456,10 @@ export default defineConfig({
text: "Work Items",
collapsed: true,
items: [
{ text: "Manage work items", link: "/core-concepts/issues/overview" },
{
text: "Manage work items",
link: "/core-concepts/issues/overview",
},
{
text: "Work item properties",
link: "/core-concepts/issues/properties",
Expand Down Expand Up @@ -498,7 +547,10 @@ export default defineConfig({
text: "Pages",
collapsed: true,
items: [
{ text: "Manage project pages", link: "/core-concepts/pages/overview" },
{
text: "Manage project pages",
link: "/core-concepts/pages/overview",
},
{
text: "Editor blocks",
link: "/core-concepts/pages/editor-blocks",
Expand All @@ -520,7 +572,10 @@ export default defineConfig({
text: "Time Tracking",
link: "/core-concepts/issues/time-tracking",
},
{ text: "Workflows and Approvals", link: "/workflows-and-approvals/workflows" },
{
text: "Workflows and Approvals",
link: "/workflows-and-approvals/workflows",
},
{ text: "Custom Relations", link: "/work-items/custom-relations" },
{
text: "Automations",
Expand Down Expand Up @@ -554,8 +609,14 @@ export default defineConfig({
text: "Page Inline Comments",
link: "/core-concepts/pages/inline-comments",
},
{ text: "Subscribers", link: "/communication-and-collaboration/subscribers" },
{ text: "Notifications", link: "/communication-and-collaboration/notifications" },
{
text: "Subscribers",
link: "/communication-and-collaboration/subscribers",
},
{
text: "Notifications",
link: "/communication-and-collaboration/notifications",
},
{ text: "Inbox", link: "/communication-and-collaboration/inbox" },
],
},
Expand Down Expand Up @@ -697,3 +758,5 @@ export default defineConfig({
},
},
});

export default extendConfig(config);
16 changes: 16 additions & 0 deletions docs/.vitepress/theme/Layout.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script setup lang="ts">
import { computed, useSlots } from "vue";
import VoidZeroTheme from "@voidzero-dev/vitepress-theme";

const BaseLayout = VoidZeroTheme.Layout;
const slots = useSlots();
const forwardSlotNames = computed(() => Object.keys(slots) as string[]);
</script>

<template>
<BaseLayout>
<template v-for="name in forwardSlotNames" :key="name" #[name]="data">
<slot :name="name" v-bind="data || {}" />
</template>
</BaseLayout>
</template>
64 changes: 54 additions & 10 deletions docs/.vitepress/theme/components/Card.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
<!-- @format -->

<script setup>
import { computed } from "vue";
import * as LucideIcons from "lucide-vue-next";

const props = defineProps({
title: String,
icon: String,
/** Doc path or full URL */
href: String,
/** Alias for `href` (common in markdown) */
link: String,
/** When set, used instead of the default slot for body text */
description: String,
/** Bottom link label (shown with →). Home feature cards only. */
cta: String,
});

const hasDestination = computed(() => Boolean((props.link || props.href || "").toString().trim()));

const resolvedHref = computed(() => {
const h = (props.link || props.href || "").toString().trim();
return h;
});

// Custom SVG icons for brands
Expand Down Expand Up @@ -44,18 +60,46 @@ const IconComponent = computed(() => {
</script>

<template>
<a :href="href" class="card-link">
<div v-if="icon" class="card-icon">
<!-- Render custom SVG if available -->
<component
:is="hasDestination ? 'a' : 'div'"
:href="hasDestination ? resolvedHref : undefined"
class="card-link"
:class="{ 'card-link--with-cta': cta }"
>
Comment on lines +63 to +68
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

External links open in same tab without rel="noopener".

The homepage uses link="https://developers.plane.so/self-hosting/overview" which is external. The rendered <a> lacks target and rel attributes, so external destinations open in the same tab (losing docs context) and miss rel="noopener noreferrer" hardening.

♻️ Proposed refinement
+<script setup>
+// ...existing code...
+const isExternal = computed(() => /^https?:\/\//i.test(resolvedHref.value));
+</script>

 <template>
   <component
     :is="hasDestination ? 'a' : 'div'"
     :href="hasDestination ? resolvedHref : undefined"
+    :target="hasDestination && isExternal ? '_blank' : undefined"
+    :rel="hasDestination && isExternal ? 'noopener noreferrer' : undefined"
     class="card-link"
     :class="{ 'card-link--with-cta': cta }"
   >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/.vitepress/theme/components/Card.vue` around lines 63 - 68, The anchor
rendered by Card.vue uses hasDestination and resolvedHref but doesn't set
target/rel for external URLs; update the component rendering logic (using
hasDestination, resolvedHref and cta) to detect external links (e.g.,
resolvedHref startsWith('http') or origin !== current origin) and, only for
external destinations, set target="_blank" and rel="noopener noreferrer" on the
<a> element while leaving internal links unchanged.

<div v-if="icon" class="card-icon" aria-hidden="true">
<div v-if="customSvgIcons[icon]" v-html="customSvgIcons[icon]"></div>
<!-- Otherwise render Lucide icon -->
<component v-else :is="IconComponent" :size="24" />
<component v-else :is="IconComponent" :size="24" stroke-width="1.5" />
</div>
<h3 class="card-title">
{{ title }}
</h3>
<p class="card-description">
<h3 class="card-title">{{ title }}</h3>
<p v-if="description" class="card-description">
{{ description }}
</p>
<p v-else class="card-description">
<slot />
</p>
</a>
<span v-if="cta" class="card-cta">{{ cta }} →</span>
</component>
</template>

<style scoped>
.card-cta {
color: #006399 !important;
font-weight: 600;
}

.card-link:hover .card-cta {
color: #0078b8 !important;
}
</style>

<style>
html.dark .card-link .card-cta,
[data-theme="dark"] .card-link .card-cta {
color: #2893cc !important;
}

html.dark .card-link:hover .card-cta,
[data-theme="dark"] .card-link:hover .card-cta {
color: #3aa5d4 !important;
}
</style>
17 changes: 13 additions & 4 deletions docs/.vitepress/theme/components/CardGroup.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
<script setup>
defineProps({
import { computed } from "vue";

const props = defineProps({
cols: {
type: [Number, String],
default: 2,
},
/** Alias for `cols` (e.g. `<CardGroup columns="3">`) */
columns: {
type: [Number, String],
default: undefined,
},
});

const columnCount = computed(() => props.columns ?? props.cols);
</script>

<template>
<div
class="card-group"
:class="{
'card-group-2': cols == 2,
'card-group-3': cols == 3,
'card-group-4': cols == 4,
'card-group-2': columnCount == 2,
'card-group-3': columnCount == 3,
'card-group-4': columnCount == 4,
}"
>
<slot />
Expand Down
Loading
Loading